標籤:style class blog code http ext
看了P/Invoke技術的介紹,於是想寫下點東西,東西包含兩個部分:知識的紀錄和我的理解及疑問。
rManaged 程式碼中調用非託管API函數的過程
1、定位包含API的DLL;
2、載入DLL
3、找到DLL中想要的那個API,然後把參數壓入棧中、排列資料(排列資料是什麼意思?資料封送)
4、把執行許可權從Managed 程式碼中轉移到Unmanaged 程式碼中()
對Dll中的函數進行一些說明,以能調用
DllImport特性來說明函數,有一些特殊的作用,比如換掉API的原來名字,見DLLImport特性。
非託管函數和託管方法中資料類型對應:資料封送
把C#中資料轉換成API中資料類型,是資料封送。對於每個 .NET Framework 類型均有一個預設非託管類型,公用語言運行庫將使用此非託管類型在託管到非託管的函數調用中封送資料。string 類型預設非託管類型是LPTSTR,可以在非託管函數的 C# 聲明中使用 MarshalAs 屬性重寫預設封送處理。例如:
[DllImport("msvcrt.dll")] public static extern int puts( [MarshalAs(UnmanagedType.LPStr)] string m);
puts 函數的參數的預設封送處理已從預設值 LPTSTR 重寫為 LPSTR
修改預設封送有什麼用? 預設情況下,本機結構和託管結構在記憶體中的布局有所不同,因此,若要跨託管/非託管邊界成功傳遞結構,需要執行一些額外步驟來保持資料的完整性。,
如果某個API中使用一個結構,那麼C#中沒有一個對應的託管類型與之對應的話,怎麼辦?需要為使用者定義的結構指定自訂封送處理。
可以為傳遞到非託管函數或從非託管函數返回的結構和類的欄位指定自訂封送處理屬性。通過向結構或類的欄位中添加 MarshalAs 屬性可以做到這一點。還必須使用 StructLayout 屬性設定結構的布局,還可以控制字元串成員的預設封送處理,並設定預設封裝大小。
C++中:
1 struct SS2 {3 4 int a;5 6 byte b;7 8 }
對應C#中:
1 class SS2 {3 public int a;4 public byte b; 5 }
封送,在此看來算是傳資料,但是為什麼要封送呢?封送是為了在託管記憶體和非託管記憶體中正常傳遞資料。?有時,出於對效能的考慮,會對託管結構的成員進行重新排列,因此有必要使用 StructLayoutAttribute 特性指示該結構為循序配置。 將結構封裝設定顯式設定為與本機結構所使用的設定相同的設定也是一個好辦法。
資料封送--基礎資料型別 (Elementary Data Type)對應關係:
資料封送中指標處理的兩種情況 :
一、普通指標
在C#中使用ref out來實行封送。
二、Handle類型
C#中使用IntPtr來進行封送
封送,我的理解是兩個方面,封裝和傳送。傳送就像是方法中參數的傳遞那樣;而封裝就有些意思了,在封裝前有時候需要做一些處理。就像是上面的 class SS和C++中的struct ss ,其實兩者中的資料a、b在記憶體中是不一樣的。詳細如下:
1 1 class SS2 2 {3 3 public int a;4 4 public byte b; 5 5 }
看起來a是在b的前面,其實在記憶體中可不是這樣的,C#中類的欄位成員的順序在記憶體中是自動安排的,也就是說不像是原始碼中那樣,至於是為什麼是自動(auto)安排順序,還不知道,希望有人知道的話說一下,而在C++中類成員的順序卻是和看到的一樣的,這就是為什麼在封裝前要做一些處理了,因為成員在記憶體中的順序不一樣!
C#中有一個特性可以調整這個順序,也就是[StructLayout(LayoutKind.Sequential)],在沒有顯式進行修飾一個類時,預設情況下是[StructLayout(LayoutKind.Auto)]的。處理如下:
[StructLayout(LayoutKind.Sequential)] class SS { public int a; public byte b; }
註冊回調方法
遇到回呼函數的處理。