Search code examples
c#c++comcom-interop

How to change/set GUID for COM class inherit from EventArgs in TLB (TLH)


I've exposed some event from library written in C# to C++ library via COM-Interop. Everything works fine, but I would like to version this library in COM style. When I was trying to do this I hit a snag - I wasn't able to change (set) GUID for SampleEventArgs class in TLH file imported from TLB of this library. Below is a minimal working example.

Implementation of the required interfaces and classes in C#:

[Guid("37B8697D-BC5F-4CCB-8002-5F734B4765C8")]
[ClassInterface(ClassInterfaceType.AutoDual)]
[ComVisible(true)]
public class SampleEventArgs : EventArgs
{
   public int Count { set; get; }
}

[Guid("8F6E11DE-C892-4540-89C0-C59F628ABC20")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ISampleInterface
{
   [DispId(1)]
   void SampleMethod(object sender, SampleEventArgs e);
}

[Guid("945B64A6-B4E5-48E7-9DB0-89BF92DCFD0C")]
[ComSourceInterfaces(typeof(ISampleInterface))]
[ComVisible(true)]
public class SampleClass
{ 
   public void SampleMethod()
   {
      SampleEventArgs args = new SampleEventArgs() { Count = 0 };
      sampleEventHandler(this, args);
   }

   public event SampleEventHandler sampleEventHandler;
}

public delegate void SampleEventHandler(object sender, SampleEventArgs e);

Import TLB file to C++ project to generate TLH file:

#import "C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscorlib.tlb"
#import "com_interop_dll.tlb"

Part of the generated TLH file:

//
// Forward references and typedefs
//

struct __declspec(uuid("23ece2ab-6e98-4301-8fff-eed74151d585"))
/* LIBID */ __com_interop_dll;
struct /* coclass */ SampleEventArgs;
struct /* coclass */ SampleClass;
struct __declspec(uuid("599c264e-506e-3780-97b6-c1edff5f4a66"))
/* dual interface */ _SampleEventArgs;
struct __declspec(uuid("e82fc089-4301-3c69-bd80-4328a85b6314"))
/* dual interface */ _SampleClass;

And my question is how can I set a GUID for _SampleEventArgs in C# code? I believe, this GUID ("599c264e-506e-3780-97b6-c1edff5f4a66") is generated automatically and exists a possibility to set this value manually.


Solution

  • You can only control the [Guid] if you declare your own interface. Pretty important in COM, the client code always works with interfaces and the implementation is always hidden. The way you are doing it now, the type library exporter is forced to create a synthetic interface from the class declaration.

    Inevitably, it must also auto-generate the IID of the interface. Not otherwise a bad thing, interfaces are immutable in COM and if you make any change then you must also change the IID.

    Also note that you are exposing too much, the reason that you have to import mscorlib.tlb. That's not great. It is required because the auto-generated interface also includes the members inherited from System.Object. And EventArgs in your case. None of this is useful to the client code. So make it resemble this:

    [Guid("599c264e-506e-3780-97b6-c1edff5f4a66")]
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    [ComVisible(true)]
    public interface ISampleEventArgs {
        int Count { set; get; }
    }
    

    The client must implement this interface in order to get the event, no need to expose your implementation:

    [ComVisible(false)]
    public class SampleEventArgs : EventArgs, ISampleEventArgs
    {
       public int Count { set; get; }
    }
    

    Change ISampleInterface.SampleMethod to expose ISampleEventArgs instead. And write ISampleClass, same recipe. Give SampleClass the [ClassInterface(ClassInterfaceType.None)] attribute and you no longer have a dependency on mscorlib.tlb