4
votes

My problem is that I have a .NET project with like 100+ classes visible to COM. Classes are published to COM using the AutoDual attribute on the class. My problem is that I would like to let the compiler auto generate the COM Interface (hence this AutoDual) BUT I would like to be able to specify the GUID for the generated interface.

The goal is to make sure that small changes like:

  • adding a method
  • modifying a method that is not being used by a specific client
  • increasing the version number of my DLL

won't break the clients (those using early binding) and force them to recompile.

I know (read here: Why should I not use AutoDual?) that a solution for that would be to create the 100+ interfaces manually and give them a GUID and DispId for each method so the changes mentionned above would no longer break older clients. However, I would like to avoid actually writing those interfaces manually, and having to maintain them when new methods needs to be added in both the class and interface.

So far I have managed to "automatically" publish those classes to COM without writing COM specific code by using PostSharp to inject the following attributes:

On classes:

  • ComVisible(true)
  • ClassInterfaceType.AutoDual
  • GUID("A guid of my own automatically generated but invariant for a gievn class FullName")

On public methods and properties:

  • DispId(xx) // where xx is again a dispId generated but invariant for a given class name/method name couple.

Having done that :

  • the class GUID are invarriant what ever changes I make to them
  • the DispId are also invarriant on the generated interface

Only the Interface GUID poses problem as it varies every time a method is added.

What I now need would be a way to ensure the generated interface for a given class always has the same GUID, by either:

  • a specific attribute i don't know of, like 'AutoDualInterfaceGuid("My GUID") on the class
  • a pre compilation process to generate the interfaces, with a GUID of my choice
  • a post compilation process to modifiy the GUID of generated COM interfaces
  • a way to modify the default generation of COM Interfaces in order to place my GUID logic in there.

Any idea on how to set a specific GUID for a generated COM Interface would be much appreciated.

1

1 Answers

5
votes

You'd simply use the [Guid] attribute on the interface and the class:

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
[Guid("{7447EEEA-3D48-4D20-80DF-739413718794}")]
public interface IFoo {
    [DispId(42)] void method();
}

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[Guid("{40815257-BFD2-43D9-9CF8-FB27CC884C71}")]
[ProgId("Acme.Foo")]
public class Foo : IFoo {
    public void method() { /* etc */ }
}

And in the AssemblyInfo.cs file:

[assembly: Guid("B75B31AD-D96A-473F-94E0-37E59847B997")]

Which covers the DispId of the interface members, the IID of the interfaces, the CLSID of the coclass, the ProgId of the coclass and the LIBID of the type library.

However, I would like to avoid actually writing those interfaces

You can use ClassInterfaceType.AutoDispatch to avoid writing the interfaces but that achieves the exact opposite of what you asked for. You can no longer control the IID anymore, you'll expose the System.Object methods which gives the client a type library dependency on mscorlib.tlb and every change you make will break the client. Very convenient to you, extremely inconvenient to your clients. But read on:

the class GUID are invariant what ever changes I make to them

I showed you how to do this. But this is actually a very strong anti-pattern in COM. Which demands that you change the IID when you make changes. Not changing it causes extremely nasty problems at runtime when the change you make is breaking. Very easy to do, just inserting or removing a method or changing the return type or arguments of a method are enough. Okay when the client code late-binds through IDispatch, fatal when it early-binds. The client will call the completely wrong method. Or you'll get arbitrary garbage values for the arguments. The client will crash with an AccessViolationException when it's lucky, next to impossible to diagnose why. It is not lucky then the call succeeds but just completely fails to operate correctly, utterly impossible to diagnose.

Don't do it.