標籤:
託管和非託管轉換新方法:Marshaling Library(zz)
託管和非託管轉換新方法:Marshaling Library(zz)
http://hi.baidu.com/superql/blog/item/38e9c8073202fcc37a8947ac.html
1.VC++2008中新增加的庫:Marshaling Library
我們一起討論一下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);
一些轉換需要分配記憶體,而且必須隨後刪除。這樣的轉換需要一個叫做context的對象,這個對象在它不再需要的時候就刪除了。比如從一個託管的String^轉換到本地的char*就需要一個context,因為在轉換過程中產生了String的一個臨時的副本。代碼如下:
marshal_context context;
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類型等概念做進一步的討論。
當Managed 程式碼需要和本地代碼互操作時,我們就進入了interop的領域。interop的情境形形色色,不變的是我們需要把資料從一個世界marshal到另一個世界。
在討論數組marshalling之前,請各位和我一起思考一個問題,什麼是數組?之所以要討論這個問題,原因在於不同的術語在不同的語境中含有不同的意 思。在使用c語言的時候,我認為數組就是一個指標。但是熟悉c#的朋友可能不同意我的觀點,數組是System.Array或者Object[]。我認為,這兩種回答都是出自語言領域的正確觀點。那麼如果有一個項目含有兩個模組,一個用本地代碼撰寫,另一個用Managed 程式碼撰寫,兩者之間的介面要求傳遞一個數組,這個”數組”包含著怎樣的語義呢?
我覺得有兩點是很重要的:
1. 如何訪問數組元素。就好比c語言中的數組指標,c#中的數組引用,都是訪問數組必不可少的線索。
2. 數組的大小。數組的大小不僅僅是System.Array.Length。它還可以包括諸如數組的維數,每個維上的啟始邊界和結束邊界。
.NET在marshal數組的時候,很大程度上也是從以上兩點出發,架起託管世界和本地代碼之間的橋樑。根據操作的具體資料類型不同,數組marshal又可以分為以下兩個大類,三個小類,我們分別介紹:
1. 數組作為參數傳遞
a) c/c++類型的數組
c類型的數組,也就是由指標指明儲存空間首地址的數組,是一個自描述很低的資料結構。儘管有些編譯器支援在固定位移量上寫入數組的長度,但是因為各個編譯 器處理的具體方法不同,沒有一個標準讓CLR來參考。所以我們在marshal一個c類型的數組的時候,不得不用其他方法考慮傳遞數組的大小,有以下兩種:
1) 約定指標數組長度
這種方法需要調用者和被調用者之間有一個約定,給出一個數組長度的固定值。在託管端聲明一個interop方法的時候,只需要用SizeConst這個屬性,把這個約定告訴CLR。CLR在進行Marshal的時候,會根據這個值,在本地堆上分配相應的空間。
public static extern void Ex(
[In, Out][MarshalAs(UnmanagedType.LPArray, SizeParamConst=3)]string[] a);
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;
}
數組作為一種常用的資料結構,各種進階語言都提供了相應的支援,在這些進階語言之間互動操作的時候,數組也是傳送集合類型資料的重要結構,希望今天的內容能對大家有所協助。
託管和非託管轉換新方法:Marshaling Library(zz) 【轉】