Click here to Skip to main content
Email Password   helpLost your password?

Introduction

Working as a developer prior to the advent of the .NET Framework really makes one appreciate the rich number of classes the Framework supports right out of the box. Many times however one may need to be working in the unmanaged world and would like to invoke one of those readily available classes provided in the managed world. Since I have been playing around with .NET for a while, I have learned many of the great classes available within the .NET Framework. SimonS recently posted a question in the C# forum that made me finish a little research and I wanted to share my finding with everyone here. I had actually been tinkering with this idea for a while but never actually had enough time to finish. Here it is in all its glory.

The problem is, suppose that I have written a nice library, set of utility functions, etc.. running under the .NET Framework, however I want to use this under pre .NET development environments. For SimonS he would like to use VB6 specifically. Enter the COM Callable Wrapper (CCW) to create a proxy that will provide access to our functions through interface pointers. This can be accomplished through the use of those fun little attribute tags (I keep finding them more and more useful everyday) and with an interface of course.

To begin, you will need to include the System.Runtime.InteropServices; namespace. The first thing we will do is create an interface with the same name as the class prefixed with an _ (underscore). Within this interface we will need to include all functions we want to "export" from within our .NET assembly. It is important that we apply the InterfaceType attribute to our interface we declare; this will expose our interface to COM. Next, above our class declaration we will include a ClassInterface attribute which exposes all public methods, properties, fields, and events from a .NET class. Previously I was using AutoDual, however Heath Stewart[^] pointed out this was not the best method to use as it can create version-related problems in the long run. After reading a little more I changed the code to use ClassInterfaceType.None which forces our class to gain access only through our interface. This keeps everything viable during changes to the class in the future. The only other item to note is to go ahead and inherit your interface you defined above in your new class.

C# Source Code

using System;
using System.Runtime.InteropServices;

namespace Tester
{
    [Guid("D6F88E95-8A27-4ae6-B6DE-0542A0FC7039")]
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface _Numbers
    {
        [DispId(1)]
        int GetDay();
        
        [DispId(2)]
        int GetMonth();

        [DispId(3)]
        int GetYear();

        [DispId(4)]
        int DayOfYear();
    }

    [Guid("13FE32AD-4BF8-495f-AB4D-6C61BD463EA4")]
    [ClassInterface(ClassInterfaceType.None)]
    [ProgId("Tester.Numbers")]
    public class Numbers : _Numbers
    {
        public Numbers(){}
        
        public int GetDay()
        {
            return(DateTime.Today.Day);
        }

        public int GetMonth()
        {
            return(DateTime.Today.Month);
        }

        public int GetYear()
        {
            return(DateTime.Today.Year);
        }

        public int DayOfYear()
        {
            return(DateTime.Now.DayOfYear);
        }
    }
}

VB.NET Source Code

Imports System
Imports System.Runtime.InteropServices

Namespace Tester

    <Guid("89439AD1-756F-4f9c-BFB4-18236F63251E"), _
    InterfaceType(ComInterfaceType.InterfaceIsIDispatch)> _
   Public Interface _Tester
        <DispId(1)> Function GetMonth() As Integer
        <DispId(2)> Function GetDay() As Integer
        <DispId(3)> Function GetYear() As Integer
        <DispId(4)> Function DayOfYear() As Integer
    End Interface

    <Guid("1376DE24-CC2D-46cb-8BF0-887A9CAF3014"), _
     ClassInterface(ClassInterfaceType.None), _
     ProgId("Tester.Numbers")> Public Class Tester
        Implements _Tester

        Public Tester()

        Public Function GetMonth() As Integer Implements _Tester.GetMonth
            GetMonth = DateTime.Now.Month
        End Function

        Public Function GetDay() As Integer Implements _Tester.GetDay
            GetDay = DateTime.Now.Day
        End Function

        Public Function GetYear() As Integer Implements _Tester.GetYear
            GetYear = DateTime.Now.Year
        End Function

        Public Function DayOfYear() As Integer Implements _Tester.DayOfYear
            DayOfYear = DateTime.Now.DayOfYear
        End Function

    End Class

End Namespace

More on Attributes - Automation

