Create a C# COM and transfer event

1.Some .net tools overview.
    At first, let's do the overview about the related tools.
    <1>. tlbexp.exe, Export a type libary file from a dll.
        tlbexp xxx.dll /out:xxx.tlb

    <2>. regasm.exe, Register the tlb file and dll file. Just like the regsvr32.exe. This tool also can export a tlb file just like as tblexp.exe.
         The .net IDE can register it by itself. set project property->configuration property->build->Register for com interop as ture.
        regasm xxx.dll /tlb:xxx.tlb
    <3>. gacutil.exe GAc is Global Assembly Cache. This tool add you dll into GAC, just like add a path into the PATH environment variable. When CLR is run, it can found your dll early.
         You also can put the dll in the run directory. But image that if there are serval modules will use your dll, you should copy serval times. So MS recommend put it into GAC.
        gacutil -I xxx.dll            
    <4>. sn.exe, The Strong Name tool helps sign assemblies with strong names. Sn.exe provides options for key management, signature generation, and signature verification.
         If you want put a dll into GAC, you should create a snk file for the dll.
        sn -K xxx.snk    
    <5>. OLE/COM objecter viewer.
         Via this tool, view a tlb file.
    <6>. guidgen.exe    

    The detail information about these tools, you can get from MSDN.

Now, let's go the first project.
    <1>. Open the .net IDE, create a class library project, named firstcsharpcom.
    <2>. Set project property->configuration property->build->Register for com interop as ture.
    <3>. Modify the AssemblyInfo.cs file,
        [assembly: AssemblyTitle("thirdcsharpcom")]
        [assembly: AssemblyDescription("The third test c# com")]
        [assembly: AssemblyKeyFile("..//..//thirdcsharpcom.snk")].
        Keep other setting as default.
        You can use sn -K thirdcsharpcom.snk to create the snk file.
        1.When you add your com to other projects, In "add reference" dialog, you will see your com displayed as the string in AssemblyDescription.
        2.Take note of the path of keyfile.
    <4>. Define the interface for your com. Now, you can assume that these interfaces are same as the interface which you create in ATL.
            public interface IMath
                int add( int a, int b );
                int sub( int a, int b );
         We define a interface named IMath, it has two method.

        1.The Guid attribute is optional, if you don't assign a GUID to the interface, the system will assign it automaticly when register.
        But Strongly recommend that assign it by yourself. If you don't do that, every time you register your com, the GUID of the interface will be different.
        We can use guidgen to get the GUID.
        2.Please define the interface public, otherwise, it will be hiden.

    <5>. Define the class. derive from the interface.
            public class Calculator: IMath
                public Calculator()
                #region IMath Members
                public int add(int a, int b)
                    return a+b;

                public int sub(int a, int b)
                    return a-b;
        1.Assign the GUID to it by yourself.
        2.If you define the constructor with parameters, This class will be defined as noncreateable in the tlb file. You can test it by yourself. :-)

    <6>. we have a interface, a class and two methods. Let's build it.
    <7>. Open the OLE/COM viewer, and open the .tlb file, you can see the IDL.
        1. helpstring("the first test for c# com"), it's just same as the string in AssemblyDescription
        2. As default, IMath interface support automation. it derive from IDispatch.
                interface IMath : IDispatch {
                    HRESULT add(
                                [in] long a,
                                [in] long b,
                                [out, retval] long* pRetVal);
                ... ...
        3. Please pay attention to the parameters of the method, they are correct exactly.(Remember how we declared ? )
        4. The coclass definition, you can define a class whose constructor has parameters, then compare the IDL.
            custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, first.Calculator)
            coclass Calculator {
                [default] interface _Calculator;
                interface _Object;
                interface IMath;
    <8>. Now, let's put the dll into the GAC.
        gacutil -I firstcsharpcom.dll.
    <9>. Now, we can test it. I test it in VB.
        create a standard EXE project, add reference.
            Dim i As firsttestcsharpcom.IMath
            Dim o As firsttestcsharpcom.Calculator
            Set o = New firsttestcsharpcom.Calculator
            Set i = o
            MsgBox i.Add(100, 200)
            MsgBox i.Sub(1000, 1)
            Set obj = Nothing

        You also can test with script.
            Set obj = CreateObject("firstcsharpcom.Calculator")
                result = obj.Add(100, 200)
                MsgBox result
                result = obj.Sub(200, 100)
            MsgBox result
            Set obj = Nothing

Now, let's test some complex datatype

    Create a new project named secondcsharpcom, do the same configuration. And create a new snk file the assign it.
    define the interface:
    public interface typeconvert

        void testcallback( System.IntPtr ptr );

        void testintger( int i , out int o );
        void teststring( string strin ,out string strout );
        void testobject( ref classinstance objin, out classinstance classobj );

        void testarray( ref int[] array, out int[] intarray);
        void testarray( ref string[] array, out string[] stringarray);
        void testobjectarray( ref classinstance[] array, out classinstance[] objarray );

        string getstring();

    Define the class implement the interface.

    public class testTypeConvert: typeconvert
        public delegate void callback();
        public testTypeConvert()
        #region typeconvert Members
        public void testintger( int i, out int o )
            o = i ;

        public void teststring(string strin, out string str)
            // TODO:  Add testTypeConvert.teststring implementation
            str = "The input string is: " + strin;

        public void testarray(ref int[] array, out int[] intarray)
            int nsize = array.Length;
            intarray = new int[nsize];//{1,2,3};
            for( int i=0; i<nsize; i++ )
                intarray[i] = array[i]+10;

        public void testarray( ref string[] array, out string[] stringarray)
            int nsize = array.Length;
            stringarray = new string[nsize];//{"hello"," world","!"};
            for( int i=0; i<nsize; i++ )
                stringarray[i] = "The No."+i+" item is " +array[i];

        public void testobjectarray( ref classinstance[] array, out classinstance[] objarray )
            int nsize = array.Length;
            objarray = new classinstance[nsize];
            for( int i=0; i<nsize; i++ )
                objarray[i] = new classinstance();
                objarray[i].i = array[i].i+100;

        public void testobject( ref classinstance objin, out classinstance classobj )
            classobj = new classinstance();
            classobj.i = objin.i+100 ;

            int nsize = objin.strarray.Length;
            classobj.strarray = new string[nsize];//{"hello"," world","!"};
            for( int i=0; i<nsize; i++ )
                classobj.strarray[i] = "The No."+i+" item is " +objin.strarray[i];

        public string getstring()
            XmlDocument doc = new XmlDocument();
            string str = "";
                str = "<?xml version='1.0' encoding='UTF-8'?> "
                    +"<xsl:stylesheet version='1.0' "
                    +"xmlns:xsl=''> "
                doc.LoadXml( str );
                Console.WriteLine( str );
            catch( Exception e )
                Console.WriteLine( e.Message );

            return str ;

    build and see the IDL file.

    interface typeconvert : IDispatch {
        HRESULT testcallback([in] long ptr);
        HRESULT testintger(
                        [in] long i,
                        [out] long* o);
        HRESULT teststring(
                        [in] BSTR strin,
                        [out] BSTR* strout);
        HRESULT testobject(
                        [in, out] _classinstance** objin,
                        [out] _classinstance** classobj);
        HRESULT testarray(
                        [in, out] SAFEARRAY(long)* array,
                        [out] SAFEARRAY(long)* intarray);
          custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, testarray)]
        HRESULT testarray_2(
                        [in, out] SAFEARRAY(BSTR)* array,
                        [out] SAFEARRAY(BSTR)* stringarray);
        HRESULT testobjectarray(
                        [in, out] SAFEARRAY(_classinstance*)* array,
                        [out] SAFEARRAY(_classinstance*)* objarray);
        HRESULT getstring([out, retval] BSTR* pRetVal);


