1.平臺互操做性和不安全的代碼:C#功能強大,但有些時候,它的表現仍然有些「力不從心」,因此咱們只能摒棄它所提供的全部安全性,轉而退回到內存地址和指針的世界。編程
C#經過3種方式對此提供支持。安全
(1)第一種方式是經過平臺調用(Platform Invoke,P/Invoke)來調用非託管代碼DLL所公開的API。ide
(2)第二種方式是經過不安全的代碼,它容許咱們訪問內存指針和地址。不少狀況下,代碼須要綜合運用這兩種方式。函數
(3)第三種方式是經過COM Interop(COM互操做)。佈局
2.平臺調用:優化
(1)外部函數的聲明:肯定了要調用的目標函數之後,P/Invoke的下一步即是用託管代碼聲明函數,和普通方法同樣,必須在一個類中聲明目標API,但要爲它添加extern修飾符,從而把它聲明爲外部函數,extern方法始終是靜態方法,所以不包含任何實現。相反,附加在方法聲明以前的DllImport特性指向實現。該特性須要定義該函數的DLL的「名稱」,導入的DLL必須在路徑內,其中包含可執行文件的目錄,以使其可以加載成功。「運行時」根據方法名來判斷函數名,然而也能夠用EntryPoint具名參數來重寫默認行爲,明確提供一個函數名。ui
(2)參數的數據類型:在肯定目標DLL和導出函數,那麼要標識或建立與外部函數中的非託管數據類型對應的託管數據類型。this
3.爲順序佈局使用StructLayoutAttribute:有些API涉及的類型沒有對應的託管類型,要調用這些API,須要託管代碼從新聲明類型。例如,可使用託管代碼來聲明非託管的COLORREF struct,如ColorRef結構清單。代碼中聲明的關鍵之處在於StructLayoutAttribute,默認狀況下,託管代碼能夠優化類型的內存佈局,因此,內存佈局可能不是從一個字段到另外一個字段順序存儲。爲了強制順序佈局,使類型可以直接映射,並且能夠在託管和非託管代碼之間逐位地複製,你須要添加StructLayoutAttribute特性,並指定LayoutKind.Sequential枚舉值。spa
4.平臺調用(P/Invoke)的錯誤處理:Win32 API編程的一個不便之處在於,錯誤常常以不一致的方式來報告,若有API返回0、一、false等,有API以out參數來處理,非託管代碼中的Win32錯誤報告不多經過異常來生成。P/Invoke設計者爲此提供了相應的處理機制,要啓用這一機制,DllImport特性的SetLastError具名參數要設爲true,這樣就能夠實例化一個System.ComponentModel.Win32Exception。在P/Invoke調用以後,會自動用Win32錯誤數據來初始化它,如VirtualMemoryManger類的代碼清單。這樣一來,開發人員就能夠提供每一個API使用的自定義錯誤檢查,同時仍然可使用一種標準方式來報告錯誤。設計
5.使用SafeHandle:不少時候,P/Invoke會涉及一個資源,好比窗口句柄(Window handle),等等。在用完此類資源以後,代碼須要清理它們。可是,不要強迫開發人員記住這一點,並每次都人工編寫代碼,而是應該提供實現IDisposable接口和終結器的類。爲了對此提供內建的支持,以下面的VirtualMemoryPtr類,該類派生自System.Runtime.InteropServices.SafeHandle。SafeHandle類包含兩個抽象成員:IsInvalid和ReleaseHandle()。在後者中,你能夠放入對資源進行清理的代碼,前者則指出是否執行了資源清理代碼。可查看VirtualMemoryPtr類代碼清單。
6.P/Invoke指導原則:
(1)覈實確實沒有託管類型已經公開你想要的API。
(2)將API外部方法定義爲private,或者在簡單的狀況下定義爲Internal。
(3)圍繞外部方法提供公共包裝方法,執行數據類型轉換和錯誤處理。
(4)重載包裝方法,並經過爲外部方法調用插入默認值,減小所需的參數數目。
(5)在聲明API的同時,使用enum或const爲API提供常量值。
(6)針對支持GetLastError()的全部P/Invoke方法,務必將SetLastError命名特性的值設爲true。這樣一來,就能夠經過System.ComponentModel.Win32Exception報告錯誤。
(7)將句柄之類的資源包裝,包裝在從System.Runtime.InteropServices.SafeHandle派生或者支持IDisposable的類中。
(8)非託管代碼中的函數指針映射到託管代碼中的委託實例。一般,這須要聲明一個特定的委託類型,它與非託管函數指針的簽名是匹配的。
(9)將輸入/輸出參數和輸出參數映射到ref參數,而不是依賴於指針。
7.不安全的代碼:可使用unsafe用做類型或者類型內部的特定成員的修飾符。unsafe修飾符對生成的CIL代碼自己沒有影響。它只是一個預編譯指令,做用是向編譯器指明容許在不安全的代碼塊內操做指針和地址。
8.指針的聲明:因爲指針(自己只是剛好指向內存地址的一些整形值)不會被垃圾回收,因此C#不容許非託管類型以外的被引用物類型。換言之,類型不能是引用類型,不能是泛型類型,並且內部不能包含引用類型。如 byte* pData;指針是一種全新的類型,和結構、枚舉、類不一樣,指針的終極基類不是System.Object,甚至不能轉換成System.Object,相反,它們能轉換成System.IntPtr(後者能轉換成System.Object)。
9.指針的賦值:咱們須要使用地址運算符(&)來獲取值類型的地址。不管哪一種方法,爲了將一些數據的地址賦值給一個指針,要求以下。
(1)數據必須屬於一個變量。
(2)數據必須是一個非託管類型。
(3)變量須要用fixed固定,不能移動。
如 byte* pData = &bytes[0];//編譯錯誤,數據可能發生移動,須要固定。
如 byte[] bytes = new bytes[24]; fixed (byte* pData = &bytes[0]){}//編譯正確
10.指針的解引用:爲了訪問指針引用的一個類型值,要求你解引用指針,即在指針類型以前添加一個間接尋址運算符*。如 byte data = *pData;不能對void*類型的指針應用解引用運算符,void*數據類型表明的是指向一個未知類型的指針。因爲數據類型未知,因此不能解引用到另外一種類型。相反,爲了訪問void*引用的數據,必須把它轉換成其餘任何指針類型的變量,而後對後一種類型執行解引用。
[StructLayout(LayoutKind.Sequential)] public struct ColorRef { public byte Red; public byte Green; public byte Blue; private byte Unused; public ColorRef(byte red, byte green, byte blue) : this() { Red = red; Green = green; Blue = blue; Unused = 0; } } public class VirtualMemoryManger { [DllImport("kernel32.dll", SetLastError = true)] private static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, IntPtr dwSize, IntPtr dwFreeType); [DllImport("kernel32.dll", EntryPoint = "GetCurrentProcess")] internal static extern IntPtr GetCurrentProcessHandle(); [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, IntPtr dwSize, AllocationType flAllocationType, uint flProtect); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool VirtualProtectEx(IntPtr hPorcess, IntPtr lpAddress, IntPtr dwSize, uint flNewProtect, ref uint lpflOldProtect); public static IntPtr AllocExecutionBlock(int size, IntPtr hProcess) { IntPtr codeBytesPtr = VirtualAllocEx(hProcess, IntPtr.Zero, (IntPtr)size, AllocationType.Reserve | AllocationType.Commit, (uint)ProtectionOptions.PageExecuteReadWrite); if (codeBytesPtr == IntPtr.Zero) { throw new Win32Exception(); } uint lpflOldProtect = 0; if (!VirtualProtectEx(hProcess, codeBytesPtr, (IntPtr)size, (uint)ProtectionOptions.PageExecuteReadWrite, ref lpflOldProtect)) { throw new Win32Exception(); } return codeBytesPtr; } public static IntPtr AllocExecutionBlock(int size) { //一般應該將方法封裝到公共包裝裏面,從而下降P/Invoke API調用的複雜性,這樣能夠加強API的可用性,同時更有利於轉向面向對象的類型結構。 return AllocExecutionBlock(size, GetCurrentProcessHandle()); } public static bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, IntPtr dwSize) { bool result = VirtualFreeEx(hProcess, lpAddress, dwSize, (IntPtr)MemoryFreeType.Decommit); if (!result) { throw new Win32Exception(); } return result; } public static bool VirtualFreeEx(IntPtr lpAddress, IntPtr dwSize) { //不管錯誤處理、struct、仍是常量值,優秀的API開發人員都應該提供一個簡化的託管API,下降層的Win32API包裝起來。 return VirtualFreeEx(GetCurrentProcessHandle(), lpAddress, dwSize); } } public class VirtualMemoryPtr:SafeHandle { public readonly IntPtr AllocatedPointer; private readonly IntPtr ProcessHandle; private readonly IntPtr MemorySize; private bool Disposed; public VirtualMemoryPtr(int memorySize) : base(IntPtr.Zero, true) { ProcessHandle = VirtualMemoryManger.GetCurrentProcessHandle(); MemorySize = (IntPtr) memorySize; AllocatedPointer = VirtualMemoryManger.AllocExecutionBlock(memorySize, ProcessHandle); Disposed = false; } public static implicit operator IntPtr(VirtualMemoryPtr virtualAMemoryPointer) { return virtualAMemoryPointer.AllocatedPointer; } protected override bool ReleaseHandle() { if (!Disposed) { Disposed = true; GC.SuppressFinalize(this); VirtualMemoryManger.VirtualFreeEx(ProcessHandle,AllocatedPointer,MemorySize); } return true; } public override bool IsInvalid { get { return Disposed; } } } [Flags] public enum AllocationType { Reserve = 0x2000, Commit = 0x1000, Reset = 0x8000, Physical = 0x400000, TopDown = 0x100000, } [Flags] public enum ProtectionOptions { PageExecuteReadWrite = 0x40, PageExecuteRead = 0x20, Execute = 0x10 } [Flags] public enum MemoryFreeType { Decommit = 0x4000, Release = 0x8000 }