ClassInterfaceType.None is my recommended option over AutoDispatch & AutoDual
Advanced to intermediate COM programmers are well aware of the problems with dual interfaces. I won’t discuss that here, but people should read Don Box’s book Essential COM if they want an excellent explanation of this. There is a good discussion of avoiding ClassInterfaceType.AutoDual at:
https://msdn2.microsoft.com/en-us/library/ms182205(VS.80).aspx
I will go further and recommend that people don’t use the surface area of classes to ever generate interfaces (ie. always use ClassInterfaceType.None and never use ClassInterfaceType.AutoDispatch or ClassInterfaceType.AutoDual). COM is an interface based model. Interface design is very important and should always be split from the implementation.
I feel like the best way to define COM interfaces is in the language that was designed specifically for that task: IDL. I would recommend that people define their interfaces (even interfaces implemented in managed) in IDL, feed those interfaces through MIDL to generate a type library, import the interface into a managed Interop assembly with tlbimp.exe, and then implement that interface in their managed class to be exposed as a COM object. I realize that all of this sounds a little vague so I’ll go through a small example.
My sample interface is defined in a simple IDL file classinterface.idl:
import "oaidl.idl";
import "ocidl.idl";
[
uuid(b8728725-d116-46ad-a1fb-83348f341bf7),
version(1.0),
helpstring("classinterface")
]
library classinterface
{
importlib("stdole2.tlb");
[
uuid(46bcde68-5ffd-4423-81ce-09c3798af91d),
version(1.0),
oleautomation
]
interface IBlogDemo : IUnknown
{
HRESULT Add([in] long n1, [in] long n2, [out, retval] long * pResult);
}
};
I compile this into a type library with:
midl classinterface.idl /tlb .\bin\classinterface.tlb
I import the type library into an Interop assembly with:
tlbimp .\bin\classinterface.tlb /out:.\bin\interop.classinterface.dll
I create a small managed library in C# like so (main.cs):
using interop.classinterface;
using System;
using System.Runtime.InteropServices;
namespace main
{
[ComVisible(true)]
[Guid("b0541354-afb9-46bc-8273-5b276fa3812e")]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IBlogDemo))]
public class BlogDemo : IBlogDemo
{
int IBlogDemo.Add(int n1, int n2)
{
return n1 + n2;
}
}
}
And I build this with:
csc /t:library /out:.\bin\main.dll /reference:.\bin\interop.classinterface.dll main.cs
The two main points in the C# file is that I say not to generate an interface based on the class with ClassInterface(ClassInterfaceType.None) and then I specify the class to use for the default interface using ComDefaultInterface.
There are several advantages to this approach. The main thing is that the user is conscious of interface design. IDL is better suited to defining interfaces and makes choices about the type of interface being created and how it is marshaled explicit. Using MIDL also means that the C++ programmer will get a real header file to consume instead of using tlbexp to output a type library from an assembly and then using nasty compiler extensions to import the type library into an auto-generated header.
A big win is that interfaces are immutable and it easier to uphold that principle with a clean split of the interface from the implementation. Auto-generated interfaces based on classes have a tendency to violate this and not version well. There are also features of the CLR (generics being a prime example) that are not supported through Interop. Using one of these features and then exposing an auto-generated class interface to COM will cause a runtime error. Many people got burned by this when they upgraded to the .NET Framework version 2.0 and one of their parent classes now inherited from a generic class which caused them to have strange runtime errors.
COM Interop is a complicated topic and there are unfortunately many scenarios where there are different ways to do exactly the same thing. When there are multiple approaches, I try to lay down a best practice that will have the highest odds of leading the programmer down the path to success. I believe that defining all interfaces in IDL is a great best practice that will result in the best possible results. I feel like COM was very successful because it split interface from implementation. We should continue that practice when doing COM Interop from managed code.
Comments
- Anonymous
May 30, 2007
Hi Mason, This is great stuff! How would you deal with a special case like COM+ and ServicedComponents? Object pooling doesn't seem to work right with ClassInterfaceType.None, and you also have to inherit from Servicedcomponent. I have seen problems with not being able to call dispose on that servicedcomponent due to the way the classinterface is set, so is there a good way to still be able to use None in that scenario?