从Visual Studio 2010迁移到2012时,类接口的uuid和DispID会发生更改

时间:2013-11-01 19:03:19

标签: .net com com-interop idispatch typelib

我们开发了一个名为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可见方法?有谁知道重新排序方法的根本原因?非常感谢。

2 个答案:

答案 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类似乎现在必须实现具有相同方法名称的_XXadapterIComInterface接口。这可以使用两个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方法将包含实际逻辑。