通用 COM 可调用包装器
允许在任何支持 COM 的编程或脚本语言中使用大多数 .NET 类
引言
这是一个通用的 COM 可调用包装器 (CCW),适用于任何 .NET 对象、类或类型。它是一个与语言无关的 COM 类库,可以将大多数 Microsoft .NET Framework 类和类型虚拟化移植到任何支持 COM 的语言中。在 Windows 操作系统上,如果您的首选语言至少能够与 COM 配合使用,那么理论上通过此库,您就可以使用任何 Microsoft .NET 类、类型和对象。它使用 VB 编写,使用普通的文本编辑器即可编译,并可使用 Microsoft 的 VB 编译器进行编译。
编译后的 DLL 可注册为 COM 类,使其在任何支持 COM 的语言中可用。底层实现上,通用功能通过标准的运行时调用和声明方法来实现。反射被用来在运行时加载程序集,并在 `Universal_CCW_Factory` 类中创建您声明的类型的包装对象和静态类。标准的 `CallByName()` 函数在 `Universal_CCW_Container` 类中使用,用于操作包装对象的属性和方法。更多的反射方法在 `Universal_CCW_Container` 类中使用,用于操作包装的 `static` 类型的字段、属性以及调用其方法。对于事件:`Universal_CCW_Container` 包装类中的匿名子程序充当通用的 Delegate/事件处理方法。事件处理本身并不新颖:匿名子程序只是将触发事件的信息复制到一个主 `Universal_CCW_Factory` 主对象的 Queue 类型属性中。
关于事件,目前仅限于 `EventHandler` 和 `ComponentModel.CancelEventHandler` 处理程序类型(或任何可以轻松向下转换为这些类型的类型)。这是因为我还没有找到在运行时强制设置处理程序类型的方法。关于 .NET 类中可能引起内部循环的某些特性,例如 `Application` 类的 `Run` 方法:对象和类存在于 VB.NET 捕获的环境中,这意味着您将无法获知任何内部处理过程。由底层对象或类的任何方法触发的内部循环将导致您的项目冻结。不存在“`main`”子程序。因此,如果您的项目需要此类专门处理,您应考虑制作一个专用包装器。
下面的窗体截图展示了在 PHP 命令行脚本下使用此库可以实现的功能。创建了一个带有 `textbox` 和 `button` 控件的 Windows 窗体,监控 `button` 点击事件,并集成了一个颜色选择器对话框,将所选颜色的十进制值打印回文本框。

