C# COM 对象供 JavaScript / HTML 使用,包括事件处理






4.92/5 (29投票s)
如何创建 C# COM 对象供 JavaScript / HTML 使用的完整示例,包括事件处理
引言
我希望能在 Web 浏览器中使用预先构建的 .NET 对象。在搜索了网络(包括 CodeProject)后,我发现一个可能的解决方案是创建一个 .NET COM 组件,并在 JavaScript 中使用 ActiveXObject 来与 COM 对象交互。通过 Google 搜索,我找到了许多有用的文章,但没有一篇能让我满意地展示如何用 C# 创建 COM 组件,如何通过 COM 暴露 .NET 事件,以及如何在 JavaScript 中使用 COM 对象。在本文中,我将把所有这些零散的知识点整合起来,展示如何实现一个完整的解决方案,在 JavaScript 中使用 C# COM 对象。
概述
本文介绍了如何创建一个 C# COM 对象(使用 Visual Studio .NET 2005),该对象可以在 Web 浏览器中的 JavaScript 中使用(仅在 Windows Vista 上的 IE 8 RC 上进行了测试)。文章提供了 COM 对象和演示如何使用该 COM 对象的简单网页的完整源代码,包括如何调用 COM 对象的方法,以及如何处理 COM 对象引发的 .NET 事件。
创建 C# COM 库
创建一个新的 C# 类库项目,名为 `MyComComponent`,并在项目的生成设置中启用“Register for COM interop”选项。如果您使用的是 Vista,在更改此设置后,您需要以管理员身份运行 Visual Studio 来生成您的项目。从项目中排除 `Class1.cs`。
向项目中添加一个名为 `MyComObject.cs` 的类,将访问修饰符更改为 `public`,并导入 `System.Runtime.InteropServices` 命名空间。
using System;
using System.Runtime.InteropServices;
namespace MyComComponent
{
public class MyComObject
{
}
}
添加用于定义将暴露给 COM 的操作和事件的接口。在 `MyComObject.cs` 文件中添加名为 `IComObject` 和 `IComEvents` 的空接口定义。
using System;
using System.Runtime.InteropServices;
namespace MyComComponent
{
public class MyComObject
{
}
public interface IComObject
{
}
public interface IComEvents
{
}
}
将 `MyComObject` 类以及 `IComObject` 和 `IComEvents` 接口暴露给 COM。首先,为类和接口添加 `[ComVisible(true)]` 属性。
using System;
using System.Runtime.InteropServices;
namespace MyComComponent
{
[ComVisible(true)]
public class MyComObject
{
}
[ComVisible(true)]
public interface IComObject
{
}
[ComVisible(true)]
public interface IComEvents
{
}
}
由于我们定义了自己的自定义 COM 接口,我们需要告诉类型库导出器不要为我们生成接口。要做到这一点,请为 `MyComObject` 类添加 `ClassInterface` 属性,其值为 `ClassInterfaceType.None`。
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class MyComObject
{
}
我们需要告诉类型库导出器 `IComEvents` 接口是 `IDispatch`,以便从我们的 COM 对象引发事件。要做到这一点,请为 `IComEvents` 接口添加 `InterfaceType` 属性,其值为 `ComInterfaceType.InterfaceIsIDispatch`。
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IComEvents
{
}
现在我们可以开始用接口定义来装饰 `MyComObject` 了。`IComObject` 接口的实现相对简单,只需显式实现即可。类型库导出器将自动为我们公开 `IComObject` 接口中的所有事件。
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public class MyComObject : IComObject
{
}
添加 `IComEvents` 接口略有不同。在 `MyComObject` 类上使用 `ComSourceInterfaces` 属性,其值为 `typeof(IComEvents)`。`ComSourceInterfaces` 属性用于定义作为 COM 事件源公开的接口列表。
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IComEvents))]
public class MyComObject : IComObject
{
}
从 COM 暴露的每个类型都有一个全局唯一标识符 (GUID)。由于我们尚未定义 GUID,类型库导出器将为我们完成此操作。但是,我们需要能够使用此 GUID 来加载类型,因此我们将现在为所有三个类型添加 GUID。请使用 guidgen 工具来完成此操作。
此时,我们已经具备了用于 COM 对象的代码结构,该对象显式实现了一个接口,并定义了一个事件源接口。此时的代码应该类似于:
using System;
using System.Runtime.InteropServices;
namespace MyComComponent
{
[Guid("4794D615-BE51-4a1e-B1BA-453F6E9337C4")]
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IComEvents))]
public class MyComObject : IComObject
{
}
[Guid("4B3AE7D8-FB6A-4558-8A96-BF82B54F329C")]
[ComVisible(true)]
public interface IComObject
{
}
[Guid("ECA5DD1D-096E-440c-BA6A-0118D351650B")]
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IComEvents
{
}
}
现在我们将向接口添加一些方法。向 `IComObject` 添加一个名为 `MyFirstComCommand` 的方法,该方法接受一个 `string` 参数并返回一个整数,以及一个名为 `Dispose` 的方法,该方法没有参数且没有返回值。
[Guid("4B3AE7D8-FB6A-4558-8A96-BF82B54F329C")]
[ComVisible(true)]
public interface IComObject
{
[DispId(0x10000001)]
int MyFirstComCommand(string arg);
[DispId(0x10000002)]
void Dispose();
}
向 `IComEvents` 添加一个名为 `MyFirstEvent` 的方法,该方法接受一个 `string` 参数且没有返回值。请注意 `event` 关键字的缺失。
[Guid("ECA5DD1D-096E-440c-BA6A-0118D351650B")]
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IComEvents
{
[DispId(0x00000001)]
void MyFirstEvent(string args);
}
剩下要做的就是在 `MyComObject` 类中实现方法和事件。`MyFirstCommand` 的实现只是引发 `MyFirstEvent` 事件并返回一个随机数。`Dispose` 方法显示一个消息框。将以下代码添加到 `MyComComponent` 类中,并向项目中添加对 `System.Windows.Forms.dll` 的引用。
[ComVisible(false)]
public delegate void MyFirstEventHandler(string args);
public event MyFirstEventHandler MyFirstEvent;
public int MyFirstComCommand(string arg)
{
if (MyFirstEvent != null)
MyFirstEvent(arg);
return (int)DateTime.Now.Ticks;
}
public void Dispose()
{
System.Windows.Forms.MessageBox.Show("MyComComponent is now disposed");
}
现在我们有了一个功能齐全的 COM 对象,用 C# 编写。COM 对象的代码应如下所示:
using System;
using System.Runtime.InteropServices;
namespace MyComComponent
{
[Guid("4794D615-BE51-4a1e-B1BA-453F6E9337C4")]
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IComEvents))]
public class MyComObject : IComObject
{
[ComVisible(false)]
public delegate void MyFirstEventHandler(string args);
public event MyFirstEventHandler MyFirstEvent;
public int MyFirstComCommand(string arg)
{
if (MyFirstEvent != null)
MyFirstEvent(arg);
return (int)DateTime.Now.Ticks;
}
public void Dispose()
{
System.Windows.Forms.MessageBox.Show("MyComComponent is now disposed");
}
}
[Guid("4B3AE7D8-FB6A-4558-8A96-BF82B54F329C")]
[ComVisible(true)]
public interface IComObject
{
[DispId(0x10000001)]
int MyFirstComCommand(string arg);
[DispId(0x10000002)]
void Dispose();
}
[Guid("ECA5DD1D-096E-440c-BA6A-0118D351650B")]
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IComEvents
{
[DispId(0x00000001)]
void MyFirstEvent(string args);
}
}
创建 COM 组件的实例
要在网页中使用 COM 组件,请在页面的 `
` 中嵌入一个 `` 标签。<object name="myComComponent" id="myComComponent"
classid="clsid:4794D615-BE51-4a1e-B1BA-453F6E9337C4" />
连接 COM 组件的事件
连接 COM 组件的事件有点棘手。您需要做的是在网页的 `
` 中嵌入一个 `` 标签,如下所示:<script for="myComComponent" event="MyFirstEvent(args)" language="javascript">
function myComComponent::MyFirstEvent(args) {
}
`for` 属性应设置为您的 COM 组件实例的名称。`event` 属性应设置为事件的 JavaScript 函数签名。JavaScript 函数的名称是 COM 组件的实例名称,后跟双冒号,再后跟事件的 JavaScript 签名。
在 COM 组件上执行命令
这是执行 `MyFirstComCommand` 的 JavaScript 代码:
var returnCode = myComComponent.MyFirstComCommand("Hello World!");
释放 COM 组件
在 Internet Explorer 8 中,COM 组件在 Web 浏览器完全关闭之前不会被销毁——关闭包含 COM 组件的标签页不会释放对象!如果用户不断刷新页面,COM 组件的新实例将被创建,但旧实例在 Web 浏览器关闭之前不会被释放。您可以使用 `onunload` 事件来检测页面何时卸载,然后释放您的 COM 组件。
在我们的示例中,我们添加的 `Dispose` 方法可用于此目的。只需更改 `Dispose` 方法的实现以清理 COM 组件使用的任何资源。此示例中的实现仅显示一个消息框,以便我们知道 `Dispose` 方法何时被调用。
整合起来
下面是一个网页的源代码,该网页创建 `MyComComponent` COM 对象的实例,在 COM 组件上执行方法,并监听来自 COM 组件的事件。
<html>
<head>
<title>My Com Component</title>
<object id="myComComponent" name="myComComponent"
classid="clsid:4794D615-BE51-4a1e-B1BA-453F6E9337C4"></object>
<script language="javascript" type="text/javascript">
function myButton_click() {
var returnCode = myComComponent.MyFirstComCommand(myArgs.value);
var msg = "myComComponent.MyFirstComCommand returned " + returnCode;
appendText(msg);
appendText("\r\n");
}
function setText(txt) {
myTextBox.value = txt;
}
function appendText(txt) {
myTextBox.value = myTextBox.value + txt;
}
function MyComComponent_onload() {
setText("");
myComComponent.MyFirstComCommand("Hi");
}
function MyComComponent_onunload() {
myComComponent.Dispose();
}
</script>
</head>
<body onload="MyComComponent_onload();" onunload="MyComComponent_onunload();">
<h1>My Com Component</h1>
<table>
<tr>
<td>
<button id="myButton" onclick="myButton_click();">Com Method</button>
<label>Args</label>
<textarea id="myArgs" rows="1" cols="16">Hello World!</textarea>
</td>
</tr>
<tr>
<td>
<textarea id="myTextBox" rows="10" cols="80"></textarea>
</td>
</tr>
</table>
<script for="myComComponent" event="MyFirstEvent(args)" language="javascript">
function myComComponent::MyFirstEvent(args) {
appendText("myComComponent raised MyFirstEvent. args: ");
appendText(args);
appendText("\r\n");
}
</script>
</body>
</html>
Using the Code
要使用示例网页,您首先需要编译 `MyComComponent` 解决方案文件。生成的程序集将在您生成解决方案时自动注册 COM Interop,因此您无需将程序集放在与网页相同的文件夹中。
生成解决方案后,在您的浏览器中打开网页 `MyComComponent.htm`(确保启用了 ActiveX 和 JavaScript)。
链接
- 使用 C# 和 .NET 创建 InfoPath 自定义控件
- 在 C# 中构建 COM 对象
- 公开 .NET 类库的 COM 接口以进行后期绑定
- 如何:在 Internet Explorer 脚本中接收托管的 C# 事件
- 如何在 JavaScript 中处理 ActiveX 事件
历史
- 2009 年 4 月 22 日 - 原始帖子