Guidelines for COM Interoperability from .NET
In developer forums in which I participate I often read and respond to questions asking about COM interoperability (interop) and my reply is almost always the same. There are guidelines - if not rules - for exposing .NET Framework components to COM and they are all based on guidelines for COM development. If you have experience writing COM components these guidelines should sound familiar; if not, I hope you find this information useful.
In COM, you interact with components through interfaces, which all derive from IUnknown. Each interface exposed to COM clients has a unique interface ID (IID) and the classes that implement those interfaces have unique class IDs (CLSIDs). In .NET you should attribute all COM-visible types with a unique value in the GuidAttribute attribute. You can create these using uuidgen.exe or guidgen.exe.
[Guid("231d685b-6e22-49ef-a046-74e187ed0d21")]
public interface IFoo
{
}
[Guid("54c0c685-4ef2-4a1a-8693-b5773dbed295")]
[ClassInterface(ClassInterfaceType.None)]
public class Foo : IFoo
{
}
Now the interfaces and classes you register will always have the same IDs from which they're identified. To make sure the type library ID doesn't change, you should also attribute your assembly with a unique GuidAttribute value as well.
[assembly: Guid("1D2E5B02-438F-49d3-8D33-BC3451418551")]
Also, you should never use automatically-generated class interfaces using the enumerations ClassInterfaceType.AutoDispatch or ClassInterfaceType.AutoDual. You should define your class interface as you see above for IFoo explicitly and implement them as the first interface of your class, as well as use ClassInterfaceType.None as the value for the ClassInterfaceAttribute on your class. If you use auto-generated class interfaces, your IID will most likely change (a problem for VTBL or "early" binding) and any changes to the order of your methods will change the order of functions in the VTBL, which is also a problem. The VTBL is a virtual table with function address used for COM clients to query using IUnknown::QueryInterface and to call those functions. If the functions change order, your compiled client could be calling the wrong function with the wrong parameters. This order problem may also affect automation clients that cache dispatch IDs, though you can resolve that issue by attributing each property or method with the DispIdAttribute.
For the same reason, you should never change a published interface; that is, you should never change the methods or properties of an interface once you've released it. You should create a new interface that derives from the older interface and implement that interface as your new class interface instead:
[Guid("231d685b-6e22-49ef-a046-74e187ed0d21")]
public interface IFoo
{
int Add(int a, int b);
int Subtract(int a, int b);
}
[Guid("bbf3e617-ee6a-4e5a-a6a5-1623edd3e6c2")]
public interface IFoo2 : IFoo
{
int Multiply(int a, int b);
float Divide(int a, int b);
}
[Guid("54c0c685-4ef2-4a1a-8693-b5773dbed295")]
[ClassInterface(ClassInterfaceType.None)]
public class Foo : IFoo2
{
#region IFoo2 Members
public int Multiply(int a, int b)
{
return a * b;
}
public float Divide(int a, int b)
{
return (float)a / (float)b;
}
#endregion
#region IFoo Members
public int Add(int a, int b)
{
return a + b;
}
public int Subtract(int a, int b)
{
return a - b;
}
#endregion
}
When registering your managed assembly for exposure to COM, you should use regasm.exe that ships with the .NET Framework redistributable. If you're installing the assembly into the Global Assembly Cache (GAC) - which means it must be strong named - you can run the following:
regasm.exe AssemblyName.dll
If you're not installing your assembly into the GAC, you should pass the /codebase switch to register the full path:
regasm.exe /codebase AssemblyName.dll
In both cases, you can generate a type library (typelib) using the /tlb option. This is useful for automation clients.
To summarize:
- Always attribute COM-visible classes and interface with the GuidAttribute using unique values.
- Never use auto-generated class interfaces; always explicitly define your interfaces.
- Never change a published interface implementation; derive a new interface as the new class interface or other interface to implement.
You can read more about exposing .NET Framework components to COM in Guidelines for Exposing Functionality to COM in the .NET Framework SDK, as well as Exposing .NET Framework Components to COM and Marshaling Data with COM Interop.