Now, let's give a highlight to EVENT, as you know, in C# microsoft use delegate, replace the callback function, the point of function. Its event mechanism also base on it.
    <1>.Create a new project named thirdcsharpcom, do the same configuration.
    <2>.Create the delegate:
        public delegate void delegatefirstevent( ref int[] input );
        public delegate void delegatesencondevent( ref string[] strinfo );
        public delegate void delegateformalevent( ref eventhandleinfo ei );

    <3>.    Defines an event sink interface (ButtonEvents) to be implemented by the COM sink.
        [Guid("B68D5E2C-9785-4376-8286-56DFCA2F83E1") ]
        public interface ITestEvents
            void firstevent( ref int[] param );
            void secondevent( ref string[] strinfo );
            void OnFormalEvent( ref eventhandleinfo ei );

    <4>.Connects the event sink interface to a class  by passing the namespace and event sink interface ("eventsource.ITestEvents, firstevents")).
    //[ComSourceInterfaces("eventsource.ITestEvents, firstevents")]
    [ComSourceInterfaces( typeof( ITestEvents ) )]
    public class eventhost: IDosomething
        public event delegatefirstevent firstevent; // this variable has the same name with the method in ITestEvents interface
        public event delegatesencondevent secondevent;    
        public event delegateformalevent OnFormalEvent;

        public eventhost()

        public void firefirstevent()
            if( firstevent != null )
                int[] arr = new int[3]{1,2,3};
                firstevent( ref arr );

        #region IDosomething Members

        public void helloword()
            if( secondevent != null  )
                string[] arr = new string[2]{"hello"," world"};
                secondevent( ref arr );

        public void dosomething( )
            if( OnFormalEvent != null )
                eventhandleinfo aa = new eventhandleinfo( );
                aa.Code = 0;
                aa.Info = "You do something successfully!";
                OnFormalEvent( ref aa );

    <5> define the interface who will trigger the event
        public interface IDosomething
            void helloword();
            void dosomething( );

    <6> build, put the dll into GAC and test in VB.
        When you define a eventhost object in a class module, you will see that this object has tree event.

About this subject, there are many information in the internet and my text is quite simple because of my poor English. I have to write it in English because of my OS. Just as a practice for my English.     

想对作者说点什么? 我来说一句