** COM interop ** ** 理论 ** ** / ** ** 实践 ** **
**
在 .NET 框架下,开发人员可以通过 COM interop tools 将 COM 组件导入导一个应用中去,一旦导入成功,那么我么就可以非常容易地调用 COM 接口给我们所提供的方法了。
A .NET Framework developer can incorporate COM components into a managed application by using COM interop tools to import the relevant COM types. Once imported, the COM types are ready to use.
一、 COM interop 概述 :
COM Interop 看上去象是介乎于 COM 和 .Net 之间的一条纽带,一座桥梁。为了保持向后兼容, COM Interop 可以使得 .Net 程序在不修改原有 COM 组件的前提下方便的访问 COM 组件。 这一点是非常重要的。事实上,全球的 COM 组件的代码量估计可能有数十亿行,拥有这些 COM 组件的公司不可能重写这些组件,所以 COM Interop 的存在为有此需求的开发者提供了很好的解决方案。
COM 和 .NET 之间存在着非常大的差异,为了使两者可以有机的结合在一起进行协同工作, COM Interop 中实际存在着 2 种桥接方式。一种是 RCW , Runtime Callable Wrapper ;另一种是 CCW , COM Callable Wrapper 。 RCW 是在运行时通过 CLR 从 Interop 装配件 (Interop Assembly) 的元数据中获取相关信息动态的实例化而得到的。使用者将感觉不到自己是在调用 COM 组件,一切都是这么的自然,和调用一个 .Net 组件没有任何区别。
需要注意的是,一个 COM 组件 ( 指的是一个实例,即一个 DLL 文件 ) 由且仅由一个 RCW 负责维护。那么这儿有一个问题了,对于一个 COM 组件的不同版本,是不是就会有不同的 RCW 与之相对应呢?答案是肯定的。那有些朋友会说, .Net 中的组件不是已经解决了 COM 中的“ DLL HELL ”问题了吗?按上面的说法,似乎并没有得到解决嘛?这儿我要说的是,在 .Net 中导入一个 COM 组件的不同版本,是会出现此类问题。解决此类问题的方法是使用 PIA(Primary Interop Assembly) 。
.Net 提供三种方法来导入一个 COM 组件
l 通过 Visual Studio .Net提从的“添加引用”功能
l 通过 tlbimp.exe 实现
l 使用 System.Runtime.InteropServices.TypeLibConverter 类编程
下面就分别介绍 COM 的封装、 HRESULTs and Exceptions 、继承、聚合和包容、如何运用 COM interop 来生成发出事件和处理事件以及 ** System.Runtime.InteropServices ** 命名空间几个重要的概念
1. COM 的封装 (COM Wrappers)
l 在一般的语言 ( 诸如 C++) 当中在客户端我们一定要控制该 COM 对象的生命周期
l 客户端的 COM 对象的方法在 C++ 中的调用通过产生该对象的实例同时获得该对象的接口指针,通过接口指针来访问该对象的方法。在 .NET 框架下则可以直接通过函数的映射来获得 ( Clients of .NET objects can obtain a description of an object's functionality using Reflection. )
l 在 .NET 框架下的运行环境中 .NET 可以在内存中为 .NET 重新对象分配内存使用。 ( NET objects reside in memory managed by the .NET Framework execution environment. )
为了解决上述问题, .NET 提供一个 COM Wrappers. 它可以使得 Managed Code 和 Unmanaged Code 可以很好结合在一起。 COM Wrappers 两种桥接方式 RCW (runtime callable wrapper) 和 CCW(COM callable wrapper) 其中 RCW 是将 Managed 的客户端与 Unmanaged 的服务器端联接起来的; CCW 是将 Unmanaged 的客户端与 Managed 的服务器端联接起来的。
2. HRESULTs and Exceptions
在 COM 编程中我们通过 HRESULT 来判断所做的操作是否成功,在 .NET 框架下我们通过抛不同的异常 (Throw Exceptions) 来捕捉错误。
注 MSDN 给我们列出了 HRESULT 不同值与 .NET 的不同异常的对照表。 (.NET Framework Developer’s Guide—HRESULTs and Exceptions)
3. 继承、聚合和包容 (Inheritance,Aggregation and Containment)
继承: .NET 提供一些标准的接口,用户在定义接口时,可以继承这些接口。
聚合: .NET 也支持 COM 提供的聚合的概念即,外对象将内对象的接口暴露在用户面前。
包容:
通过在外对象的构造函数中创建内对象的实例,这样客户端就可以通过该实例获得接口进行调用接口的各个方法。
4. 如何运用 COM interop 来生成发出事件和处理事件
在以下内容中,将要描述有关 COM 对象出接口与事件接收器的连接机制。
关于 COM 对象的出接口与事件接收器之间的连接机制与在描述 COM 原理与应用中的机制是一样的,即 COM 对象声明一个出接口,在事件接收器中表示该接口的实现方法。一旦, COM 对象与事件接收器的连接建立好以后,那么客户端就可以随时接收到 COM 对象服务器端的事件、消息。下面我们从 C# 的服务器端和事件接收器两个方面来描述这个问题。
Handling Events Raised by a COM Source( 描述 COM 源对象是如何产生一个事件的 )
Raising Events Handled by a COM Sink ( 通过接收器来处理事件 )
5. ** System.Runtime.InteropServices ** 命名空间
System.Runtime.InteropServices 是一个有关访问 COM 对象以及在 .NET 框架下的本地 API 函数。在创建 COM 接口时经常要运用这个命名空间。
二、 C# 接口编程
下面将从接口的定义、接口的访问、接口的实现以及接口的转换编程这些方面来阐述运用 C# 进行接口编程的方法。
1. 接口的定义
接口的声明:
[attributes] [modifiers] interface identifier [:base-list] {interface-body}[;]
l attributes (可选):附加的定义性信息 。
l · modifiers (可选):允许使用的修饰符有 new 和四个访问修饰符。分别是: new 、 public 、 protected 、 internal 、 private 。在一个接口定义中同一修饰符不允许出现多次, new 修饰符只能出现在嵌套接口中,表示覆盖了继承而来的同名成员。 The public, protected, internal, and private 修饰符定义了对接口的访问权限。
l 指示器和事件。
l identifier :接口名称。
l base-list (可选) : 包含一个或多个显式基接口的列表,接口间由逗号分隔。
l interface-body :对接口成员的定义。
l 接口可以是命名空间或类的成员,并且可以包含下列成员的签名: 方法、属性、索引器 。
l 一个接口可从一个或多个基接口继承。
接口的主体:
interface-body: { interface-member-declarationsopt }
接口可以包含一个和多个成员,这些成员可以是方法、属性、索引指示器和事件,但不能是常量、域、操作符、构造函数或析构函数,而且不能包含任何静态成员。接口定义创建新的定义空间,并且接口定义直接包含的接口成员定义将新成员引入该定义空间。
2. 接口的访问
C# 中的 CASTS 来代替 QueryInterface
( Using Casts Instead of QueryInterface )
在 C++ 中客户端需要通过 QueryInterface 来获得 COM 对象的接口指针。在 C# 编程中却不必这么麻烦。我们可以直接将 COM 对象对应到相应的 COM 接口上。如果我们在程序中对应错误,那么在运行时 C# 会抛出异常。
3. <