You may be wondering what all those different attributes are being used for. The short answer is simply Automation, more exactly an Automation Server. Visual Basic is an Automation client, which simply means it consumes COM components that expose their functionality through an IDispatch interface. Visual Basic can't do vtable lookup's to get the address of an interface pointer so IDispatch steps in to help. IDispatch identifies several important methods, the two we will discuss here are; GetIDOfNames and Invoke. As you may have guessed, GetIDOfNames returns the DispId defined within our interface which can then be passed to the Invoke method along with any parameters of the "calling" function from the client. With the use of attributes we can define all of our DispId's and even the ProgId for our class. This methodology is what allows certain scripting clients such as VBScript to talk to COM components.

Before Compiling

Property Page

Doing It By Hand Or Not...

You may find it useful to set the Register for COM interop option to True. This will create a type library and make the correct registry entries for you. You don't have to do this, and in fact you can use the Assembly Registration Tool (Regasm.exe)[^] that comes with the .NET SDK, however it is much simplier to just check it in the property window. One caveat, if you are doing this by hand, without the IDE to register for COM Interop, prior to running the reasm.exe tool to create a type library you will need to use the Strong Name tool (sn.exe)[^] to sign your assembly and thus allowing the assembly to be placed in the Global Assembly Cache (GAC)[^]. The following screen shot shows the creation of a strong named key file via the command line.

The choice to install the assembly to another location other than the GAC is ultimately up to you. If you wish to place your assembly in another location other than the GAC you should include the /codebase flag when using regasm.exe on the command line. Either location that you choose, be it the GAC or your own directory with the /codebase flag will require you to have a strong-named assembly. Once you have created this file simply edit your AssemblyInfo.cs file and change the AssemblyKeyFile property to reflect the new strong name key file name. In case you decide to just use the .NET SDK, the following command line should work fine, creating your type library and making the registery additions.

regasm Tester.dll /tlb:Tester.tlb

To copy your assembly over to the GAC you can use the Global Assembly Cache Tool (Gacutil.exe)[^] with the following command:

gacutil /i tester.dll

Here Comes VB

Open VB, create a new Standard EXE, select Project ---> References and your class should be listed. Place a check mark on your class and add a button to your form. Click on the button and add the following "code".

Private Sub Command1_Click()

    Dim i As Tester.Numbers
    Set i = New Tester.Numbers

    MsgBox "The date is: " & i.GetMonth & "/" & i.GetDay & "/" & i.GetYear

End Sub

Throw in a little MFC too

Ok, I know most everyone here, or at least a lot of you use MFC on a regular basis. Here we can see the implementation in MFC is rather simple. I removed the code for the about box on initialization just to shorten it up. You will need to include the #import "Tester.tlb" statement in your header file as well as using namespace Tester; assuming you want scope for everything in the example.

BOOL CNickDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    CString strMessage;
    Tester::_Numbers *com_ptr;
    CoInitialize(NULL);
    Tester::_NumbersPtr p(__uuidof(Tester::Numbers));
    com_ptr = p;

    int m_iDay = com_ptr->GetDay();
    int m_iMonth = com_ptr->GetMonth();
    int m_iYear = com_ptr->GetYear();

    strMessage.Format("Today is: %d/%d/%d", m_iMonth, m_iDay, m_iYear);
    MessageBox(strMessage, "Today's Date", MB_ICONASTERISK | 
                   MB_ICONINFORMATION);
    
    SetIcon(m_hIcon, TRUE);            
    SetIcon(m_hIcon, FALSE);        
        
    return TRUE; 
}

Final Thoughts

There have been many questions posted regarding the concept that is being applied in this article. Again, the purpose of creating a COM Callable Wrapper (CCW) is strictly to provide a bridging mechanism between .NET and COM. The .NET Framework is still required to be installed on the client machine or server to work. We are identifying an interface in .NET that is exposed as an IDispatch interface in turn to COM (via our attributes). IDispatch interfaces are what Automation clients such as VB use to enable COM support. So with just a few attributes applied in the correct location we are able to quickly create an assembly that exposes it methods to COM enabled non .NET applications. Even though COM and the CLR work under different architectural structures they still work well when integrated. Hope this is of some help to someone somewhere thinking about things like this. If anyone else has any feedback I am open, just post a thread below.

Update History

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralWhy does VB .NET change my typelib GUID?
R.D.H.
6:26 4 Feb '10  
I have found that the IDE registers the typelib that is generated. However the IDE seems to change the GUID (I have been unable to predict just when it will do so). How can I prevent that? My AssmemblyInfo.vb file has the ComVisibleAttribute set to TRUE but I see no GUID for my typelib. Can I specify one? Do I do that via the "GUID" entry I see on the project setting/application/assembly information dialog?

