C# COM 可见类型:是否需要 GUID?

C# COM visible type: is GUID needed?

我有以下 class:

[ComVisible(true)]
[Guid("F8351C66-7F0E-4E38-AE64-9A262202E230")]
[ProgId("CarProject.CarFactory")]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class CarFactory
{
    public CarFactory()
    {}

    [DispId(0)]
    public Car CreateCar(int tyres)
    {
         return new Car(tyres);
    }
}

[ComVisible(true)]
[Guid("83f622b9-74f4-4700-9167-52c4ce9e79aa")]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class Car
{
    [DispId(0)]
    public int NumberOfTyres { get; private set; }

    public Car(int tyres)
    {
        this.NumberOfTyres = tyres;
    }
}

Car 对象由工厂创建,因此 COM 客户端仅使用自动生成的 _Car 接口。请注意,没有默认构造函数,因此无法通过 COM 实例化 class。

我的问题是:是否需要 Guid 属性?我在注册表中找不到它的值。

不,不需要它,因为它永远不会被使用。事实上,从来不需要它,应用 [Guid] 是一种非常糟糕的做法。

这种声明方式还有其他几个问题,讨论它们都需要我写一本书,不太实用。查看反编译的类型库会容易得多,这样您就可以看到客户端编译器看到的内容。使用 Visual Studio 开发人员命令提示符,如果您还没有类型库,请使用 Tlbexp.exe 生成类型库。 运行 Oleview.exe、文件 > 查看类型库和 select .tlb 文件。突出显示您现在看到的相关详细信息:

importlib("mscorlib.tlb");

这很尴尬,客户端程序员不仅需要添加对您的类型库的引用,而且 mscorlib.dll 的 .NET 类型库。存放在c:\windows\microsoft.net\framework\v4.0.30319目录下。或 v2.0.50727,旧版本。非常不直观,通常不会有好结果。请注意您在上一个问题中是如何遇到麻烦的。继续阅读以找出发生这种情况的原因。

[
  odl,
  uuid(BD7A2C0E-E561-3EBC-8BB7-1C72EE61D5B0),
  hidden,
  dual,
  nonextensible,
  oleautomation,
  custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "ClassLibrary171.Car")    

]
interface _Car : IDispatch {
    // etc..
}

这是自动生成的 "class interface",您现在已经知道了。这是客户端编译器实际用来进行调用的那个。请注意 [uuid] 属性,与 C# 中的 [Guid] 属性相同。但它有一个相当随机的值。它是自动生成的,就像界面一样。所以在声明中使用 [Guid] 实际上根本没有完成任何事情。

[hidden] 属性以及接口名称上的前导下划线告诉类型浏览器工具(如 VS 中的对象浏览器)隐藏声明。使用 OleView 查看详细信息变得很重要。否则,一个怪癖可以追溯到不支持接口的语言,旧版本的 Visual Basic(如 VB6 和 VBA)和脚本语言(如 VBScript 和 JavaScript)是主要例子。此类语言需要通过使界面看起来像 class 来模拟它们,这是您考虑公开 class 的唯一原因。

[
  uuid(83F622B9-74F4-4700-9167-52C4CE9E79AA),
  version(1.0),
  noncreatable,
  custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "ClassLibrary171.Car")
]
coclass Car {
    [default] interface _Car;
    interface _Object;
};

注意 [noncreatable] 属性。类型库导出器可以看到客户端代码永远无法创建 Car 实例,因为它没有默认构造函数。这反过来又帮助 Regasm.exe 弄清楚不必为 Car 注册 CLSID,这就是为什么您无法在注册表中找到它的原因。

并注意 _Object 界面。出现是因为每个 .NET class 都派生自 System.Object。同样,_Car 接口也有 Object 的方法(ToString、Equals、GetHashCode、GetType),因为 Car 继承了它们。这就是您最终依赖 mscorlib.tlb 的方式。它们 可能 对客户端程序员有用,但这并不常见,您通常肯定不想记录它们。越少暴露越好。


长话短说,这是因为您使用了 ClassInterfaceType.AutoDual。它很方便,但不是很好,Microsoft 强烈反对,建议使用 ClassInterfaceType.AutoDispatch。但这仅对脚本语言有用。

您可以通过显式声明 ICar 接口而不是让类型库导出器为您生成它来避免这一切。它需要是 [ComVisible(true)] 并且 class 需要实现它。现在您可以使用 ClassInterfaceType.None.

现在你可以给界面一个[Guid]了。但要注意这是一种不好的做法,COM 要求接口 不可变 。一旦你在野外暴露了一个,你就再也不能改变它了。非常重要的是,COM 有一个讨厌的 DLL 地狱问题,这是由于在机器范围内进行注册而导致的,您通常无法保证可以重新编译客户端程序。这可能导致非常难以诊断的崩溃。如果您确实必须更改它,那么界面需要一个新的 [Guid],以便新旧界面可以共存。最简单的做法是完全省略该属性,CLR 已经自动生成了它。使用 [Guid] 确实允许创建与旧接口声明二进制兼容的接口,但这很难正确执行。