65.9K
CodeProject 正在变化。 阅读更多。
Home

VS.NET 2003 的 COM 类向导

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (38投票s)

2004年7月13日

6分钟阅读

viewsIcon

171610

downloadIcon

2306

一个简化 C# COM 类创建的向导。

Sample Image - CSCom.jpg

引言

几天前,Muhammad Musa Ali 在 Code Project 上发表了一篇关于《让 .NET 类库的 COM 接口可被晚期绑定调用》的文章,向所有的 VB 开发者展示了如何创建一个可以与外部世界(例如 VB、VBA、JScript 等非 .NET 客户端)进行互操作的类。

作为一名 C# 开发者,我感到非常震惊,竟然存在 VB.NET 类库的 COM 类向导,而 C# 却没有!我必须纠正这种不公平的状况 ;-),所以,我带来了:一个 C# 类库的 COM 类向导。

如何暴露一个类以进行 COM 互操作(C# 方式)

Muhammad 描述了两种暴露类以进行 COM 互操作的方式:简单方式和复杂方式。

简单方式就是 VS.NET 在你让它使用向导创建一个 COM 类时所做的事情。基本上,会创建三个 GUID:类本身、该类实现的 COM 可见接口,以及该类触发的事件。这三个 GUID 被填充到 ComClassAttribute 中,就完成了。编译器会处理其他所有事情。

直到那天,我才听说过 ComClassAttribute,于是我在 MSDN 上查找了这个属性。很快,我就发现了我为什么完全没有注意到这个属性:它位于我通常不接触的 Microsoft.VisualBasic 命名空间中。

所以,在 C# 类中使用这个属性是可能的,前提是你给你的项目添加一个对 VisualBasic 的引用(我承认,我并没有广泛地尝试过),但仅仅为了获取一个类属性就添加额外的引用似乎有点得不偿失。

于是,我尝试了复杂的方式:通过自己定义所有接口和属性来告诉编译器该怎么做。

Code Project 上也有一篇关于这个主题的非常详尽的文章(理解 .NET 应用程序与经典 COM 的互操作),所以我只描述重要步骤。

定义一个你想暴露给 COM 的接口

COM 是基于接口的,所以你的类必须告诉 COM 它将要暴露哪些方法和属性。

创建你的类并让它实现该接口

总得有人来做实际的工作,所以实现接口并填充你的方法和属性。

定义一个你想引发事件的接口

如果你想为 COM 客户端引发事件,这些事件(或者更准确地说,事件处理程序)也必须以接口的形式声明。幸运的是,你不需要深入研究 COM 的事件模型或处理 ConnectionPoint 细节,框架会为你处理这些。

使用类属性将它们组合在一起

这是内部的实现细节。通过在类上分配 GUID,使用 ClassInterfaceAttribute 作用于你的类,以及使用 InterfaceTypeAttribute 作用于你的接口,来告诉框架你的类暴露了一个 COM 接口。如果你想要事件,那么也为你的事件接口分配一个 InterfaceTypeAttribute,并指定一个 ComSourceInterfacesAttribute 来告诉 COM 该接口的用途。这个属性有几个带字符串参数的构造函数,但使用它们也很容易出错,因为 .NET 对这些类型的格式要求非常严格,所以尽可能地,你应该使用接收 Type 参数的构造函数,例如:

[ComSourceInterfaces(typeof(IComClassEvents))]

另一件重要的事情是,为每一个发布的**方法、属性和事件处理程序**添加一个 DispIdAttribute,否则它们将无法按预期工作。

我天生懒惰,难道没人能为我做这个吗?

以上步骤如果每次都要手动完成,会很容易出错。VS.NET 有创建代码的向导概念,为什么不用呢?

为了对抗 VB.NET 在 COM 类向导方面的优势,我深入研究了一下这些向导是如何工作的,以及如何添加自己的向导。一旦找到了要修改的正确文件,这其实相当容易。

不深入细节的话,大多数创建类的向导都使用一个模板文件,其中包含最终类的大部分代码。然后,这个模板会被一个 JavaScript 脚本复制并修改,以插入正确的类名或命名空间名等。此外,项目选项也可以被修改(我用它将“注册 COM 互操作”标志设置为 true,用于接收新 COM 类的类库)。

例如,我新的 C# COM 类的模板的类声明如下:

public class [!output CLASS_NAME] : [!output INTERFACE_NAME]

方括号中的部分随后会被 JavaScript 文件中创建的变量值替换,从而得到一个有效的类声明,前提是你提供了正确的参数。

public class ComClass1 : IComClass1

我也想要这个,可以吗?

要自己尝试一下,只需使用我在本文开头提供的安装包链接。这是一个 MSI 包,它会搜索你的 VS.NET 2003 安装路径,然后安装新的向导。

安装完成后,你可以在 C# 类库项目中创建一个新的 COM-Class 模板。

免责声明:尽管我在我的电脑上测试了该安装包,但我无法保证它会在你的电脑上正常工作,或者不会以任何方式损害你的电脑。如果你安装了该包,之后你的电脑爆炸了,我概不负责!

该安装包应该可以在 VS.NET 2003 的德语和英语安装版本上工作。如果你安装的是其他语言版本,那么你可能需要在相应语言代码的子目录下创建模板文件和脚本的副本。

向导文件可以在 <VS.NET 2003 根目录>\VC#\VC#Wizards\CSharpComClass 找到,其中在 Scripts and Templates 子目录下的 1031(德语)或 1033(英语)子目录中,分别对应德语和英语。

我也将类模板、JavaScript 文件和向导定义文件(*.vsz*.vsdir)作为本文的“源代码”包含在内(链接在开头),所以如果你不信任我的安装程序(或者它对你不起作用),你可以使用这些文件自己添加新的模板。

结论

如果你遵循了这些步骤,那么 COM 互操作实际上并不难。只需记住,你测试类的环境也会影响结果。我花了一个几乎完整的周末在思考为什么我的测试类触发的事件(在我调用了类的一个函数几秒钟后)在脚本环境中收到了,但在 VBA 客户端中(似乎)没有收到,尽管其他一切都正常。最后,我发现接收事件的对象在事件引发之前就已经被销毁了 :-(。

如果你喜欢这篇文章和新向导,别忘了投票!

版本历史

  • 2004 年 7 月 13 日 - 初始发布。
  • 2004 年 8 月 14 日 - 更新至 1.0.1。
    • 将已发布类接口的接口类型更改为 ComInterfaceType.InterfaceIsDual,以便也支持早期绑定。
    • 将建议的 DispId 从 0 更改为 1。
    • 类模板文件中的少量格式更改。

祝您编码愉快,

mav

© . All rights reserved.