C#互操作性入门系列(一):C#中互操作性介绍

本专题概要:
  • 引言
  • 平台调用
  • C++ Interop(互操作)
  • COM Interop(互操作)
一、引言
  这个系列是在C#基础知识中遗留下来的一个系列的,因为在C# 4.0中的一个新特性就是对COM互操作改进,然而COM互操作性却是.NET平台下其中一种互操作技术,为了帮助大家更好的了解.NET平台下的互操作技术,所以才有了这个系列。然而有些朋友们可能会有这样的疑问——“为什么我们需要掌握互操作技术的呢?” 对于这个问题的解释就是——掌握了.NET平台下的互操作性技术可以帮助我们在.NET中调用非托管的dll和COM组件。.NET是建立在操作系统的之上的一个开发框架,其中.NET 类库中的类也是对Windows API的抽象封装,然而.NET类库不可能对所有Windows API进行封装,当.NET中没有实现某个功能的类,然而该功能在Windows API被实现了,此时我们完全没必要去自己在.NET中自定义个类,这时候就可以调用Windows  API 中的函数来实现,此时就涉及到托管代码与非托管代码的交互,此时就需要使用到互操作性的技术来实现托管代码和非托管代码更好的交互。.NET 平台下提供了3种互操作性的技术:
  1. Platform Invoke(P/Invoke),即平台调用,主要用于调用C库函数和Windows API
  2. C++ Introp, 主要用于Managed C++(托管C++)中调用C++类库
  3. COM Interop, 主要用于在.NET中调用COM组件和在COM中使用.NET程序集。
下面就对这3种技术分别介绍下。
二、平台调用
  使用平台调用的技术可以在托管代码中调用动态链接库(Dll)中实现的非托管函数,如Win32 Dll和C/C++ 创建的dll。看到这里,有些朋友们应该会有疑问——在怎样的场合我们可以使用平台调用技术来调用动态链接库中的非托管函数呢?
  这个问题就如前面引言中说讲到的一样,当在开发过程中,.NET类库中没有提供相关API然而Win32 API 中提供了相关的函数实现时,此时就可以考虑使用平台调用的技术在.NET开发的应用程序中调用Win32 API中的函数;
  然而还有一个使用场景就是——由于托管代码的效率不如非托管代码,为了提高效率,此时也可以考虑托管代码中调用C库函数。
2.1 在托管代码中通过平台调用来调用非托管代码的步骤
(1).  获得非托管函数的信息,即dll的名称,需要调用的非托管函数名等信息
(2). 在托管代码中对非托管函数进行声明,并且附加平台调用所需要属性
(3). 在托管代码中直接调用第二步中声明的托管函数
2.2 平台调用的调用过程
(1)  查找包含该函数的DLL,当需要调用某个函数时,当然第一步就需要知道包含该函数的DLL的位置,所以平台调用的第一步也就是查找DLL,其实在托管代码中调用非托管代码的调用过程可以想象成叫某个人做事情,首先我们要找到那个人在哪里(即查找函数的DLL过程),找到那个人之后需要把要做的事情告诉他(相当于加载DLL到内存中和传入参数),最后让他去完成需要完成的事情(相当于让非托管函数去执行任务)。
(2) 将找到的DLL加载到内存中。
(3) 查找函数在内存中的地址并把其参数推入堆栈,来封送所需的数据。CLR只会在第一次调用函数时,才会去查找和加载DLL,并查找函数在内存中的地址。当函数被调用过一次之后,CLR会将函数的地址缓存起来,CLR这种机制可以提高平台调用的效率。在应用程序域被卸载之前,找到的DLL都一直存在于内存中。
(4) 执行非托管函数。
平台调用的过程可以通过下图更好地理解:
三、C++ Interop
  第二部分主要向大家介绍了第一种互操作性技术,然后我们也可以使用C++ Interop技术来实现与非托管代码进行交互。然而C++ Interop 方式有一个与平台调用不一样的地方,就是C++ Interop 允许托管代码和非托管代码存在于一个程序集中,甚至同一个文件中。C++ Interop 是在源代码上直接链接和编译非托管代码来实现与非托管代码进行互操作的,而平台调用是加载编译后生成的非托管DLL并查找函数的入口地址来实现与非托管函数进行互操作的。C++ Interop使用托管C++来包装非托管C++代码,然后编译生成程序集,然后再托管代码中引用该程序集,从而来实现与非托管代码的互操作。 关于具体的使用和与平台调用的比较,这里就不多介绍,我将会在后面的专题中具体介绍。
四、COM Interop
  COM(Component Object Model,组件对象模型)是微软之前推荐的一个开发技术,由于微软过去十多年里面开发了大量的COM组件,然而不可能在使用.NET技术重写这些COM组件实现的功能,所以为了解决在.NET中的托管代码能够调用COM组件的问题,.NET 平台下提供了COM Interop,即COM互操作技术,COM Interop不仅支持在托管代码中使用COM组件,而且还支持想CMO组件功能托管对象。下面就这两种支持分别做一个介绍。
4.1 在.NET中使用COM组件
在.NET中使用COM对象,主要有3种方法:
      1. 使用TlbImp工具为COM组件创建一个互操作程序集来绑定早期的COM对象,这样就可以在程序中添加互操作程序集来调用COM对象
      2. 通过反射来后期绑定COM对象
      3. 通过P/Invoke创建COM对象或使用C++ Interop为COM对象编写包装类
但是我们经常使用的都是方法一,下面介绍下使用方法一在.NET 中使用COM对象的步骤:
  1. 找到要使用的COM 组件并注册它。使用 regsvr32.exe 注册或注销 COM DLL。                
  2. 在项目中添加对 COM 组件或类型库的引用。                
添加引用时,Visual Studio 会用到Tlbimp.exe(类型库导入程序),Tlbimp.exe程序将生成一个 .NET Framework 互操作程序集。该程序集又称为运行时可调用包装 (RCW),其中包含了包装COM组件中的类和接口。Visual Studio 将生成组件的引用添加至项目。
     3. 创建RCW中类的实例,这样就可以使用托管对象一样来使用COM对象。
下面通过一个图更好地说明在.NET中使用COM组件的过程:
4.2 在COM中使用.NET程序集
  .NET 公共语言运行时通过COM可调用包装(COM Callable Wrapper,即CCW)来完成与COM类型库的交互。CCW可以使COM客户端认为是在与普通的COM类型交互,同时使.NET组件认为它正在与托管应用程序交互。在这里CCW是非托管COM客户端与托管对象之间的一个代理。 CCW既可以维护托管对象的生命周期,也负责数据类型在COM和.NET之间的相互转换。实现在COM使用.NET 类型的基本步骤如:
1. 在C#项目中添加互操作特性
 可以修改C#项目属性使程序集对COM可见。右键解决方案选择属性,在“应用程序标签”中选择“程序集信息”按钮,在弹出的对话框中选择 “使程序集COM可见” 选项,如下图所示:
2. 生成COM类型库并对它进行注册以供COM客户端使用
在“生成”标签中,选中 “为COM互操作注册”选项,如下图:
勾选“为COM互操作注册”选项后,Visual Studio会调用类型库导出工具(Tlbexp.exe)为.NET程序集生成COM类型库再使用程序集注册工具(Regasm.exe)来完成对.NET程序集和生成的COM类型库进行注册,这样COM客户端可以使用CCW服务来对.NET对象进行调用了
五、总结

  介绍到这里,本专题的内容就结束,本专题主要对.NET 提供的互操作的技术做了一个总的概括,在后面的专题中将会对具体的技术进行详细的介绍和给出一些简单的使用例子。