Uuid и DispID интерфейса класса изменяются при переходе с Visual Studio 2010 на 2012

Мы разработали сборку.NET с именем XXadapter. Цель состоит в том, чтобы XXadapter выступал в качестве COM-объекта для неуправляемых клиентов. Класс XXadapter реализует интерфейс, определенный в C++ COM IDL. COM-объект C++ был добавлен в качестве ссылки на проект C#, таким образом предоставляя COM API через Interop. Поэтому интерфейс класса _XXadapter был сгенерирован COM Interop и использован неуправляемыми клиентами.

Все было хорошо, пока я не попытался перенести проект XXadapter с VS2010 на VS2012(обратите внимание, что в исходном коде НЕТ изменений). UUID _XXadapter и некоторые DispID метода в _XXadapter были изменены.

Это свойства класса XXadapter:

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

Вот определение _XXadapter в библиотеке типов (просматривается oleview.exe) перед миграцией:

[
  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(...);

    ...
};

После миграции _XXadapter определяется как

[
  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(...);

    ...
};

Изменен не только uuid _XXadapter, но также DispID всех Methods_XXXX().

В результате сборка _XXadapter утратила обратную совместимость с COM-клиентами.

Исследуя эту проблему, я обнаружил, что переупорядочение метода Method_three/four/five() в библиотеке типов может быть вызвано тем, что эти три метода частично объявлены в отдельном файле. Я попытался переместить объявление для всех видимых COM-методов в один файл, эту проблему можно решить. Тем не менее, это делает огромный файл, который мы изначально хотели избежать. Есть ли решение для обеспечения обратной совместимости без перемещения видимых COM-методов? Кто-нибудь знает причину переупорядочения методов? Огромное спасибо.

2 ответа

Решение

Вы показали слишком мало своего кода C#, я не вижу, используете ли вы [DispId]атрибуты открытых методов вашего класса. Кроме того, вы не ответили на мой вопрос в комментариях о типе привязки COM-клиента. Какова природа вашего кода COM-клиента?

Если он опоздал, скорее всего, вы могли бы спасти ситуацию с относительно небольшими усилиями,предоставив точно такой жеDispIdатрибуты для ваших методов, как они были сгенерированы VS2010.

В случае раннего связывания (чаще всего используется с C++ COM-клиентами), вы все равно можете попытаться смоделировать макет вашего старого, сгенерированного VS2010 интерфейса класса с новым, точно определенным вручную, тонко настроенным интерфейсом C#, чтобы сохранить двоичную совместимость (включая IID, расположение методов и DispIds). В этом случае ваш новый класс будет выглядеть так (обратите внимание на новый ComDefaultInterface(typeof(_XXadapter)) атрибуты):

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

Теперь новый _XXadapter интерфейс, о котором я говорю, будет выглядеть так:

// 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...
}

Таким образом, вы можете обойтись без перекомпиляции вашего COM-клиента.

Более того, оказывается XXadapter класс теперь должен реализовать как _XXadapter а также IComInterface интерфейсы, которые имеют одинаковые имена методов. Это можно сделать с помощью двух явных реализаций, использующих общий код, а именно:

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

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

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

Таким образом InternalMethodOne Метод будет содержать фактическую логику.

Руководства должны измениться, жесткое требование в COM. Основная ошибка, которую вы сделали, - разоблачение реализации класса. Видно, когда вы видите методы System.Object, выставленные в вашем коклассе, такие как ToString, Equals и т. Д., Что подвергает вас риску перекомпоновки компилятором порядка методов, что является неопределенной деталью реализации.

Правильный способ сделать это - всегда делать реализацию невидимой. Как это:

[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..
}

Обратите внимание на ClassInterfaceType.None, который скрывает внутренние компоненты класса. COM-клиент видит только объявления интерфейса, они фиксированы, а порядок предсказуем. Я включил 4 метода System.Object, которые вы изначально представили, вам не нужно писать их реализацию. Это должно спасти вашу двоичную совместимость, просто убедитесь, что вы обновили атрибуты [Guid], чтобы они соответствовали старым.

Другие вопросы по тегам