托管和非托管转换新方法:Marshaling Library(zz)html
http://hi.baidu.com/superql/blog/item/38e9c8073202fcc37a8947ac.htmlc++
1.VC++2008中新增长的库:Marshaling Libraryc#
咱们一块儿讨论一下VC++2008中引入的新库——Marshaling Library。在这个类库以前咱们使用的传统方法是固定指针(pin_ptr)。要使用Marshaling Library必须包含头文件<msclr/marshal.h>,使用命名空间msclr::interop,并使用marshal_as这个模板方法来执行转换,该模板方法须要两个参数:一是目标类型做为模板参数,另外一个就是这个方法的参数,就是要转换的对象。数组
若是你要把一个const wchar_t* 类型转换成String^,你能够这样写:数据结构
const wchar_t* source;函数
String^ dest = marshal_as<String^>(source);post
一些转换须要分配内存,并且必须随后删除。这样的转换须要一个叫作context的对象,这个对象在它再也不须要的时候就删除了。好比从一个托管的String^转换到本地的char*就须要一个context,由于在转换过程当中生成了String的一个临时的副本。代码以下:url
marshal_context context;spa
const wchar_t* = context.marshal_as<const wchar_t*>(str);指针
在这个marshaling库中预约义好了不少转换,大部分都是字符串的类型转换。你还能够根据本身的须要,自行扩展。若是有兴趣的话,能够参考MSDN。
2.利用Marshaling Library进行互操做与之前的对比
经过一些具体的实例来看一看Marshaling Library给咱们带来的便捷:
之前的状况:在此以前咱们是经过包含<vcclr.h>头文件,引用System::Runtime::InteropServices命名空间中的Marshal类的一些方法来进行类型的转换,这些方法一方面不容易记忆,另外一方面转换不直接也容易出错。好比作字符串在托管和非托管之间进行转换:
l 把托管字符串转换成ANSI字符串
String^ s = gcnew String("sample string");
IntPtr ip = Marshal::StringToHGlobalAnsi(s);
const char* str = static_cast<const char*>(ip.ToPointer());
l 把非托管ANSI字符串转换成托管字符串
void ManagedStringFunc(s) {
String^ ms = Marshal::PtrToStringAnsi(static_cast<IntPtr>(s));
}
这里的s是char*的类型,若是是const char* 的话,还须要先去掉字符串变量的常量性
const char* tempString = const_cast<char*>(s);
由于static_const没法将const char*转换成System::IntPtr。
l 转换Unicode字符串的方式与上面相似,在将const wchar_t*转换成String^类型的时候也要先去掉其常量性。
有了Marshal库之后:如今用Marshal库以后就能够marshal_as模板方法进行全部转换!
marshal_as模板方法就提供了直接将ANSI,Unicode字符串包括进行托管和非托管转换的方法,记得要包含头文件和引用命名空间。示例代码以下:
#include <msclr/marshal.h>
using namespace msclr::interop;
l 把ANSI字符串char* ch转换成托管字符串
String^ s = marshal_as<String^>(ch);
l 把常量ANSI字符串const char* ch转换成托管字符串
String^ s = marshal_as<String^>(ch);
注意:这里就再也不须要去除其常量性!
l 把托管字符串转换成非托管
marshal_context context;
return context.marshal_as<const char*>(str);
注意:这时就须要一个context上下文对象
l Unicode字符串在托管和非托管类型之间的转换和ANSI字符串的转换相似。
能够看出来,marshal_as不只更加方便,还提供了更多的支持类型好比BSTR 和System::String^,std::string和System::String^等等。这就大大提升了开发人员的效率,使得转换信手拈来。
若是想要扩展自定义类型,从本地到托管的话只须要实现下面一个模板方法便可:
namespace msclr {
namespace interop {
template<>
inline TO marshal_as<TO, FROM> (const FROM& from) {
// Insert conversion logic here, and return a TO parameter.
}
}
}
若是须要从托管类型转换到本地类型,也是须要实现一个模板类,有兴趣的话能够参考msdn,便不在此赘述。
int intdat1[] = {1,2,3,4,5};
array<int>^ gcdat1 = gcnew array<int>(5);
Marshal::Copy((IntPtr)intdat1,gcdat1,0,5);
char* str = "abcdef";
array<Byte>^ byteArray =gcnew array<Byte>(6);
Marshal::Copy((IntPtr)str,byteArray,0,6);
本文从数组的定义开始,介绍数组marshalling的三种方法,并对blittable类型等概念作进一步的讨论。
当托管代码须要和本地代码互操做时,咱们就进入了interop的领域。interop的场景形形色色,不变的是咱们须要把数据从一个世界marshal到另外一个世界。
在讨论数组marshalling以前,请各位和我一块儿思考一个问题,什么是数组?之因此要讨论这个问题,缘由在于不一样的术语在不一样的语境 中含有不一样的意 思。在使用c语言的时候,我认为数组就是一个指针。可是熟悉c#的朋友可能不一样意个人观点,数组是System.Array或者Object[]。我认 为,这两种回答都是出自语言领域的正确观点。那么若是有一个项目含有两个模块,一个用本地代码撰写,另外一个用托管代码撰写,二者之间的接口要求传递一个数 组,这个”数组”包含着怎样的语义呢?
我以为有两点是很重要的:
1. 如何访问数组元素。就比如c语言中的数组指针,c#中的数组引用,都是访问数组必不可少的线索。
2. 数组的大小。数组的大小不只仅是System.Array.Length。它还能够包括诸如数组的维数,每一个维上的启始边界和结束边界。
.NET在marshal数组的时候,很大程度上也是从以上两点出发,架起托管世界和本地代码之间的桥梁。根据操做的具体数据类型不一样,数组marshal又能够分为如下两个大类,三个小类,咱们分别介绍:
1. 数组做为参数传递
a) c/c++类型的数组
c类型的数组,也就是由指针指明存储空间首地址的数组,是一个自描述很 低的数据结构。尽管有些编译器支持在固定偏移量上写入数组的长度,可是由于各个编译 器处理的具体方法不一样,没有一个标准让CLR来参考。因此咱们在marshal一个c类型的数组的时候,不得不用其余方法考虑传递数组的大小,有如下两 种:
1) 约定指针数组长度
这种方法须要调用者和被调用者之间有一个约定,给出一个数组长度的固定值。在托管端声明一个interop方法的时候,只须要用SizeConst这个属性,把这个约定告诉CLR。CLR在进行Marshal的时候,会根据这个值,在本地堆上分配相应的空间。
2)经过一个额外的参数指定数组长度
可能有的朋友以为,约定一个静态的数组长度还不够灵活,但愿可以动态的传递数组的长度,从而使 得数组marshalling没必要受制于固定数组长度的限制。咱们先来看普通的函数调用是如何解决这个问题的:caller和callee经过约定一个额 外的参数,来传递数组的长度,这能够被视做是一个调用者和被调用者的约定。因为marshalling须要CLR的参与,那么就须要把这个约定用CLR能 够理解的方式,进行扩充,造成一个三方约定。
CLR用属 性SizeParamIndex来描述此类约定。
public static extern void Ex2(
[In, Out][MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)]string[] a,
int len);
b) SafeArray
SafeArray是COM引入的数据类型,是一个自描述度很高的数据结构。他能够很清楚的告诉用户,该 数组的元素类型,数组包含了多少维,每一维的起始 位置和终止位置。因此marshal这类safearray的时候,只须要经过设定属性,告诉CLR,当前array对应的本地代码是safearray 便可。举例以下:
public void DumpSafeArrayStringIn( [In][MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_BSTR)]Object[] array);
你们能够看到,SafeArraySubType能够用来指定数组元素的类型
2. 数组做为字段传递
好久以来,对于interop,一直有这样的评价,简单数据结构的marshalling其实并不复杂,但 是一旦进入了struct或者class这种你 中有我,我中有你的层叠数据结构以后,marshalling就成了bug的温床。因此在这里,咱们也要提提数组做为struct/class的一个字段 的方法。在这里首先要给这个stuct/class加一个限制,是byval。因为这个限制,你们能够想象的出,CLR在marshal的时候,作的事情 是相似于浅copy的内存复制,因此对数组marshal的时候,也就只支持固定长度的数组marshal。
public class StructIntArray
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]
public int[] array;
}
数组做为一种经常使用的数据结构,各类高级语言都提供了相应的支持,在这些高级语言之间交互操做的时候,数组也是传送集合类型数据的重要结构,但愿今天的内容能对你们有所帮助。