Also, when the assembly is registered, there is no "TypeLib" subkey under the CLSID of the object that can be used to cross-reference the type library. So I have to add that entry myself. This is important to me because I need to call LoadTypeLib from my COM application and so far I have been unable to embed the .tlb file that the IDE generates into the VB .Net assembly that contains my object.

I have been able to create a .rct file and add the .tlb file as a custom resource of type "TypeLib" and then use vbc /win32Resource:myresource.res. If I open the DLL in the IDE, I see the win32 resource and my typelib appears to be there. But neither OleView nor LoadTypeLib can open the DLL and "see" the typelib. Hence I am reading the TypeLib subkey to get the GUID and then reading the entry from HKCR\Typelib to find the path to the .tlb file so I can open it using LoadTypeLib.

But for some reason after coding, cleaning and/or rebuilding, the GUID of the typelib is removed from HKCR\TYPELIB and a new entry with a different GUID is added.

So how do I force the IDE to create a "stable" GUID for my typelib?

By the way, I generated the .res file by simply doing a "save" of the .rct file after I added the .tlb file to it. I also added the .res file as an existing item to the project hoping that VB .NET would embed the data into the DLL without me having to run the compiler from a command line.

If anyone knows how to go about embedding the typelib into the assembly directly so that OleView and LoadTypeLib can load the typelib, I would be most appreciative. Cool
GeneralRe: Why does VB .NET change my typelib GUID?
R.D.H.
10:58 4 Feb '10  
I found that the application GUID is used for the typelib GUID. Now I have to find out how to generate the registration data without building from the IDE. And for some reason now that I have added the GUID, when I build I get error MSB3216 telling me my assembly could not be registered - access denied. Make sure I am running as an admin (I am). Foolish me, I also added a key and signed the assembly. So one of the two has all of a sudden caused this failure. Note to self: Call Bill and ask him if Microsoft couldn't have made this a bit more difficult.
QuestionUnderstand the concept but not sure why you would... [modified]
Ramjet1
6:01 23 Jan '10  
Hello,

I understand the article, I understand the concept, but I don't understand quite why you are doing a couple of things. For example if we are exposing an interface, and that interface has defined the methods etc. that I want available then why would I expose the class so that the public methods are available? I already did that in the interface...didn't I??

Here is how I understand it so maybee someone can set me straight.



[Guid("D6F88E95-8A27-4ae6-B6DE-0542A0FC7039")] // The GUID is one we generate and maintain so it isn't constantly changing
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] // Declaring an interface that is only available to COM as late binding
public interface _Numbers // Actuall interface declaration
{
[DispId(1)] //Used for scripting clients to get info to find what's supported and thus invoke correctly
int GetDay(); //Method GetDay which returns an int

...

} //end interface