注意:代码及其任何实现均遵循 MIT 许可。虽然功能齐全,但由于底层语言或实现的许可限制以及固有的安全漏洞,此代码仅用于教育目的。但我除了延续 MIT 许可外,不对其使用施加任何限制。有关完整的版权声明,请参阅源代码。由于此库允许无限制地使用 .NET Framework,因此它可能存在危险,尤其是在安装此库的情况下使用 Internet Explorer。使用此代码时不提供任何保证或承诺。请通过电子邮件 four.zero.one.unauthorized@gmail.com 联系我,以报告任何错误或了解更多信息。Microsoft 与本项目无关,请勿联系他们寻求与使用 `Universal_CCW` 库相关的支持。有关更多详细信息和高级示例,包括在 PHP 中处理事件,请 访问 Sourceforge 项目网站。
背景
大多数 .NET 类默认似乎不启用 COM 或未注册 COM。在其他任何语言中使用这些类可能会遇到困难。可能需要为每个能想象到的 .NET 类开发自定义 COM 类包装器。事件处理更是无法实现。互联网上普遍的建议,包括微软的建议,都是“制作一个包装器”。对我来说,这似乎是个荒谬的想法:为每一种可想到的情况制作一个专门的包装器,考虑到成百上千的 .NET 类,这是一项艰巨的任务。因此,需要一个通用的包装器。对于尚未完全支持 .NET Framework 的语言(例如 PHP 无法转换某些值),或者如果大多数 .NET 类和程序集因某种奇怪的原因无法被通常支持 COM 或 .NET 的语言访问,那么这个库就是为你准备的。
使用此代码
在新的 Universal_CCW 命名空间下提供了两个新的已注册 COM 的类
Universal_CCW_Factory
:COM 可访问的工厂,持有事件队列、使用的程序集注册表,并生成新的容器对象Universal_CCW_Container
:由 `Universal_CCW_Factory` 生成的 COM 可访问的容器对象。它充当您的语言与 .NET 对象或类之间的中间件/包装器对象,提供 get、set、call 和事件订阅功能
Start
- 在 COM 中注册编译后的 DLL 和生成的 TLB 文件。您可以使用 Regasm.exe COM 注册新类。如果您的语言仅限于 32 位平台,您必须以 32 位编译和注册。
- 使用特定于您语言的 CreateObject 函数来创建 `Universal_CCW.Universal_CCW_Factory` 的新实例。
- 了解您将要使用的类的完整 .NET 程序集名称和完整类名称,使用 `Universal_CCW_Factory.New_Object("任何唯一的 ID/名称", "程序集名称", "完整类名称")` 来包装对象,或使用 `Universal_CCW_Factory.New_Static("程序集名称", "完整类名称")` 来包装 `static` 类。这将返回一个新的 `Universal_CCW_Container` 对象,它充当 .NET 对象/类的包装器。
属性
如果您正在处理一个对象,请使用 `Universal_CCW_Container` 包装对象的以下方法来与底层对象的属性进行交互
- `Universal_CCW_Container.Get_Property_Value("属性名称")`:返回包装对象命名属性的值。如果通常应该返回一个对象(例如控件),您将收到一个包装该返回对象的新的 `Universal_CCW_Container` 对象。
- `Universal_CCW_Container.Set_Property_Value("属性名称", "新值")`:将包装对象的属性设置为给定的新值。如果“新值”恰好是一个 `Universal_CCW_Container` 对象,它将被自动提取并作为新值进行分配。
如果您正在处理一个 `static` 类,请使用 `Universal_CCW_Container` 包装对象的以下方法来与底层 `static` 类的属性和字段进行交互
- `Universal_CCW_Container.Get_Static_Member_Value("字段/属性", "属性名称")`:“字段/属性”是一个字符串,其值为“`field`”或“`property`”,用于指定您要查找的成员类型(不可否认,这部分可以改进)。返回包装的 `static` 类命名属性或字段的值。如果通常应该返回一个对象(例如控件),您将收到一个包装该返回对象的新的 `Universal_CCW_Container` 对象。
调用方法
如果您正在处理一个对象,请使用 `Universal_CCW_Container` 包装对象的以下方法来调用其方法
- `Universal_CCW_Container.Call_Method("方法名称", "参数数组")`:“参数数组”是一个一维数组,按顺序包含底层方法所需的参数。返回包装对象命名方法的执行结果。如果“参数数组”中的任何项恰好是一个 `Universal_CCW_Container` 对象,则它将被自动提取并作为新值进行分配。如果通常应该返回一个对象(例如控件),您将收到一个包装该返回对象的新的 `Universal_CCW_Container` 对象。
如果您正在处理一个 `static` 类,请使用 `Universal_CCW_Container` 包装对象的以下方法来调用其方法
- `Universal_CCW_Container.Call_Static_Method("方法名称", "参数数组")`:“参数数组”是一个一维数组,按顺序包含底层方法所需的参数。返回包装的 `static` 类命名方法的执行结果。如果“参数数组”中的任何项恰好是一个 `Universal_CCW_Container` 对象,则它将被自动提取并作为新值进行分配。如果通常应该返回一个对象(例如控件),您将收到一个包装该返回对象的新的 `Universal_CCW_Container` 对象。
事件
`Universal_CCW_Container.Subscribe_To_Event("事件名称")` 将导致您指定的类型的所有事件被排队到主 `Universal_CCW_Factory` 事件队列中。底层实现上,此方法基本上为指定的事件类型创建了一个或多或少通用的委托和事件处理程序。
查看 `Universal_CCW_Factory.Pending_Message_Count` 属性以监控队列中的待处理事件数。使用 `Universal_CCW_Factory.Consume_Message()` 方法来拉取事件队列中的第一个事件。注意:`Consume_Message()` 返回一个 Hashtable(`source`=>“容器对象 ID”,`event`=>事件名称,`args`=>EventArgs)。注意:`EventArgs` 是一个对象,因此它将自动包装在一个新的 `Universal_CCW_Container` 对象中。请参阅上面的说明,了解如何操作底层事件对象的属性和方法。
示例
PHP
以下示例仅在主机系统上发出蜂鸣声。仅用于命令行使用。注意: `PublicKeyToken` 和其他 .NET 程序集信息可能因系统而异...
<?php
// load the main Universal_CCW_Factory COM class
$COM = new COM("Universal_CCW.Universal_CCW_Factory");
$asmb_full_name = 'Microsoft.VisualBasic, Version=8.0.0.0, Culture=neutral, ' .
'PublicKeyToken=b03f5f7f11d50a3a';
$class_name = 'Microsoft.VisualBasic.Interaction';
// create a new static wrapper for the Microsoft.VisualBasic.Interaction class
$vb_static = $COM->New_Static($asmb_full_name, $class_name);
// beep
$vb_static->Call_Static_Method("Beep");
?>
JavaScript
以下 JavaScript 示例在用户访问网页时,会在客户端计算机上创建一个弹出式 Windows 窗体。客户端必须安装 `Universal_CCW` 库和 .NET Framework。由于它使用 `ActiveXObject()` 函数,因此仅在 Internet Explorer 中有效,除非客户端安装了第三方 ActiveX 插件(可能 这个可能有效)。注意: `PublicKeyToken` 和其他 .NET 程序集信息可能因系统而异。
是的,这给了网站对客户端计算机的完全控制权,但让我们暂时忽略安全问题。
<html>
<head>
<script type="text/javascript">
function create_form() {
// create a new main Universal_CCW_Factory COM object instance
var main = new ActiveXObject("Universal_CCW.Universal_CCW_Factory");
// create and wrap a new Form object
var form = main.New_Object("form1", "System.Windows.Forms, Version=2.0.0.0, " +
"Culture=neutral, PublicKeyToken=b77a5c561934e089", "System.Windows.Forms.Form");
form.Set_Property_Value("Text", "A Blank Form");
// create and wrap a new Textbox object, and set its Parent to the form object
var textbox = main.New_Object("txt1", "System.Windows.Forms, Version=2.0.0.0, " +
"Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.Windows.Forms.TextBox");
textbox.Set_Property_Value("Parent", form);
textbox.Set_Property_Value("Height", 200);
textbox.Set_Property_Value("Width", 200);
textbox.Set_Property_Value("Multiline", true);
// cause the form object to become visible
form.Call_Method("Show");
}
</script>
</head>
<body onload="create_form();">
test
</body>
初步结果...
