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

pev*_*l27 3 c# com

我有以下课程:

[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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

Han*_*ant 6

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

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

importlib("mscorlib.tlb");
Run Code Online (Sandbox Code Playgroud)

这非常尴尬,客户端程序员不仅需要添加对类型库的引用,还需要添加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..
}
Run Code Online (Sandbox Code Playgroud)

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

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

[
  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;
};
Run Code Online (Sandbox Code Playgroud)

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

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


长话短说,发生这种情况是因为您使用了ClassInterfaceType.AutoDual. 这是一种便利,但不是很好,微软强烈反对它,推荐使用 ClassInterfaceType.AutoDispatch。但这仅对脚本语言有用。

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

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