3

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.

Community
  • 1
  • 1
Chloe
  • 41
  • 6
  • Is your COM client early-bound or late-bound? Also, can you show some (at least the header) of your C# class? – noseratio Nov 02 '13 at 13:08
  • 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 Nov 04 '13 at 14:32
  • I've [explained](http://stackoverflow.com/a/19777899/1768303) how you could try to simulate the binary compatibility, @Ivy. Worth a shot. – noseratio Nov 04 '13 at 22:03
  • 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 Nov 04 '13 at 22:25
  • Don't use the `new` keyword. See my thoughts about it in the comments to my answer. – noseratio Nov 04 '13 at 22:30

2 Answers2

3

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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • I presume the source code described above is the same, regardless of the version of Visual Studio is used. I am not sure the guids *have* to change, simply because the code is now being compiled with a different version of VS. Obviously C++ COM objects do not exhibit this issue... Can you please elaborate, Hans? – alexg Nov 01 '13 at 23:12
  • The VS version doesn't matter when it is done properly. As shown. The guids *must* change when the interface changes, hard COM rule. – Hans Passant Nov 01 '13 at 23:25
  • Thank you Hans, you are right that the part of class XXadapter implementation is exposed to its COM clients. Also, there is no explicitly defined interface for _XXadapter. My question now is because XXadapter inherits from several COM interfaces, then if I define the _XXadapter and specify **[DispID()]** to keep the backwards compatibility for those methods, does this new defined _XXadapter keep the inheritance? I have tried and the compiler asked me to add **new** keyword to hide inherited members because of warning CS0108. – Chloe Nov 04 '13 at 18:43
  • I have no idea what you are talking about. – Hans Passant Nov 04 '13 at 19:06
  • Sorry that I did not explain clearly. This question is modified to explain the goal of XXadapter. The origins of the interfaces that XXadapter implements are all in the legacy unmanaged C++ code, so C# assembly is not the original creator of the interface being used. Now, I try to define `public interface _XXadapter : ICOMInterface{...}` and let class XXadapter only implement _XXadapter. Here is the warning: **warning CS0108: CompanyName._XXadapter.Method_one(...) hides inherited member CompanyName_Lib.IComInterface.Method_one(...). Use the new keyword if hiding was intended.** – Chloe Nov 04 '13 at 21:36
  • Your IDL doesn't say that _XXadapter inherits IComInterface. So don't inherit it. Only have the class implement it. – Hans Passant Nov 04 '13 at 21:46
2

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.

noseratio
  • 59,932
  • 34
  • 208
  • 486
  • Thank you Noseratio. **[DispID]** was not used for public method in Class XXadapter. I think COM client binding type is early binding in our case(sorry I missed this question). You are right that _IXXXMethods(or _XXadapter that was recognized by XXadapter's COM clients) has to be defined explicitly for sure in order to specify UUID and DispID. However, as I asked to Hans, the only concern I have is the warning related to inheritance. – Chloe Nov 04 '13 at 22:20
  • @ivy, this is an important concern. You resolve collisions like this by **[explicitly implementing](http://msdn.microsoft.com/en-us/library/aa288461(v=vs.71).aspx)** the interface. E.g., inside your class: `void _XXadapter.Method_one() { IComInterface.Method_one(); }` – noseratio Nov 04 '13 at 22:25
  • The origins of the interfaces(IComInterface) that XXadapter implements are all in the legacy unmanaged C++ code, so C# assembly is not the original creator of the interface being used. So it is impossible to call `IComInterface.Method_one();` when implementing `void _XXadapter.Method_one().` Actually, XXadapter has to define Method_one()in its own way. This post [link](http://stackoverflow.com/questions/2952950/how-to-declare-and-implement-a-com-interface-on-c-sharp-that-inherits-from-anoth) might relate to my concern. – Chloe Nov 04 '13 at 22:51
  • @Ivy, not sure I understand correctly, you may want to use pastebin.com or edit your question to post more code. Anyhow, if `XXadapter` now has to implement both `_XXadapter` and `IComInterface`, I still recommend using two *explicit implementations*, which would share the common code, i.e.: `void _XXadapter.Method_one() { this.InternalMethodOne(); }`, `void IComInterface.Method_one() { this.InternalMethodOne(); }`. Thus, the private class method `InternalMethodOne` would contain the actual logic. – noseratio Nov 04 '13 at 22:59
  • @Ivy, I guess I see what you're doing wrong: `public interface _XXadapter : ICOMInterface {...}`. Do not derive `_XXadapter` from anything. It has to be a dual (`IDispatch`-based) interface with **exactly the same methods layout** as in the VS2010 IDL file you showed, for this idea to work. Instead, derive `XXadapter` class from both `_XXadapter` and `IComInterface`, and implement both interfaces explicitly, as I explained in the above comment. I've updated my answer to reflect that. – noseratio Nov 04 '13 at 23:53
  • 1
    Thank you so much @Noseratio. You are absolutely right that the warning about inheritance is caused by defining `public interface _XXadapter : ICOMInterface {...}`. Also, the explicit implementation is critical too. Thank you for your insight that helping us solve the problem. – Chloe Nov 05 '13 at 16:40