基于.net开发,托管的便利好处自然不用再多言,垃圾回收、内存管理等等,加之强大的FCL类库作支持后盾,一般情况下我们都不会直接用到非托管代码,一些常用的底层api都已经被FCL类库进行了很好的封装,我们只需要知道用到哪一个类即可。
但是类库虽然强大,却非万能的,总有一些基于底层的api没有被封装,或者说程序要调用一些第三方的接口,一般都是c/c++的dll。
在这种情况之下,我们便要考虑到托管代码对于非托管代码的调用问题了,这种技术称之为互操作性技术。
在.net平台下提供了三种互操作的实现方式:
- Platform Invoke(P/Invoke),即平台调用,主要用于调用C库函数和Windows API
- C++ Interop, 主要用于Managed C++(托管C++)中调用C++类库
- COM Interop, 主要用于在.NET中调用COM组件和在COM中使用.NET程序集。
1、平台调用:
要想实现平台调用,需要明确以下信息:非托管dll文件的名称、函数名称以及参数详细信息。然后,在托管代码之中声明调用非托管的函数,最后即可调用。如下面代码便是一个典型的C#的平台调用例子:
#region P/Invoke 设置本地时间
//声明dll名称以及函数信息和参数信息,注意,如果dll名称非全路径,则默认在C盘system32文件夹路径下,或者当前程序运行路径之下进行寻找
[DllImport("kernel32.dll")]
private static extern bool SetLocalTime(ref SYSTEMTIME time);
[StructLayout(LayoutKind.Sequential)]
private struct SYSTEMTIME
{
public short year;
public short month;
public short dayOfWeek;
public short day;
public short hour;
public short minute;
public short second;
public short milliseconds;
}
/// <summary>
/// 设置本地计算机时间
/// </summary>
/// <param name="dt">DateTime对象</param>
public static void SetLocalTime(DateTime dt)
{
SYSTEMTIME st;
st.year = (short)dt.Year;
st.month = (short)dt.Month;
st.dayOfWeek = (short)dt.DayOfWeek;
st.day = (short)dt.Day;
st.hour = (short)dt.Hour;
st.minute = (short)dt.Minute;
st.second = (short)dt.Second;
st.milliseconds = (short)dt.Millisecond;
SetLocalTime(ref st);
}
#endregion
2、C++ Introp调用:
通过C++ Introp调用的方式,允许非托管代码与托管代码处于一个程序集之内,甚至同一份源代码之中。该调用方式是在源代码上直接链接和编译非托管代码来实现与非托管代码进行互操作的。C++ Interop使用托管C++来包装非托管C++代码,然后编译生成程序集,然后在托管代码中引用该程序集,从而来实现与非托管代码的互操作。
3、COM Interop调用:
COM(Component Object Model,组件对象模型)是微软之前推荐的一个开发技术,过去微软使用该技术进行了许多功能组件的开发,如果把这些已经成型的COM组件用.net托管方式重做一遍,显然是没有意义且极为不现实的。那么想要在这种情况之下使用COM组件的功能,就需要一种托管代码对其的调用技术,就是COM Interop,COM Interop不仅支持在托管代码中使用COM组件,而且还支持向COM组件功能托管对象。
在.NET中使用COM对象,主要有3种方法:
- 使用TlbImp工具为COM组件创建一个互操作程序集来绑定早期的COM对象,这样就可以在程序中添加互操作程序集来调用COM对象。(简单的可以理解为用调用中间层的方式去调用COM组件)
- 通过P/Invoke创建COM对象或使用C++ Interop为COM对象编写包装类。
- 通过反射来后期绑定COM对象。
我们经常使用的是第一种方式,在VS开发工具中引用一个COM组件,需要这个组件已被注册(regsvr32.exe进行COM组件的注册或注销)。
被注册之后,有两种引用方式:你可以在.net项目的引用列表中进行右键添加引用,在COM组件选项页下选择你注册的组件进行引用。或者,如果你注册的是一个ActiveX组件,那么你可以在工具箱之中添加被注册的COM组件,然后将其拖动到程序窗体之上进行引用。在成功引用之后,VS会用到Tlbimp.exe工具来为该COM组件生成一个或多个互操作程序集,该程序集又称为运行时可调用包装 (RCW),其中包含了包装COM组件中的类和接口。Visual Studio 将被生的成组件的互操作的程序集引用添加至项目。 而这种被生成出来的互操作程序集都会以这样的格式命名:Interop.xxx.dll 或 AxInterop.xxx.dll,其中Interop.xxx.dll是一个单纯的组件包装程序集,而AxInterop.xxx.dll则包装了ActiveX控件,可以拖放在窗体上。另外需要注意的是,引用COM组件操作而生成的代理程序集文件名称,和你.net项目引用列表之中的引用名称可能会不一样,但你点击该引用会发现,确实是将引用映射到了生成的代理程序集文件之上。如同所示,这是引用一个COM组件而生成的两个互操作程序集:
这样的话,我们使用RCW互操作程序集就像使用一个普通托管对象一样。
同时,我们也可以将托管代码生成为可供COM组件进行操作的程序集,需要通过COM可调用包装(COM Callable Wrapper,即CCW),具体操作,需要对.net托管项目的属性进行相应的设置,勾选项目属性页里面的为COM互操作注册选项,使程序集对COM可见。经过设置之后,编译的过程就会变为:Visual Studio会调用类型库导出工具(Tlbexp.exe)为.NET程序集生成COM类型库再使用程序集注册工具(Regasm.exe)来完成对.NET程序集和生成的COM类型库进行注册,这样COM客户端可以使用CCW服务来对.NET对象进行调用了。
如需在别的电脑使用C#开发的COM组件,需要将dll文件与tlb文件复制到目标电脑,然后使用RegAsm.exe进行手动注册,RegAsm.exe注册工具可在.net平台文件夹路径下找到,如:C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe
将上述注册工具复制到要注册的COM组件的同级位置,然后使用cmd指令进行注册即可,语句:
先使用cd指令定位到COM组件目录,然后执行:RegAsm dll文件.dll /tlb:tlb文件.tlb