在程序设计领域微软技术一直引领着每个时代,在每一代的技术背后都有相关的动机,而这些动机及其实现细节往往是大部分的程序设计课程没有涉及的,但是对于我们理解相关的技术又十分重要,本文将对几种相关技术和隐藏在动机背后的细节做简要的阐述,算是抛砖引玉。 .NET互操作技术主要分为3种,P/Invoke,C++ Interop,COM Interop,其中P/Invoke 主要用于调用C库函数和Windows API。C++ Interop则主要用于Managed C++调用 C++类库和核心算法库,它甚至允许托管代码和非托管代码在同一个文件中。 COM Interop主要包括正向的RCW和反向的CCW。下面以一个简单的例子对互操作中比较重要的数据封送进行简单的介绍。 如何封送字符串(P/Invoke方式) 假设非托管代码定义如下: void _cdecl stringMarshal( const wchar_t* inString, wchar_t* outString, int buffersize) { If(NULL != inString) { wcscpy_s(outString, buffersize, inString); } } 这段代码编译生成的文件名为:stringMarshal.dll 在托管代码中其托管定义如下: [DllImport (“stringMarshal.dll”, CharSet = CharSet.Unicode, CallingConvention =CallingConvention.Cdecl) ] public extern static void stringMarshal (string inString, StringBuilder outString, int bufferSize);
这里有几点需要注意: 1. 在声明函数时必须要用extern修饰符,目的是为了告诉编译器此函数是外部实现的,没有方法体,因此不需要在托管代码中搜索这个函数。 2. 在声明函数时必须要用static修饰符,原因是非托管的DLL导出的非托管方法都是可以直接调用的,无需对相关的类进行实例化,大部分情况下根本就不存在类。 3. 因为非托管代码中的字符串为wchar_t*类型,所以CharSet需要设置为CharSet.Unicode。 4. 因为非托管代码的调用方式为_cdecl, 所以托管部分的CallingConvention需要设置为CallingConvention.Cdecl,另外这种类型的调用方式是调用方负责处理堆栈,所以支持可变类型参数函数例如printf()的互操作。 5. 输入字符串需要封装为string是因为这个字符串属于固定字符串,互操作过程中不需要变化,而输出字符串则需要封装为StringBuilder,因为这种字符串默认为具有IN/OUT属性,其内容可变,而且当字符串经常需要变化时效率高。
托管代码中调用非托管代码方式如下: private static void TestStringMarshal () { string inString = "Wally input test string."; int bufferSize = inString.Length; StringBuilder strbd = new StringBuilder(bufferSize); stringMarshal (inString, strbd, bufferSize + 1); Console.WriteLine("Wally Input string: {0}", inString); Console.WriteLine("Wally output string: {0}", strbd.ToString()); }
总结 本文简要的介绍了.NET托管代码和本地非托管代码的互操作技术,并对数据封送的实现细节做了简单的说明,希望对大家的技术提高有所帮助,算是抛砖引玉,期待大家在这方面写出更多更好的文章。 |