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], чтобы они соответствовали старым.