When exposing managed types as COM types, your classes must have CLSIDs, your interfaces must have IIDs, and so on. System.Runtime.InteropServices provides a custom attribute (GuidAttribute) that enables you to be explicit about these GUIDs. But the CLR also has a reasonable algorithm for generating GUIDs on-the-fly, so you normally don't need to be explicit about it. You can see evidence of this in type libraries you export from assemblies: Although you never marked your managed types with GUIDs, the exported type library shows that every type has one.
The goal of the GUID-generation algorithm is to change GUIDs when incompatible changes are made to managed types, but to keep them unchanged when compatible changes are made.
Here's how it works:
This gives you reasonable behavior... as long as you stop VS.NET from giving your project a wildcard version like "1.0.*", which increments the assembly version number each time you recompile. For example, adding a new method to your class won't change its CLSID, but adding a new method to your interface will change its IID (since interfaces should never change). Upgrading your assembly version number will change your class's CLSID, but won't change your interface's IID. And if you don't want any of your GUIDs to change when your assembly version number changes, you can use the ComCompatibleVersionAttribute (introduced in v1.1) to plug in a different version number as input to the GUID-generation algorithm. In v1.1, several .NET Framework assemblies marked themselves with ComCompatibleVersionAttribute to keep their auto-generated GUIDs matching what was shipped in v1.0.
Let's compare this behavior to Visual Basic 6. VB6, like managed code, provides an easy way to write COM components. (Of course, managed code is usually used with other goals in mind!) VB6 hides GUIDs from you when you write COM components, but unlike managed code:
However, VB6 does provide an important option for giving your types stable GUIDs: binary compatibility. The binary compatibility option (on the Properties->Component tab in the IDE) tells the VB6 compiler to look at the type library embedded inside the previously-compiled DLL or EXE, extract the GUIDs, and reuse those GUIDs each time you recompile. Using this option is almost essential when it comes to managed code interoperating with VB6 COM components.
A simple example that breaks without binary compatibility is the following:
If the COM class was still registered under the old CLSID, instantiating it would work, but you'd get an InvalidCastException as soon as the CLR attempts to call QueryInterface with an IID that the recompiled COM object no longer recognizes. This happens because the GUIDs from the old type library are captured in the metadata inside the Interop assembly, which is now out of date. Re-importing the type library would fix this problem, but using binary compatibility prevents you from having to do this each time you recompile.
A more subtle problem from not using binary compatibility is the following situation, which a colleague and I recently encountered out in the field. A VB.NET client was calling two methods on a VB6 COM component. The program worked fine when running standalone. But when the COM component was running inside the VB6 debugger, the first method call succeeded and the second method call threw an exception.
Why did this happen? The VB.NET code was making late-bound calls to the COM component (since it was declared "As Object") and the second method returned a UDT. In other words, a VT_RECORD VARIANT was being returned via an IDispatch::Invoke call. In order for the CLR to map the returned VT_RECORD into a managed type, the managed definition of the structure must be registered under HKEY_CLASSES_ROOT\Record\{GUID}. (Note that REGASM.EXE does this when you run it on an Interop assembly.) The program worked outside of the VB6 debugger because at some point in the past, the Interop assembly for the pre-compiled COM component was already registered. But the VB6 project was not using the binary compatibility option, so the UDT was being assigned a different GUID every time we hit F5 in the debugger! When the CLR retrieved the GUID for the structure, it found nothing in the registry. Enabling the binary compatibility option instantly fixed the issue, since VB6 started assigning the structure its GUID from the previously-compiled type library, which was already correctly registered.
So, when programming with VB6, use the binary compatibility option!
Here's a KB article that attempts to clarify VB6's three compatibility options.
And since we're talking so much about GUIDs, here's a stupid parlor trick: