Sunday 4 September 2005

What does the (VB.NET) ComClass attribute do?

A former colleague emailed me this week to ask about registering C# assemblies for COM Interop. He was convinced that you needed to do something special to advertise a COM object from C#, since we’d used the ComClass attribute in VB.NET. Also, Rockford Lhotka wrote (in July 2004):

“There are several COM interop features in VB that require much more work in C#. VB has the ComClass attribute and the CreateObject method for instance.”

I was able to assure him that for his scenario – late-bound use of an object – all you need to do is use regasm to register the assembly. That registers every public class in the assembly, except any that are marked [ComVisible(false)]. (A quick aside here – the registration time is longer the more public classes you have, so you should only make the classes you actually want to expose to COM public.)

But if regasm registers all public classes in an assembly, what is the ComClass attribute for? The documentation suggests it’s required but as we’ve just seen it isn’t, at least not for this scenario.

It’s a signal to the VB.NET compiler to do three things:

  • Emit an interface definition automatically generated from the methods and properties in the class – a so-called Class Interface.
  • Add this interface to the Implements list for the class (so the class appears to implement this interface).
  • Emit a <ClassInterface(ClassInterfaceType.None)> attribute to suppress auto-generation of a class interface.

That last one is interesting. The .NET Framework supports automatic generation of class interfaces at registration time using the ClassInterfaceAttribute attribute. That generated interface is either a dispatch-only or a dual interface depending on the type selected. So what’s the difference?

Let’s take a simple test:

Imports System.Runtime.InteropServices
<ComClass()> _
Public Class WithComClassAttribute
    Sub Test()
    End Sub
End Class
Public Class WithNoAttribute
    Sub Test()
    End Sub
End Class
<ClassInterface(ClassInterfaceType.AutoDual)> _
Public Class WithClassInterfaceAttribute
    Sub Test()
    End Sub
End Class

First, let’s look at the generated code with the help of Reflector (in C# mode):

namespace VbComTlbTest
{
      [ClassInterface(2)]
      public class WithClassInterfaceAttribute
      {
            // Methods
            public WithClassInterfaceAttribute();
            public void Test();
      }
      [ClassInterface(0), ComClass]
      public class WithComClassAttribute : _WithComClassAttribute
      {
            // Methods
            public WithComClassAttribute();
            public void Test();
            // Nested Types
            public interface _WithComClassAttribute
            {
                  // Methods
                  [DispId(1)]
                  void Test();
            }
      }
      public class WithNoAttribute
      {
            // Methods
            public WithNoAttribute();
            public void Test();
      }
}

Notice that VB has generated the automatic class interface as a nested type, inside the class it belongs to.

To see the difference from a COM perspective, we need to look at the type library. Export it using tlbexp, then use OLE View from the Platform SDK (also supplied with VS.NET) to load the type library (File/View TypeLib).

I’m not going to post the entire type library IDL shown by OLE View, but note that not setting an attribute generates an empty class interface definition (equivalent to setting ClassInterfaceType.AutoDispatch), and that setting AutoDual generates a class interface containing all inherited methods, including ToString, Equals, GetHashCode and GetType from System.Object.

So to duplicate VB.NET’s behaviour with the ComClass attribute in C# – if you need to, bearing in mind that this really only applies to early-binding scenarios – you need to define an interface then implement that interface, and use ClassInterfaceType.None. This (declaring interfaces) is, of course, what you have to do in traditional C++.