我们开发了一个名为XXadapter的.NET程序集。目标是让XXadapter充当非托管客户端的COM对象。 XXadapter类实现C ++ COM IDL定义的接口。添加了C ++ COM对象作为C#项目的引用,从而通过Interop公开COM API。因此,类接口_XXadapter接口由COM Interop生成,并由非托管客户端使用。
在我尝试将XXadapter项目从VS2010迁移到VS2012之前,一切都很顺利(请注意,没有源代码更改)。 _XXadapter的uuid和 _XXadapter 中某些方法的DispID已被更改。
这是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(...);
...
};
不仅更改了_XXadapter的uuid,还修改了所有Methods_XXXX()的DispID。
因此,_XXadapter程序集已失去与COM客户端的向后兼容性。
通过调查和谷歌搜索这个问题,我发现类型库中的Method_three / four / five()的重新排序可能是由这三个方法partially declared在一个单独的文件中引起的。我试图将所有COM可见方法的声明移动到同一个文件中,这个问题可以解决。但是,这会产生一个我们原本想避免的巨大文件。是否有任何解决方案可以保持向后兼容性而无需移动COM可见方法?有谁知道重新排序方法的根本原因?非常感谢。
答案 0 :(得分:3)
guids 有要改变,这是COM中的一项苛刻要求。您犯下的底层核心错误是暴露类实现。可以看到System.Object方法在您的coclass中公开,比如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]属性以匹配旧的属性。
答案 1 :(得分:2)
您展示的C#代码太少,我不知道您是否在类的公共方法上使用[DispId]
属性。另外,您没有在关于 COM client binding type 的评论中回答我的问题。 COM客户端代码的性质是什么?
如果是迟到的,您可以通过相对较少的努力来保存情况,为您的方法提供完全相同的DispId
属性因为它们是由VS2010生成的。
如果早期绑定(最常用于C ++ COM客户端),您仍然可以尝试使用新的,手动定义的,精细模拟旧的VS2010生成的类接口的布局-tuned 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
接口。这可以使用两个explicit implementations来完成,共享公共代码,即:
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
方法将包含实际逻辑。