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.
Join the conversationAdd Comment
This all sounds remotely familiar. 🙂 Good job covering the topic.
Hi,
I’m trying to expose a .NET class library to vb6. Now I have created the interfaces of every class with the next definitions:
<InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)> _
Public Interface MyInterface …..
and add new GuidAttributes for the classes:
GuidAttribute("521A85AD-6114-473f-934C-89492A1EDF3D")> _
Public Class MyClass Implements MyInterface ….
In "AssemblyInfo.vb" I wrote this definition to take effect for all the public classes of the class library:
<Assembly: AssemblyKeyFile("C:tempkeypair.snk")>
<Assembly: ClassInterface(ClassInterfaceType.None)>
After that, I compile the .NET solution and then I do thenext commands:
RegAsm myLibrary.dll /codebase /tlb
This command register the library and creates a type library.
GacUtil -i myLibrary.dll
This command adds the assembly to the global assembly.
Then I create a new VB6 project and then I reference my COM. I can define objects and view its methods and properties as the way they were created on the COM interface. The problem is when I try to run the project. I get the next error:
Runtime error 429: ActiveX component can’t create Object
Why is this happening?
My library reference the CAPICOM but I put the capicom.dll accesible on the exe directory.
Do you have any idea?
Thanks a lot.
Best regards,
Jesus Rodriguez
If you regasm your assembly with the /codebase switch, you need not add the assembly to the GAC. Likewise, if you add your assembly to the GAC first you don’t need to regasm your assembly with the /codebase switch.
In this case since you’re dependent on an interop assembly for the native CAPICOM COM server library I would recommend adding your assembly to the GAC as well as the CAPICOM interop assembly (and any other assemblies on which you depend that aren’t already in the GAC). This way the CLR can resolve the path to the interop assembly.
To see if this is truly the problem, you should run fuslogvw.exe from the .NET Framework SDK. Make sure logging is enabled and start your VB6 project. After the error occurs go back to fuslogvw.exe, click the Refresh button if you didn’t close it from before, and see what binding errors might occur.
If you don’t want to add your assembly to the GAC (usually best during development) make sure you regasm with the /codebase switch but the interop assembly for CAPICOM must either be in the GAC, in the VB6 application’s probing path (by default, the application directory itself), or the location specified in the application’s .config file. See "How the Runtime Locates Assemblies" at for more information.
Excellent – SIMPLE and CONCISE!!