C#學習筆記15。本站提示廣大學習愛好者:(C#學習筆記15)文章只能為提供參考,不一定能成為您想要的結果。以下是C#學習筆記15正文
1.平台互操作性和不平安的代碼:C#功用弱小,但有些時分,它的表現依然有些“力所能及”,所以我們只能摒棄它所提供的一切平安性,轉而退回到內存地址和指針的世界。
C#經過3種方式對此提供支持。
(1)第一種方式是經過平台調用(Platform Invoke,P/Invoke)來調用非托管代碼DLL所地下的API。
(2)第二種方式是經過不平安的代碼,它允許我們訪問內存指針和地址。很多狀況下,代碼需求綜合運用這兩種方式。
(3)第三種方式是經過COM Interop(COM互操作)。
2.平台調用:
(1)內部函數的聲明:確定了要調用的目的函數當前,P/Invoke的下一步便是用托管代碼聲明函數,和普通辦法一樣,必需在一個類中聲明目的API,但要為它添加extern修飾符,從而把它聲明為內部函數,extern辦法一直是靜態辦法,因而不包括任何完成。相反,附加在辦法聲明之前的DllImport特性指向完成。該特性需求定義該函數的DLL的“稱號”,導入的DLL必需在途徑內,其中包括可執行文件的目錄,以使其可以加載成功。“運轉時”依據辦法名來判別函數名,但是也可以用EntryPoint具名參數來重寫默許行為,明白提供一個函數名。
(2)參數的數據類型:在確定目的DLL和導出函數,那麼要標識或創立與內部函數中的非托管數據類型對應的托管數據類型。
3.為順序規劃運用StructLayoutAttribute:有些API觸及的類型沒有對應的托管類型,要調用這些API,需求托管代碼重新聲明類型。例如,可以運用托管代碼來聲明非托管的COLORREF struct,如ColorRef構造清單。代碼中聲明的關鍵之處在於StructLayoutAttribute,默許狀況下,托管代碼可以優化類型的內存規劃,所以,內存規劃能夠不是從一個字段到另一個字段順序存儲。為了強迫順序規劃,使類型可以直接映射,而且可以在托管和非托管代碼之間逐位地復制,你需求添加StructLayoutAttribute特性,並指定LayoutKind.Sequential枚舉值。
4.平台調用(P/Invoke)的錯誤處置:Win32 API編程的一個方便之處在於,錯誤常常以不分歧的方式來報告,如有API前往0、1、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 }View Code