[Guid("13FE32AD-4BF8-495f-AB4D-6C61BD463EA4")] //Now why are we doing this? Defining a GUID for the class but why?
[ClassInterface(ClassInterfaceType.None)] //This so a COM interface won't autogenerate but I thought default was to not auto generate?
[ProgId("Tester.Numbers")] //This is what will be in registry tied to GUID (WHICH ONE???) which will in turn give path to dll....????
public class Numbers : _Numbers //Here we inherit the interface so _Numbers is base and this class inherits from that but WHY??? Simply so we can line up our interface methods / props etc. to the underlying actuall code that will execute.
{....



Interestingly as a follow up both Guid make it into the registry but the interface does not seem to point to the class Guid? While it works exactly as the author describes and demonstrated I am bugged but not understanding end to end every little step.

modified on Saturday, January 23, 2010 11:49 AM

AnswerRe: Understand the concept but not sure why you would...
Nick Parker
6:42 23 Jan '10  
@RamJet1,

The ProgId is for the purpose of late-bound client initialization, an example would be a call to CreateObject("Tester.Numbers") in VBScript. The concrete or coclass has to implement the interface (i.e., _Numbers in the example) because your typical client code only interacts with the interface, see the MFC code example for reference. HTH.

- Nick Parker
Microsoft MVP - Visual C# My Blog | My Articles

GeneralRe: Understand the concept but not sure why you would...
Ramjet1
5:46 24 Jan '10  
Understood so as I did my detective work I found that BOTH GUID's were in the registry. Is there a "tie" between them I am missing? Somehow there has to be a way for the system to know the GUID for the interface connects to the GUID for the derived class or vice versa? I mean it wokrs beautifully so if you don't have the time I'm in no pinch just trying to understand why two exposed GUID's are needed.
GeneralRe: Understand the concept but not sure why you would...
Nick Parker
6:51 24 Jan '10  
Both GUID's are used, the GUID on the interface is typically referred to as the IID and the coclass GUID is the CLSID. Notice the lookup in the MFC example with the usage of __uuid(Tester::Numbers), however he is assigned to Tester::_Numbers *com_ptr, the interface.

- Nick Parker
Microsoft MVP - Visual C# My Blog | My Articles

QuestionExposing .NET Components to COM on smart device
Manshy732
1:24 16 Nov '09  
Hi Everybody,

I new with .Net interop and I am facing a problem and i need help it is urgent.
I want to use .Net libraries in un managed code, I have followed this article
Exposing .NET Components to COM

and it really works.
The problem happens when i have began to do the same steps with MFC smart device application, it fails and gives my run time error "COM error". I made some searches and i have discovered that i need to register the .dll as COM class first before I can use it, but i don't know how to do so.
Can any one help me.

Thanks in advance.
GeneralProblems testing in another PC
Member 4505443
5:51 15 Sep '09  
The example works great in my PC, if i understand is because VStudio created all the registry entries for me, but when i try this in another PC with only the framework it doesnt work, i tried, regasm tested.dl, still doesnt work,
Any idea what im missing
Thanks
Generalhaving problems with this example
nologo
1:12 2 Jun '09  
Hi, i have downloaded source and it works great! i've setup debugging to launch excel.exe and open up a excel work sheet and got the macro working etc.
the problem occurs when i create my own version of the code ...again setting excel for debugging so it launchs directly into excel..but now i can't set the reference from Excel.
but following the exact same method using Tester..i can see the Tester as a reference for you're source working.
what am i doing wrong?
GeneralVery helpful, but the code does not match the article
Cameron Tully-Smith
16:09 23 May '09  
Very helpful. The code does not seem to match the article; the article's code is much more useful than the .zip, at least for VB .Net.
GeneralVery Usefull
Joao Tito Livio
9:21 7 Feb '09  
Thanks Nick, you made my day. ***** Stars

Regards
Joao
QuestionHow can component be used in VC++?
rhaak03
7:22 14 Jan '09  
I am attempting to use the .NET component you have described in VC++, but am getting the following error:
error C2653: '_Numbers' : is not a class or namespace name
(and the errors go on from there)

I am using the following statement in my .h file:
#import "path\Tester.tlb" named_guids implementation_only no_namespace

Do you have any advice for using the .NET component in this environment? Thanks in advance!!!
GeneralHow do you pass an ArrayList or other type of collection from a managed COM object?
steven rosenberg
3:57 16 Dec '08  
Can someone explain how to return an ArrayList or other type of collection from a managed COM object to an unmanged COM client in C/C++
QuestionGenerating empty implementation for COM in C# class using the VS 2005 sometimes returns CoClass or the Interface
jaygaba
22:13 19 Nov '08  
Hi Nick

I have few COM interfaces defined in a IDL files. For e.g. Interface1,Interface2 in IDLFile1 and Interface3,Interface4 in IDLFile2. Few methods in Interface1 have signatures with returns type as pointer to other interfaces.

I created the typelib and added it as a reference in a C# COM Class Library project. I created a new C# class which implements the Interface1. for e.g. Class1 : SampleCOMLib.Interface1. On right clicking on the interface I get the option implement interface1. On doing so Visual Studio IDE automatically generates the blank implementation, along with parameter datatype conversion, for the methods defined in the COM Interface1.

But I have noticed the return type of the methods are not consistent i.e. in some cases methods continue to have return type as Interfaces and in some cases return type consists of the CoClass which implements the interface.

Why is the behaviour different for the methods? How do I make sure that methods return only the interface and not the CoClass?

Thank you in advance

Jay
General.Net gui components in MFC
manchukuo
9:06 18 Nov '08  
Hi!

Great article.

How can i use .NET GUI components in MFC with COM method??
GeneralCOM - .NET Query
jaygaba
3:49 15 Oct '08  
Hi All

We have to develop a plug-in for a COM client. There are certain interfaces defined in an IDL file that needs to be implemented by our plug-in. We are planning to develop the plug-in in C#.

Is it possible for C# Class to implement the COM interface defined in the IDL and for a VC++ COM client to use the C# Class?

For e.g.

IDL file has an interface "IAdd" which has a method "add". This is implemented by a C# class "AddClass.cs", which is defined within a COM Library. This "AddClass.cs" when compiled gives a DLL which is to be used by the VC++ COM Client.

The steps I have performed so far:

1. Compiled the IDL file to generate a tlb file.
2. Imported the tlb file as a reference to the C# project
3. Implemented the COM interface
4. Created and registered the COM dll for the C#
5. Trying to access the C# COM class from VC++ Client

(i.e.: IDL -> tlb -> C# -> C# dll -> tlb -> VC++ Client)

But I keep running into problem: "Class not registered"

Any idea?

Thank you in advance

Regards
GeneralRe: COM - .NET Query
Nick Parker
15:27 15 Oct '08  
Jay,

You need to make sure you have either registered your assembly in the GAC using gacutil or set the codebase flag with the path to find the assembly.

- Nick Parker
Microsoft MVP - Visual C# My Blog | My Articles

GeneralRe: COM - .NET Query
jaygaba
3:39 16 Oct '08  
Thank you Nick. I used the /codebase switch and its working now

Thank you once again
General.NET 1.1\2.0 GUI User Components at VB6
Koltz
23:40 10 Sep '08  
Hi All!

Is it possible to use .net gui components at vb6?
How to add these components at vb6 toolbox or how to add gui component to form and show it?
GeneralRe: .NET 1.1\2.0 GUI User Components at VB6
Koltz
8:25 11 Sep '08  
Find answer here =) :

http://www.codeproject.com/KB/vb-interop/UsingDotNETControlsInVB6.aspx

Dim obj As VBControlExtender
Set obj = Controls.Add("ToVB6.UserControl1", "UserControl1", Me)
GeneralHow to expose C# struct to VBA
Mehul.Shah
3:56 6 Jun '08  
I have simple C# struct like

Public struct MyStruct
{
public const string myStr = "SampleString";
}

But when I generate tlb I do not get the struct member exposed to COM.

Can any one tell me what I am missing?

I need it ASAP.

Thanks
Mehul
GeneralRe: How to expose C# struct to VBA
ARehman
7:57 15 Jul '09  
Hello,

For me any API which takes struct as a pram is not exposed to COM.

were you able to find the solution for your issue?

Thanks
Atiq
GeneralC# in VS2005
Member 4666922
17:17 1 May '08  
Make sure add ComVisible(true), if use VS2005. Otherwise, there will be a warning and no .TLB output.

[Guid("B2D69112-C322-4c8b-AF9C-E194DC497687")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[ComVisible(true)]
...
GeneralThird party dll's
Member 2059004
6:42 29 Apr '08  
Hi

I’m experiencing problems calling a third party dll from within my COM component. Short (as short as possible) scenario: I’ve made a Business and DataLayer using NHibernate and Castle.Windsor (both layers are NOT comvisible). To call these layers from a vbscript or ASP I’ve made a COM object wrapping the methods offered by the business layer (BL). First I experienced problems with configuration. The contructor for the main BL class instantiates a WindsorContainer (for Dependency injection) which looks in the configuration file for a custom configuration section. Because wscript.exe is the actual application calling the COM dll .NET started looking for Wscript.exe.config. I’ve managed to override this setting, with this line AppDomain.CurrentDomain.SetData(”APP_CONFIG_FILE”, “ZebraZone.Toolset.Service.dll.config”);. But now, when I try to create the third party objects (like the WindsorContainer) .NET cannot find the correct dll (though it resides in the same dir as the COM dll). Putting the third party dll in c:\WINDOWS\System32\ (same dir as wscript.exe) solves this problem. But that is not a workable solution. I want to tell .NET to go looking for referenced dll in a folder of my choice, how do I do that? I’ve already tried stuff like this: AppDomain.CurrentDomain.SetData(”APPBASE”, “c:\\Visual Studio Projects\\ZebraZone\\COMTest\\COMTest\\bin\\Debug\\”); or replacing APPBASE by RELPATH, PRIVATE_BINPATH, CACHE_BASE, DYNAMIC_BASE, SHADOW_COPY_DIRS. Nothing helps, I’m getting desperate…
Grtz
Stif
QuestionRe: Third party dll's
theCPkid
3:49 8 Jul '08  
hey!! I am facing the exactly same problem. Did you find any solution for it?
Thanks.

the fruits of your success will be in direct ratio to the honesty and sincerity of your own efforts in keeping your own records, doing your own thinking and, reaching your own conclusions.
..surviving in autumn..in love with spring..


Last Updated 29 Sep 2004 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010