Interop 选择
在 Visual Studio .NET 2003 的所有基于 .NET 框架的语言中,Visual C++ 7.1 提供了最好的 interop 功能。它具有实现实际的 interop 方案所必需的功能,Quake II 到 .NET 框架的移植便是例证,具体细节请访问http://www.vertigosoftware.com/Quake2.htm。Visual C++ 2005 进一步扩展了这一功能。
在托管与本机环境中,使用 .NET Interop 有四种主要途径:COM interop 可以使用 Runtime Callable Wrappers (RCW) 与 COM Callable Wrappers (CCW) 来实现。.通用语言运行库 (CLR) 负责类型封送(除非在极少的情况下使用自定义封送拆收器),并且这些调用的开销很大。您需要非常小心地尽量避免接口往来过于频繁,否则就会出现很严重的性能问题。您还需要保证这些包装一直与其底层的组件保持一致。也就是说,在您因简单的 Interop 场景而试图使用大量的本机 COM 代码时,COM Interop 非常有用。
第二种 Interop 选择是使用 P/Invoke。要达到此目的,可以使用 DLLImport 属性,并且在方法声明中为您想要导入的函数指定属性。封送是按照它在声明中的指定方式来处理的。然而,只有在您有代码需要通过 DLL 导出公开必需的功能时,DLLImport 才是有用的。
当您需要从本机代码调用托管代码时,CLR 宿主也是一种选择。在这种情况下,本机应用程序必须驱动所有的执行:设置主机、绑定到运行库、启动主机、检索适当的 AppDomain、设置调用上下文、查找所需的程序集和类,并调用所需类上的操作。在控制发生什么以及何时发生方面,这无疑是最健壮的解决方案之一,但这也会带来让人难以置信的枯燥,并需要许多自定义代码。
第四种选择,也有可能是最简单并最可行的选择,就是使用 C++ 的 Interop 功能。通过设置 /clr 开关,编译器会生成 MSIL 而不是本机代码。唯一被生成为本机代码的是那些无法被编译成 MSIL 的代码,其中包括带有内联 asm 块的函数,以及使用像 Streaming SIMD Extensions (SSE) 这样一些特定于 CPU 的固有特性的操作。Quake II 就是使用 /clr 开关移植到 .NET 的。Vertigo 软件小组花费了一天的时间将原来由 C 编写的游戏代码成功地编译成 C++ 代码,然后设置了 /clr 开关。他们的代码很快就可以运行在 .NET 框架上。在不添加任何附加的二进制文件而只是简单地加入适当的头文件的情况下,托管 C++ 和本地 C++ 可以相互调用,而无需部分开发人员做一些额外的工作。编译器负责创建适当的 thunk 来往返在两种环境之间。
这给 C++ 开发人员带来了一些问题。问题之一就是现在声名狼籍的混合 DLL 加载问题,Visual Studio .NET 2002 和 Visual Studio .NET 2003 的用户都受此问题的影响。如果您正在运行加载器锁内的本机代码并且引用一个还没有加载的程序集中的托管类型,CLR 会非常友善地帮您加载这一程序集。它是通过调用 LoadLibrary 来实现的。当然,LoadLibrary 会尝试获得加载器锁,这会使您碰到死锁问题。开发人员和产品经理如果听说这个问题在即将推出的版本中会得到解决一定非常高兴。
/clr 开关对 C++ 开发人员来说是一个极好的工具,但它也有一些缺点。正如我之前提到的一样,由 /clr 开关产生的映像既包含本机代码又包含托管代码,这有时会导致问题的出现。首先,这些混合映像并不是遵循 CLI 的(这意味着,例如,它们将无法在 Rotor 上运行)。它们有本机的入口点,而当您频繁跨越托管边界时会带来极大的转换开销。但最重要的是,这些本机入口点的存在会对使用包括反射在内的程序集的工具带来极大的危害。为了使用反射来检查一个映像,必须首先加载程序集并执行它。只有在所有的初始化都执行完毕时,反射才能检查元数据。遗憾的是,反射无法正确地加载包含有本机入口点的托管程序集。
此外,Visual Studio .NET 2003 很少生成可验证的代码,即使它这样做,它花费在处理一些其他重要问题上的时间也会比较多。而 MSIL 对无法验证的指令有着一流的支持(您可以进行指针算术运算,执行间接加载和访问本机堆),可验证的代码使您能够处理一些需要部分信任的情况,而这又可以支持 Visual Studio 2005 提供一些丰富功能。ClickOnce 部署依赖于部分信任,与 SQL Server 2005 中的托管代码宿主一样。Visual C++ 2005 开发小组的主要目标之一就是让编译器能够在开发人员开发非混合和可验证的映像产品时有所帮助。它们通过引入两个新的编译器开关来实现这一点:/clr:pure 和 /clr:safe。不过,在我深入讲解如何使用这些新开关之前,我需要分析一下 C++ Interop 函数的工作原理。
评论