ComClass and backward compatibility

Can anybody tell me the recommended way to expose VB.NET classes to COM if we expect the interfaces to change

The Microsoft documentation recommends using the ComClass attribute (or template) for classes that need to be exposed to COM, but ComClass doesn't seem to have any provision for backward compatibility when I later add properties or methods to the class. If I just add public members to the VB.NET class, I get a COM interface with the same IID but with additional members. This violates the COM rule which states that COM interfaces must be immutable.

Clearly I need to assign a new InterfaceID to the VB.NET class, while still implementing the old interface, and I am struggling to find any documentation on how to do this in a VB.NET ComClass. Can somebody point me at some documentation on this subject I feel there must be some out there, since the only Microsoft documentation I've found so far states that ComClass is the way to go, and gives no indication that COM clients will break if you ever change the interfaces.


Answer this question

ComClass and backward compatibility

  • KimberlyL

    Dear Community,

    I disagree with and cannot adopt the first solution offered by nobugz. It boils down to "just hope you never have to deal with it", and it's too big a project to take that risk. Suppose I find out in 18 months' time that I can't just get away with it, and that I should have used a different IID for every version of every interface in every one of a large number of components. At that stage, it is not simply a huge job to do it all retrospectively; It is actually too late to do anything about it because the interfaces are already out there, i.e. multiple different interfaces with the same IID, which IMHO is just asking for trouble (and illegal).

    Furthermore, the comment that "nothing is going to fix that" (in reference to a v2 client running against a v1 server) ignores my comments about the client making an informed decision, given the information that the v1 server does not in fact implement the v2 interface. If the client is given false information by the server (i.e. that it does support the interface because the IIDs are the same) then it cannot make an informed decision; All the client can do in that case is crash and burn.

    I also cannot adopt the second solution offered by nobugz. The idea of distributing each version of the COM server with a different filename and different GUIDs is unsatisfactory for the following reasons:

    1. It is a COM-visible component, which means that it must have an InProcServer32 entry in the registry (I cannot assume all clients will be running Windows XP or later), and AFAIK there can be only one such registry entry per component (or per class), therefore multiple versions of the same COM component cannot coexist on the same machine, much less in the same process.
    2. All interface versions need to be implemented by a single COM object. For example, consider a v2 client that obtains a COM object from the v2 server and passes it to a v1 client. The v1 client will be unable to use the object if the v2 COM server doesn't implement the v1 interface.

    Therefore, if anybody else out there has any suggestions I'd really like to hear them.

    Many thanks,

    CP


  • VSB

    Further to my original post ... I found this article:

    http://msdn.microsoft.com/library/default.asp url=/library/en-us/dnlongmig/html/intmiglongch03.asp

    The section entitled Specific Recommendations on Exposing Managed APIs to COM says that the ClassInterface attribute has versioning issues (with AutoDual and AutoDispatch), and that we should use ClassInterface(None) and explicitly define a new interface whenever we add functionality. It doesn't mention the ComClass attribute, but I suspect ComClass is functionally equivalent to ClassInterface(AutoDual) in terms of having the same versioning issues. Can anybody confirm or deny this

    But interestingly, further down that section (under subheading "Interface Inheritance") it gives an example of a .NET interface derived from another .NET interface and what happens to it when exported to a Type Library. As follows:

    .NET Interfaces:
    public interface IWork
    {
    void Method1();
    }
    public interface IWork2 : IWork
    {
    void Method2();
    }

    Exported COM Interfaces:
    public interface IWork : IDispatch
    {
    void Method1();
    }
    public interface IWork2 : IDispatch
    {
    void Method1();
    void Method2();
    }

    If this were accurate then it would be ideal for me, as I could use interface inheritance to define subsequent versions of an interface, and implement them easily in my VB.NET class. But on my machine it appears not to be accurate. This is what I get in the Type Library:

    Exported COM Interfaces on my machine:
    public interface IWork : IDispatch
    {
    void Method1();
    }
    public interface IWork2 : IDispatch
    {
    void Method2();
    }

    Notice the absence of Method1 in the IWork2 interface. My machine has the following:

    Microsoft Visual Studio 2005
    Version 8.0.50727.42 (RTM.050727-4200)

    Microsoft .NET Framework
    Version 2.0.50727

    I would like to know whether this is a bug in the documentation or a bug in the Type Library Exporter. Because if it's the latter then maybe I can expect it to be fixed, and then my COM versioning problems would be resolved. I have a vague notion that a previous version of VS.NET used to include inherited interface members in the exported Type Library, but I no longer have that version installed so I can't be sure about that.

    Does anyone have any insight into this


  • Blkbird

    I cannot argue with your logic, it is the company line. I'm just talking out of experience. I just never got in trouble when I distributed an updated COM server. I'd distribute an updated client too but had no control over the copies that were swiped by whomever. Those copies always just worked fine with V2 server with the compatible V-table. It just never happened that somebody swiped the V2 client and tried to use it with a V1 server. Nothing is going to fix that.

    The solution to your predicament is still easy if you follow the rulez. Just distribute the V2 server with new IIDs and a new filename (or folder). Everybody that still uses V1 client and V1 server is not going to be affected...



  • ivanchain

    I tried exporting a type library from a small sample with interface inheritance and I'm seeing the same behavior that you are, so the documentation is indeed incorrect.

    Now back to your problem, I tried both of the methods that you describe to expose a backwards compatible COM object and ran into the same issues. This basically means that there is no automated way to expose a backward compatible interface, which also holds true for the more advanced COM scenarios. I was, however, able to workaround the associated problems with only minor tweaks and a little bit of manual changes.

    The first question that I have. Do you really need your COM objects to implement IDispatch Because if you don't then you can simply declare your interfaces like this:

    <Guid("d0b8ae5c-29f0-4657-a608-c015bd39ef26")> _
    <ComVisible(True)> _
    <CLSCompliant(False)> _
    <InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)> _
    Public Interface IYourInterface1
    Sub Method1()
    End Interface

    <Guid("0029991c-91d2-4f3d-9d8a-9bb17f7224fc")> _
    <ComVisible(True)> _
    <CLSCompliant(False)> _
    <InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)> _
    Public Interface IYourInterface2
    Inherits IYourInterface1
    Sub Method2()
    End Interface

    When exporting this into a TLB, the inheritance will be lost, but to the consumers it really doesn't matter. They QI for IYourInterface1 for all the old behavior, and QI for IYourInterface2 for all the new behavior.

    Now, if you really need your interfaces to inherit from IDispatch. You can follow either of the 2 approaches that you described. The differences would be:

    Method A

    1. Compile the vb code into a dll.
    2. Run the tlbexp.exe utility on that dll to generate a type library.
    3. Run oleview.exe utility on the resulting type library.
    4. Copy the "generated" idl that is displayed and manually add back the inheritance relationship:
      • Make the first interface derive from IDispatch
      • Make the second interface derive from the first interface
    5. Run midl.exe compiler to generate the final type library.

    Method B

    1. Define the interfaces in a .idl file.
    2. Compile the IDL file into a typelibrary with the midl.exe compiler.
    3. Run the tlbimp.exe utility on the typlelibrary to generate a managed wrapper.
    4. Run the ildasm.exe utility on the managed wrapper and output the text to a file.
    5. Edit the file and remove the duplicate methods from the second interface.
    6. Run the ilasm.exe compiler to generate the "correct" interop wrapper.

    COM Interop should be easier and require less manual intervention, but until that happens these workarounds should get the job done.

    Hope this helps,



  • Chris Jiang

    Chris, in reply to your comment:

  • "The problem with Method A is that the type library I generate with this method will differ from (and presumably be replaced by) the one that gets generated/exported on a client's machine when they run REGASM.EXE /TLB."
  • You can always ship both the dll & tlb files that you generate and have the final users register them separately:

    • Run regasm.exe without the /tlb option to register the dll. (This won't generate a tlb)
    • Run regsvr32.exe to register the correct tlb.

    Hope this helps,



  • Kolja

    When you change the interface, you need to change the IID. The ComClassAttribute class lets you do that. When you just add additional methods/properties, you don't need to change the IID, as long as you add them at the end of the interface. A client that expects the original interface will still see the correct dispatch table.


  • ChristianBG

    Thanks for taking the time and effort to give such a considered response.

    I have issues with your first suggestion, of declaring only IUnknown interfaces and keeping them separate (i.e. v2 interface containing only the new functionality added in v2, and so on):
    • Firstly we have always provided dual interfaces to our consumers and I would like to continue to do so, because to remove support for IDispatch would be a technological step backwards, and moving to VB.NET from VB6 is supposed to be a step forward.
    • Secondly, I don't want to require all that additional effort from our consumers. They shouldn't have to keep switching interfaces to get at different properties and methods; they should just be able to use a single interface that represents all the functionality we've added to date.
    I appreciate your suggestions regarding the dual interfaces and how to work around some of the issues I originally complained about. However:
    • The problem with Method A is that the type library I generate with this method will differ from (and presumably be replaced by) the one that gets generated/exported on a client's machine when they run REGASM.EXE /TLB.
    • As you rightly point out, Method B is just far too much work to have to do every time I want to release a version with one additional method in one of the many classes in the component. (The same applies to method A, perhaps to a lesser extent).
    With both methods A and B, it would be highly error-prone to try and do it manually every time, and a major project to write tools to automate the process, almost equivalent to writing a replacement type-library exporter. What it needs, IMHO, is more attributes to customize the existing type-library exporter. But as you say, "until that happens..."

    So in the meantime I've decided to go with the approach I mentioned in my second post, of overriding the framework-provided implementation of QueryInterface, so that when a client queries for an older version of an interface it will be given the latest version instead (which is binary compatible with the older versions, of course). And I've had to resort to employing a hack to get this done, because I still haven't found a way to do it through the framework. But I think it's worth it because of the time and effort it will save on all future releases.

    I'm also going to mark this question as "answered" for now, but I guess it won't be truly answered until the next version of the framework, and only then if Microsoft can be bothered to resolve these issues (which they probably won't, as I suspect they're hoping that COM will just go away).

    Thanks again for your response,

    Chris Pyman



  • Prasenna

    nobugz, I cannot keep the same IID when I add methods/properties, because that would violate the COM rule which states that COM interfaces must be immutable.

    There is a reason for that rule, even when just adding properties/methods as opposed to changing the interface. Consider an early-bound COM client compiled against v2 but running against v1. The client will ask the COM object "do you support the v2 interface" and the COM object will say "yes I do, because it has the same IID as v1", but the COM object will not in fact support the later methods added in v2. The vtable it provides will be smaller than the client is expecting. The best that can happen in this case is that the program will crash when the client calls a v2 method, but the actual behaviour is totally unpredictable. We might, if we're lucky, just get an access violation when the client reads the vtable; or we might execute random code and potentially do damage.

    Needless to say, this is unacceptable. What ought to happen (which would in fact happen if I obey the rules of COM) is that the COM object should say "no I do not support the v2 interface", and then the client can make an informed decision as to what to do next (e.g. query for v1 or some other interface, or cancel the operation in a controlled manner). But it cannot do that if I keep the same IID when extending the interface.

    I'm aware of the need to preserve the order of members in the source code, as this determines the order of functions in the vtable (when using the ComClass attribute). And I have no intention of changing the interfaces as such, just extending them by adding properties and methods. But as I said, in order to comply with the COM rules I have to assign a new IID when I release a version with new properties or methods. And for backward compatibility I still need to implement the old interface, because a v1 client should be able to run against a v2 COM server.

    I've even tried to find a way to override the default implementation of QueryInterface, because as long as I keep the order of members in the source code, any client that queries for the v1 interface can be given a v2 interface pointer because it will be binary compatible. But I haven't found a way to do this.

    Please help


  • ComClass and backward compatibility