GUID Generation and VB6 Binary Compatibility

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:

  • CLSIDs (and GUIDs on stucts) are generated based on a hash of the fully-qualified class name plus the name, version, and public key of the assembly containing the class.

  • IIDs are generated based on a hash of the fully-qualified interface name plus the signatures of all the interface s members that are exposed through Interop.

  • LIBIDs are generated based on a hash of the name, version, and public key of the assembly.

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:

  • The default GUID-generation behavior is not very helpful.  Your types get brand new GUIDs each time you recompile.

  • There is no way to explicitly choose your GUIDs in source 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:

  1. Compile a VB6 COM component that does not use the binary compatibility option.

  2. Import the type library (using TLBIMP.EXE or VS.NET) to create an Interop assembly.

  3. Consume the Interop assembly in managed code.

  4. Recompile the COM component.  Re-running the managed consumer is now broken.

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:

  1. Create a fresh GUID using your favorite means of invoking the CoCreateGuid API (i.e. running GUIDGEN.EXE, UUIDGEN.EXE, calling Guid.NewGuid from managed code, etc.).

  2. Starting with the third cluster of digits and proceeding from left to right, find the first digit between 0-9 (inclusive) and multiply it by its zero-based position in the GUID (using base 10 for all math).  For example, with the GUID 55fe1e41-b221-2d9a-9e61-550737c019ed, I’d take the first digit in the third cluster (2) and multiply it by its position (12) to get the number 24.

  3. Subtract 30 from the resulting number and divide the result by 2.

  4. The number you’re left with is 9.  How did I know that?

Comments (54)

  1. Following your instructions in reverse order , we know that the number after the multiplication must be 9*2+30 = 48. The only divisor of 48 in the range [12,15] (the third cluster) is 12, so the digit at position 12 is always 4.

  2. Adam Nathan says:

    Yes, the digit at position 12 will always be 4. But why?

  3. Mike Iles says:

    The high four bits of that octet are the GUID version number. Version 4 is the "randomly or pseudo-randomly" generated version, as opposed to other GUID versions that use identifying characteristics of the local machine (i.e. MAC address) to generate a ‘spatially-unique node ID’.

    The fact that we only ever use version 4 GUIDs now is certainly due to privacy concerns, but I would be curious to read some background information on Microsoft’s history in this area. Was it always this way?

  4. Marcus Pelletier says:

    I’m looking for a place to add a question regarding COM interop and .NET components written for COM consumption specifically.

    I’m wondering about the meaning behind the ‘ThreadingModel’ value under the InprocServer32 key. In COM, this is a directive, but I’m thinking for a .NET ‘COM’ component, it is more like advice to the client on how the component will behave.

    I would like for a .NET component to be created in the MTA and I want to do this by setting the ThreadingModel value to "Free".

    The behavior is strange though. The constructor of the .NET object gets called on a thread whose ApartmentState is "MTA" but method of the object are still called on the same thread as the STA client (and MFC client) with the ApartmentState set to "STA".

    I tried posting a question like this on the .NET Framework message board but there was no response.

  5. "background information on Microsoft’s history " Where?

  6. When programming with VB6, use the binary compatibility option!

  7. Read Print says:

    Free books for students, teachers, and the classic enthusiast.

  8. Geburtstag says:

    Very interesting Site!

    I could get used to such things!

    Thanks to you guys.

  9. Grusskarten says:

    Very interesting Site! Good Informations, well done!

  10. Manga says:

    I really enjoyed visiting your site!

  11. Manga says:

    Thank you so much for the informations! Well done!

  12. tanga says:

    Very interesting site!

  13. tangas says:

    thank you so much for the informations!

  14. kochrezepte says:

    Thank you so much for the informations!

  15. bewerbung says:

    Very interesting site!

  16. bewerben says:

    Thank you a lot!

  17. tattoos says:

    Very interesting site!

  18. Thank you for the informations!

  19. you´re doing a wonderful job

  20. tatoo says:

    Thank you so much for the informations!

  21. tatto says:

    Well done, thank you!

  22. lebenslauf says:

    Thank you so much for the informations

  23. Frank Palma says:

    Thank you for this perfect holiday site. In German: Machen Sie Urlaub auf der kanarischen Insel La Palma. Hier können Sie wandern und entspannen. Gute Infos finden Sie auf meiner La Palma Site. Gönnen Sie sich Urlaub!!

  24. Nice Site: Auf dieser Seite gibt es Infos zum Ort Puerto Naos auf der kanarischen Insel La Palma. Alles was Ihr über einen Urlaub auf der Insel La Palma wissen müssen. Außerdem haben wir Fotos, ein Forum, viele Informationen über La Palma und den Ort Barlovento und Puerto Naos. Es lohnt sich bestimmt. Außerdem ist die Insel La Palma genial zum Wandern. Holt Euch die Tipps!

  25. hochzeit says:

    Very interesting site, thank you so much!

  26. Computer sind sehr wichtige Instrumente in der modernen Zeit. Sie muessen erhalten und verbessert werden.

  27. text song says:

    Very interesting site!

  28. liedertexte says:

    Thank you so much for the informations!

  29. songtext says:

    you´re doing a wonderful job

  30. lieder texte says:

    Thank you for the informations!

  31. I really enjoyed visiting your site!

  32. Very interesting site!

  33. Thank you for the wonderful informations!

  34. I really enjoyed visiting your site!

  35. traumdeutung says:

    Very interesting Webpage

  36. Very interesting site!

  37. I really enjoyed visiting your site

  38. Petervds says:

    Here’s one to that needs some tought:

    I developed a VB.NET component that does some background processen. The .NET component is called from VB6 EXE whenever required. So far all is well in interopland.

    works like a charm!

    The VB6 EXE application itself is made up out of serveral other VB6 dll’s and ocx’s. Now this is the problem : each time i recompile one of these dll’s or ocx’s (with binary compatibility ‘on’ of course) the exe can no longer call the VB.NET library, i get an object can’t create activex component. This is a bit bizar knowing that only the exe calls the VB.NET library and the exe itself was never changed and knowing that the changed vb6 dll can still be called from the exe!

    The curious thing is that if i redeploy the same components using the installer, all is well again in interop land. So something must be happening during this install that i don’t understand/know of.

    Can anyone please try this and tell what i’m doing wrong here.

  39. <a href="; target="_blank">versicherungsvergleich</a></strong>

  40. msn says:

    who knows