Overcoming COM Versioning Problems

Most VB 6.0 developers are only too familiar with COM interface and binary compatibility issues when maintaining VB 6.0 classes. Commonly known as DLL Hell, these problems arise because developers need to change or extend COM interfaces after they've been distributed and are already in use by client applications. In the .NET world, there are mechanisms to allow you to alter component interfaces at will without dire consequences, but when you have COM Interop projects, you're back to dealing with interface versioning issues again. More specifically , when you expose a VB .NET class to a VB 6.0 application, you need to understand how to keep the client and server synchronized.

If you look at the MathClass class in the InteropDemo VB .NET project, you can see that the class is marked with the attribute <ClassInterface(ClassInterfaceType.AutoDual)> . If you try removing this attribute, rebuilding the VB .NET solution, and then rereferencing the VB .NET component in its VB 6.0 host application, you'll see that the VB 6.0 IntelliSense no longer shows the VB .NET method or its parameters. In addition, the VB 6.0 object browser will no longer show the VB .NET method. The code will carry on working as before, but developer productivity in COM Interop situations is likely to plummet without the help of the VB 6.0 object browser and IntelliSense. So why doesn't the VB .NET compiler include this seemingly essential attribute by default?

If you recall from my earlier discussion of the differences between the managed and unmanaged worlds , COM only works with interfaces, whereas .NET has an object-based view of the world. By default, the VB .NET compiler adds a ClassInterface attribute with a type of AutoDispatch to any VB .NET class marked for COM Interop. This automatically generates a dispatch-only class interface for COM clients to consume . This type of class interface omits the interface description from the resulting type library. This means that Visual Studio (or Regasm ) doesn't publish any type information for the exposed methods . Listing 11-2 shows the type library that the OleView utility shows is produced by Visual Studio (or Regasm ) for the MathClass class with a ClassInterface attribute of AutoDispatch . As you can see, it shows no information about the class method and its parameters.

Listing 11-2. Type Library Information with a ClassInterface Attribute of AutoDispatch
start example
 [   uuid(E3F24144-AD11-3282-8347-B1AF8D277B1B),   hidden,   dual,   custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, InteropDemo.MathClass) ] dispinterface _MathClass {     properties:     methods: }; 
end example

The benefit of this approach is that it doesn't break late-bound VB 6.0 (or other COM) clients when you modify the VB .NET component, because information about the component is retrieved dynamically at runtime rather than at compile time.

The drawback of using a ClassInterface of AutoDispatch is that VB clients of the component can only do late binding to the .NET class, as there's no interface description in the type library. Hence, the VB 6.0 object browser and IntelliSense are blind to the class methods. Although this approach gives you some flexibility in modifying your .NET classes, it means that you'll encounter the standard problems with late binding: no compile-time checking and slower performance. The lack of compile-time checks means that you won't see any problems involved with calls into the VB .NET component until the method calls actually execute, which means a significant likelihood of runtime bugs in your code.

Overriding the COM Interop default by setting the ClassInterface attribute explicitly to AutoDual , as I did in the second example application, gives you back the benefits of early binding. You can see the methods and method arguments in the VB 6.0 object browser and IntelliSense, and the compiler can check your method calls before your code starts to run. Listing 11-3 shows the OleView listing of the type library produced by Visual Studio (or Regasm ) for the MathClass class, which has a ClassInterface attribute of AutoDual . As you can see, it shows full information about the class method and its parameters.

Listing 11-3. Type Library Information with a ClassInterface Attribute of AutoDual
start example
 [   uuid(04EFAF41-9530-3EA1-BFAE-7B2655428B28),   hidden,   dual,   nonextensible,   custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, InteropDemo.MathClass) ] dispinterface _MathClass {     properties:     methods:         [id(00000000), propget,           custom(54FC8F55-38DE-4703-9C4E-250351302B1C, 1)]         BSTR ToString();         [id(0x60020001)] VARIANT_BOOL Equals([in] VARIANT obj);         [id(0x60020002)]         long GetHashCode();         [id(0x60020003)]         _Type* GetType();         [id(0x60020004)]         double AddTwoNumbers(                          [in] double FirstNumber,                          [in] double SecondNumber); }; 
end example

Unfortunately, this early binding convenience comes at the cost of breaking the interface between the VB 6.0 application and the VB .NET component every time that you change your VB .NET component, regardless of whether you change the actual interface. For instance, adding a new method to the class interface causes the type library to be regenerated with the methods potentially in a different order and using completely different dispatch IDs. This breaks both early-bound and late-bound COM clients, and it means that any change to the VB .NET component requires a recompilation of every VB 6.0 client of that component.

The recommended way of allowing early binding in this situation while still being able to handle versioning issues is to set the ClassInterface attribute explicitly to None , and factor the public interface methods into a completely separate interface that is then implemented by the .NET component. Listing 11-4 shows how you could do this for the MathClass class. Compare this with Listing 11-1 to see the changes.

Listing 11-4. VB .NET Can Combine COM Early Binding with Good COM Versioning
start example
 Option Strict On Imports System.Runtime.InteropServices Public Interface IMathClass     Function AddTwoNumbers(ByVal FirstNumber As Double, _                                   ByVal SecondNumber As Double) As Double End Interface <ClassInterface(ClassInterfaceType.None)> _ Public Class MathClass : Implements IMathClass     Public Sub New()     End Sub     Public Function AddTwoNumbers(ByVal FirstNumber As Double, _                                 ByVal SecondNumber As Double) As Double _                                 Implements IMathClass.AddTwoNumbers         'Return the result to VB6         Return FirstNumber + SecondNumber     End Function End Class 
end example

The separation of the interface from its implementation means that Visual Studio or Regasm will generate a dispatch-only interface for the IMathClass interface and not generate any COM interface for the implementation class. Now you're free to change the code within the implementing class without breaking the interface. Although this is slightly more work, it allows COM early binding together with flexibility to change the .NET component without breaking the interface. This is likely to mean fewer bugs, more stable code, and better developer productivity. Listing 11-5 shows the type library produced for the source code shown in Listing 11-4. Notice that IMathClass is now the default interface for the .NET component.

Listing 11-5. Type Library Information with a Separately-Implemented Interface
start example
 [   odl,   uuid(F7826BDD-938F-3BD5-9D4F-7C8D95DE5C4C),   version(1.0),   dual,   oleautomation,   custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, InteropDemo.IMathClass) ] interface IMathClass : IDispatch {     [id(0x60020000)]     HRESULT AddTwoNumbers(                      [in] double FirstNumber,                      [in] double SecondNumber,                      [out, retval] double* pRetVal); }; 
end example

So as a final reminder of how to handle COM versioning when writing COM Interop applications, this list summarizes the advantages and disadvantages of each way of generating a .NET class interface:

  • ClassInterfaceType . AutoDispatch is the default. It's friendly about COM versioning, but at the cost of allowing only late-bound COM clients and potential bugs that won't be found until runtime.

  • ClassInterfaceType . AutoDual allows early-bound COM clients and compile-time checking, but completely ignores COM versioning, meaning that you have to recompile your COM clients every time the VB .NET component is changed.

  • ClassInterfaceType . None together with a separation of the class interface from its implementation gives you the best of both worlds: early binding and compile-time checking together with the flexibility to change the VB .NET component.

Comprehensive VB .NET Debugging
Comprehensive VB .NET Debugging
ISBN: 1590590503
EAN: 2147483647
Year: 2003
Pages: 160
Authors: Mark Pearce

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net