Udostępnij za pośrednictwem


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

  • Anonymous
    October 19, 2003
    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.
  • Anonymous
    October 20, 2003
    Yes, the digit at position 12 will always be 4. But why?
  • Anonymous
    October 20, 2003
    The comment has been removed
  • Anonymous
    December 19, 2003
    The comment has been removed
  • Anonymous
    March 21, 2004
    "background information on Microsoft's history " Where?
  • Anonymous
    April 11, 2004
    When programming with VB6, use the binary compatibility option!
  • Anonymous
    April 11, 2004
    Yeah.
  • Anonymous
    April 11, 2004
    Free books for students, teachers, and the classic enthusiast.
  • Anonymous
    May 03, 2004
    Nice blog.
  • Anonymous
    June 03, 2004
    Very interesting Site!

    I could get used to such things!

    Thanks to you guys.
  • Anonymous
    June 04, 2004
    Very interesting Site! Good Informations, well done!
  • Anonymous
    June 09, 2004
    Very interesting Site!
  • Anonymous
    June 15, 2004
    I really enjoyed visiting your site!
  • Anonymous
    June 15, 2004
    Thank you so much for the informations! Well done!
  • Anonymous
    June 15, 2004
    Nice blog.
  • Anonymous
    June 15, 2004
    Very interesting site!
  • Anonymous
    June 15, 2004
    thank you so much for the informations!
  • Anonymous
    June 16, 2004
    Thank you so much for the informations!
  • Anonymous
    June 18, 2004
    Very interesting site!
  • Anonymous
    June 18, 2004
    Thank you a lot!
  • Anonymous
    June 18, 2004
    Thank you a lot!
  • Anonymous
    June 19, 2004
    Very interesting site!
  • Anonymous
    June 19, 2004
    Thank you for the informations!
  • Anonymous
    June 19, 2004
    Cool Site!
  • Anonymous
    June 19, 2004
    you´re doing a wonderful job
  • Anonymous
    June 19, 2004
    Cool Website!
  • Anonymous
    June 19, 2004
    Thank you so much for the informations!
  • Anonymous
    June 19, 2004
    Well done, thank you!
  • Anonymous
    June 20, 2004
    Thank you so much for the informations
  • Anonymous
    June 24, 2004
    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!!
  • Anonymous
    June 24, 2004
    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!
  • Anonymous
    June 24, 2004
    Very interesting site, thank you so much!
  • Anonymous
    July 01, 2004
    Computer sind sehr wichtige Instrumente in der modernen Zeit. Sie muessen erhalten und verbessert werden.
  • Anonymous
    July 03, 2004
    Very interesting site!
  • Anonymous
    July 03, 2004
    Thank you so much for the informations!
  • Anonymous
    July 03, 2004
    you´re doing a wonderful job
  • Anonymous
    July 03, 2004
    Thank you for the informations!
  • Anonymous
    July 03, 2004
    Great Resource!
  • Anonymous
    July 03, 2004
    Cool Site!
  • Anonymous
    July 03, 2004
    I really enjoyed visiting your site!
  • Anonymous
    July 03, 2004
    Very interesting site!
  • Anonymous
    July 03, 2004
    Cool Website!
  • Anonymous
    July 03, 2004
    Very interesting site!
  • Anonymous
    July 03, 2004
    Thank you for the wonderful informations!
  • Anonymous
    July 03, 2004
    I really enjoyed visiting your site!
  • Anonymous
    July 03, 2004
    Very interesting Webpage
  • Anonymous
    July 03, 2004
    Cool Site!
  • Anonymous
    July 17, 2004
    Very interesting site!
  • Anonymous
    July 17, 2004
    I really enjoyed visiting your site
  • Anonymous
    July 17, 2004
    Cool Website!
  • Anonymous
    July 25, 2004
    The comment has been removed
  • Anonymous
    July 27, 2004
    The comment has been removed
  • Anonymous
    July 08, 2005
    <a href="http://www.versicherungsvergleicher.org" target="_blank">versicherungsvergleich</a></strong>
  • Anonymous
    July 10, 2006
    who knows