3
votes

We have developed a .NET Assembly named XXadapter. The goal is to have XXadapter act as a COM object to unmanaged clients. XXadapter class implements a C++ COM IDL-defined interface. The C++ COM object was added as a reference to the C# project, thus exposing COM API via Interop. Therefore, class interface _XXadapter interface was generated by COM Interop, and used by unmanaged clients.

Everything was good until I tried to migrate XXadapter project from VS2010 to VS2012(Please note that there are NO source code changes). _XXadapter's uuid and some of method's DispID in _XXadapter have been changed.

This is the XXadapter class's properties:

[ComVisible(true)]
[ComSourceInterfaces( typeof( _IBaseEvents ) )]
[ClassInterface(ClassInterfaceType.AutoDual)]
[Guid("class ID")]
public partial class XXadapter : ICOMInterface
{
...
}

Here is the _XXadapter definition in type library(viewed by oleview.exe) before migration:

[
  odl,
  uuid(E8******-****-****-****-************),
  hidden,
  dual,
  nonextensible,
  oleautomation,
  custom(123456-1234-1234-1234-123456789012, CompanyName.XXadapter)    

]
interface _XXadapter : IDispatch {
    [id(00000000), propget,
      custom(654321-4321-4321-4321-210987654321, 1)]
    HRESULT ToString([out, retval] BSTR* pRetVal);
    [id(0x60020001)]
    HRESULT Equals(
                    [in] VARIANT obj, 
                    [out, retval] VARIANT_BOOL* pRetVal);
    [id(0x60020002)]
    HRESULT GetHashCode([out, retval] long* pRetVal);
    [id(0x60020003)]
    HRESULT GetType([out, retval] _Type** pRetVal);
    [id(0x60020004)]
    HRESULT GetVersion([out, retval] BSTR* pRetVal);
    [id(0x60020005)]
    HRESULT Method_one(...);
    [id(0x60020006)]
    HRESULT Method_two(...);

    ...

    [id(0x6002000e)]
    HRESULT Method_three(...);
    [id(0x6002000f)]
    HRESULT Method_four();
    [id(0x60020010)]
    HRESULT Method_five(...);

    ...
};

After migration, the _XXadapter defined as

[
  odl,
  uuid(E6****-****-****-****-************),
  hidden,
  dual,
  nonextensible,
  oleautomation,
  custom(123456-1234-1234-1234-123456789012, CompanyName.XXadapter)    

]
interface _XXadapter : IDispatch {
    [id(00000000), propget,
      custom(654321-4321-4321-4321-210987654321, 1)]
    HRESULT ToString([out, retval] BSTR* pRetVal);
    [id(0x60020001)]
    HRESULT Equals(
                    [in] VARIANT obj, 
                    [out, retval] VARIANT_BOOL* pRetVal);
    [id(0x60020002)]
    HRESULT GetHashCode([out, retval] long* pRetVal);
    [id(0x60020003)]
    HRESULT GetType([out, retval] _Type** pRetVal);
    [id(0x60020004)]
    HRESULT GetVersion([out, retval] BSTR* pRetVal);
    [id(0x60020005)]
    HRESULT Method_three(...);
    [id(0x60020006)]
    HRESULT Method_four(...);
    [id(0x60020007)]
    HRESULT Method_five(...);
    [id(0x60020008)]
    HRESULT Method_one(...);
    [id(0x60020009)]
    HRESULT Method_two(...);

    ...
};

Not only the uuid of _XXadapter has been changed, but also the DispID of all the Methods_XXXX().

As a result the _XXadapter assembly has lost its backwards compatibility with its COM clients.

By investigating and googling for this issue, I found the reordering of Method_three/four/five() in type library could be caused by these three methods are partially declared in a separate file. I have tried to move the declaration for all COM visible methods into the same file, this problem can be solved. However, this makes a huge file which we originally wanted to avoid. Is there any solution to keep the backwards compatibility without moving COM visible methods? Does anyone know the root cause of reordering the methods? Thank you so much.

2
Is your COM client early-bound or late-bound? Also, can you show some (at least the header) of your C# class?noseratio
Thank you Noseratio. XXadapter class is partial defined with following attributes: [ComVisible(true)] [ComSourceInterfaces( typeof( _IXXXEvents ) )] [ClassInterface(ClassInterfaceType.AutoDual)] [Guid("15******-****-****-****-************")] public partial class XXadapter : IXXVersion{...}Chloe
I've explained how you could try to simulate the binary compatibility, @Ivy. Worth a shot.noseratio
Yes, @Norseratio. I believe you pointed me the right direction. I replied your answer and talked about my concern. How do you think about the warning and adding new keyword? Thank you.Chloe
Don't use the new keyword. See my thoughts about it in the comments to my answer.noseratio

2 Answers

2
votes

You showed too little of your C# code, I don't see if you're using [DispId] attributes on the public methods of your class. Also, you didn't answer my question in the comments about the COM client binding type. What is the nature of your COM client code?

If it is late-bound, chances are your could still save the situation with relatively little efforts, by providing exactly the same DispId attributes for your methods as they were generated by VS2010.

In case of early binding (most often used with C++ COM clients), you still could try to simulate the layout of your old, VS2010-generated class interface with a new, manually defined, fine-tuned C# interface, to preserve the binary compatibility (including the IID, methods layout and DispIds). In this case, your new class would look like this (note the new ComDefaultInterface(typeof(_XXadapter)) attribute):

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(_XXadapter))]
[ComSourceInterfaces(typeof(_IXXXEvents))]
[Guid("15******-****-****-****-************")]
public partial class XXadapter: _XXadapter, ICOMInterface
{
    // ...
}

Now, the new _XXadapter interface I'm talking about would look like this:

// keep the IID and methods layout as generated by VS2010 for _XXadapter,
// the way it appears in the IDL from OleView (interface _XXadapter)

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
[Guid("E8****-****-****-****-************")] 
public interface _XXadapter {
    [DispId(00000000)]
    string ToString { get; }

    [DispId(0x60020001)]
    bool Equals([In] object obj);

    // etc...
}

This way, you may be able to get away without recompiling your COM client.

Moreover, it appears the XXadapter class now has to implement both _XXadapter and IComInterface interfaces which have the same method names. This can be done using two explicit implementations, sharing the common code, i.e.:

public partial class XXadapter: _XXadapter, ICOMInterface
{
    void _XXadapter.Method_one() { this.InternalMethodOne(); }

    void ICOMInterface.Method_one() { this.InternalMethodOne(); }

    private void InternalMethodOne() { /* the actual implementation */ }
}

Thus, the InternalMethodOne method would contain the actual logic.

3
votes

The guids have to change, a rock-hard requirement in COM. The underlying core mistake you made is exposing the class implementation. Visible from seeing the System.Object methods getting exposed in your coclass, like ToString, Equals, etc. Which exposed you to the risk of the compiler re-arranging the method order, an undefined implementation detail.

The right way to do it is to always make the implementation invisible. Like this:

[InterfaceType(ComInterfaceType.InterfaceIsDual)]
[Guid("put the IID here")]
public interface IXXadapter {
    string ToString();
    bool Equals(object obj);
    int GetHashCode();
    Type GetType();
    // etc...
}

[ClassInterface(ClassInterfaceType.None)]
[Guid("put the CLSID here")]
public class XXadapter : IXXadapter {
    // etc..
}

Note ClassInterfaceType.None, that hides the class internals. The COM client only sees the interface declarations, they are fixed and the order is predictable. I included the 4 System.Object methods that you originally exposed, you don't have to write their implementation. That should rescue your binary compatibility, just ensure you update the [Guid] attributes to match the old ones.