3

我们开发了一个名为 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() 的重新排序可能是由于这三个方法部分声明在单独的文件中造成的。我尝试将所有 COM 可见方法的声明移动到同一个文件中,这个问题可以解决。但是,这会产生一个我们原本想要避免的巨大文件。是否有任何解决方案可以在不移动 COM 可见方法的情况下保持向后兼容性?有谁知道重新排序方法的根本原因?太感谢了。

4

2 回答 2

3

向导必须改变,这是 COM 中的一项严格要求。您犯的基本核心错误是暴露类实现。从您的 coclass 中暴露的 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] 属性以匹配旧属性。

于 2013-11-01T21:07:15.637 回答
2

您显示的 C# 代码太少,我看不出您是否在[DispId]类的公共方法上使用了属性。此外,您没有在关于COM 客户端绑定类型的评论中回答我的问题。您的 COM 客户端代码的性质是什么?

如果它是后期绑定的,那么您仍然可以通过为您的方法提供与 VS2010 生成的完全相同的DispId属性,从而通过相对较少的努力来挽救这种情况。

早期绑定的情况下(通常与 C++ COM 客户端一起使用),您仍然可以尝试使用新的、手动定义、微调的 C# 接口来模拟旧的 VS2010 生成的类接口的布局,以保持二进制兼容性(包括 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方法将包含实际逻辑。

于 2013-11-04T21:52:15.800 回答