Click here to Skip to main content
15,393,955 members
Articles / Desktop Programming / MFC
Posted 14 Jan 2003


285 bookmarked

Exposing .NET Components to COM

Rate me:
Please Sign up or sign in to vote.
4.86/5 (121 votes)
29 Sep 2004CPOL6 min read
A method of calling .NET functions from a COM enabled non .NET environment through a COM callable wrapper

Table of Contents


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
    public interface _Numbers
        int GetDay();
        int GetMonth();

        int GetYear();

        int DayOfYear();

    public class Numbers : _Numbers
        public Numbers(){}
        public int GetDay()

        public int GetMonth()

        public int GetYear()

        public int 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 simpler 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.

Image 2

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 registry additions.

regasm Tester.dll /tlb:Tester.tlb

Image 3

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()

    CString strMessage;
    Tester::_Numbers *com_ptr;
    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 | 
    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 its 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

  • 1/15/2003 - Initial release
  • 1/16/2003 - Switched ClassInterfaceType from AutoDual to None to correct versioning problems
  • 1/16/2003 - Added VB.NET Source by request
  • 1/18/2003 - Added MFC example for additional fun
  • 2/10/2003 - Included more information on the process
  • 9/15/2003 - Included more information about manual registration without the IDE
  • 10/28/2003 - Included a more overall detailed explanation of what happens in the Final Thoughts section.
  • 11/26/2003 - Update included coverage of a ProgId, Guids, and DispIds


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Nick Parker
Software Developer (Senior)
United States United States
Nick graduated from Iowa State University with a B.S. in Management Information System and a minor in Computer Science. Nick works for Zetetic.

Nick has also been involved with the Iowa .NET User Group since it's inception, in particular giving presentations over various .NET topics. Nick was awarded the Visual C# MVP award from Microsoft for four years in a row.

In his mystical spare time he is working on a development project called "DeveloperNotes" which integrates into Visual Studio .NET allowing developers easy access to common code pieces. He is also a fan of using dynamically typed languages to perform unit testing, not to mention how he loves to talk about himself in the third person.

Comments and Discussions

GeneralMy vote of 5 Pin
werner.keilholz8-Oct-10 4:29
Memberwerner.keilholz8-Oct-10 4:29 
GeneralEvent Problem - CallByName Function pointer Pin
yincekara15-Apr-10 5:00
Memberyincekara15-Apr-10 5:00 
QuestionVB: Which class to name what? Pin
Tom Ruby23-Mar-10 8:37
MemberTom Ruby23-Mar-10 8:37 
GeneralPublic Shared Vs Public Pin
Musa Biralo11-Mar-10 9:12
MemberMusa Biralo11-Mar-10 9:12 
QuestionReturning Interfaces to Other Objects Pin
pionium11-Feb-10 1:30
Memberpionium11-Feb-10 1:30 
QuestionWhy does VB .NET change my typelib GUID? Pin
R.D.H.4-Feb-10 5:26
MemberR.D.H.4-Feb-10 5:26 
AnswerRe: Why does VB .NET change my typelib GUID? Pin
R.D.H.4-Feb-10 9:58
MemberR.D.H.4-Feb-10 9:58 
QuestionUnderstand the concept but not sure why you would... [modified] Pin
Ramjet123-Jan-10 5:01
MemberRamjet123-Jan-10 5:01 

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.

<br />
<br />
[Guid("D6F88E95-8A27-4ae6-B6DE-0542A0FC7039")] // The GUID is one we generate and maintain so it isn't constantly changing<br />
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] // Declaring an interface that is only available to COM as late binding<br />
public interface _Numbers // Actuall interface declaration<br />
{<br />
   [DispId(1)]  //Used for scripting clients to get info to find what's supported and thus invoke correctly<br />
   int GetDay();  //Method GetDay which returns an int<br />
   <br />
   ...<br />
<br />
} //end interface<br />
<br />
[Guid("13FE32AD-4BF8-495f-AB4D-6C61BD463EA4")] //Now why are we doing this? Defining a GUID for the class but why?<br />
[ClassInterface(ClassInterfaceType.None)] //This so a COM interface won't autogenerate but I thought default was to not auto generate?<br />
[ProgId("Tester.Numbers")] //This is what will be in registry tied to GUID (WHICH ONE???) which will in turn give path to dll....????<br />
    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.<br />
    {....<br />
<br />

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... Pin
Nick Parker23-Jan-10 5:42
protectorNick Parker23-Jan-10 5:42 
GeneralRe: Understand the concept but not sure why you would... Pin
Ramjet124-Jan-10 4:46
MemberRamjet124-Jan-10 4:46 
GeneralRe: Understand the concept but not sure why you would... Pin
Nick Parker24-Jan-10 5:51
protectorNick Parker24-Jan-10 5:51 
QuestionExposing .NET Components to COM on smart device Pin
Manshy73216-Nov-09 0:24
MemberManshy73216-Nov-09 0:24 
GeneralProblems testing in another PC Pin
Member 450544315-Sep-09 4:51
MemberMember 450544315-Sep-09 4:51 
Generalhaving problems with this example Pin
zell712-Jun-09 0:12
Memberzell712-Jun-09 0:12 
GeneralVery helpful, but the code does not match the article Pin
Cameron Tully-Smith23-May-09 15:09
MemberCameron Tully-Smith23-May-09 15:09 
GeneralVery Usefull Pin
Joao Tito Livio7-Feb-09 8:21
MemberJoao Tito Livio7-Feb-09 8:21 
QuestionHow can component be used in VC++? Pin
rhaak0314-Jan-09 6:22
Memberrhaak0314-Jan-09 6:22 
QuestionHow do you pass an ArrayList or other type of collection from a managed COM object? Pin
steven rosenberg16-Dec-08 2:57
Membersteven rosenberg16-Dec-08 2:57 
QuestionGenerating empty implementation for COM in C# class using the VS 2005 sometimes returns CoClass or the Interface Pin
jaygaba19-Nov-08 21:13
Memberjaygaba19-Nov-08 21:13 
General.Net gui components in MFC Pin
manchukuo18-Nov-08 8:06
Membermanchukuo18-Nov-08 8:06 
GeneralCOM - .NET Query Pin
jaygaba15-Oct-08 2:49
Memberjaygaba15-Oct-08 2:49 
GeneralRe: COM - .NET Query Pin
Nick Parker15-Oct-08 14:27
protectorNick Parker15-Oct-08 14:27 
GeneralRe: COM - .NET Query Pin
jaygaba16-Oct-08 2:39
Memberjaygaba16-Oct-08 2:39 
General.NET 1.1\2.0 GUI User Components at VB6 Pin
Koltz10-Sep-08 22:40
MemberKoltz10-Sep-08 22:40 
GeneralRe: .NET 1.1\2.0 GUI User Components at VB6 Pin
Koltz11-Sep-08 7:25
MemberKoltz11-Sep-08 7:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.