平臺叫用服務

平臺叫用服務，或稱P/Invoke，是微軟的公共語言基礎設施實現的一個特性，類似於微軟的公共語言執行時提供的跨平臺呼叫方式，允許受控程式碼呼叫原生程式碼。託管程式碼提供對.NET Framework的組成庫中定義的類、方法和型別的本機訪問。雖然.NET Framework提供了一組廣泛的功能，但它可能無法訪問通常以非託管程式碼編寫的許多較低級別的作業系統庫或也以非託管程式碼編寫的第三方庫。P/Invoke是程式設計師可以用來訪問這些庫中的函式的技術。透過在託管程式碼中宣告非託管函式的簽名來呼叫這些庫中的函式，該簽名充當可以像任何其他託管方法一樣呼叫的實際函式。該宣告引用庫的檔案路徑，並定義託管型別中的函式引數和返回值，這些託管型別最有可能由公共語言執行時(CLR)與非託管型別隱式封送。當非託管資料型別對於從託管型別到託管型別的簡單隱式轉換來說變得過於複雜時，框架允許使用者在函式、返回和/或引數上定義屬性，以顯式細化資料的編組方式，以免試圖隱式地這樣做會導致異常。與使用非託管語言進行程式設計相比，託管程式碼程式設計師可以使用許多低階程式設計概念的抽象。因此，僅具有託管程式碼經驗的程式設計師需要溫習程式設計概念，例如指標、結構和引用傳遞，以克服使用P/Invoke時的一些障礙。平臺叫用服務這一特性與微軟的公共語言執行時提供的較為類似，因此一般提到P/Invoke多數指微軟的.NET實現方案。這一方案能夠實現透過託管程式碼訪問原生程式碼。使用P/Invoke可以透過CLR來控制DLL的載入，以及將非託管程式碼的資料型別轉換為託管資料型別。Windows.在Microsoft Windows作業系統中，Native API有時也是以COM介面方式來推出，像是ADSI，FSRM等，通常是新的服務或是介面才會廣泛使用COM原生介面方式。因為.NET Framework的推行，Windows的應用程式介面被分為兩種，一種是遵循原本Windows API方式的，稱為Native API，另一種則是以.NET Framework為基礎開發的，稱為Managed API，例如Managed DirectX或是IIS Admin APIs等。在Microsoft Windows作業系統中，若是透過VB或是.NET Framework存取直接開放C函式的Native API時，則必須要利用平臺叫用服務方式存取；若是存取以COM方式開放的Native API時，若該API支援COM Automation規格時，即可利用COM Interop Services來存取。體系架構.概述.有兩類P/Invoke:細節.使用P/Invoke 時，CLR處理DLL載入以及將非託管以前的型別轉換為CTS]型別。要執行此操作，CLR：P/Invoke對於使用標準C 或 C++ DLL非常有用。當程式設計師需要訪問廣泛的Windows API時，可以使用它，因為Windows系統函式庫提供的許多功能缺乏可用的包裝器。當Win32 API未由.NET Framework公開時，必須手動編寫此API的包裝器。缺點.編寫P/Invoke包裝器可能很困難並且容易出錯。使用本機DLL意味著程式設計師無法再像.NET環境中通常提供的那樣受益於型別安全和垃圾收集。 當它們使用不當時，可能會導致諸如記憶體區段錯誤或記憶體洩漏等問題。獲取在.NET環境中使用的原生函式的準確簽名可能很困難。其他陷阱包括：當使用C++/CLI時，發出的CIL可以自由地與託管堆上的物件互動，同時與任何可定址的本機記憶體位置互動。可以使用簡單的“object-&gt;field”表示法來分配值或指定方法呼叫，從而呼叫、修改或構造託管堆駐留物件。消除了任何不必要的上下文切換，降低了記憶體需求，從而顯著提高了效能。C++/CLI帶來了新的挑戰：如果遇到這些問題，這些為每個問題指定解決方案。一個主要的好處是消除了結構宣告，欄位宣告的順序和對齊問題在C++互操作的上下文中不存在。例子.基本例子.第一個簡單示例顯示瞭如何獲取特定DLL的版本：Windows API中的“DllGetVersion”函式簽名：HRESULT DllGetVersionDLLVERSIONINFO* pdviP/Invoke C#程式碼以呼叫“DellGetVersion”函式：[StructLayout(LayoutKind.Sequential)]private struct DLLVERSIONINFO {public int cbSize;public int dwMajorVersion;public int dwMinorVersion;public int dwBuildNumber;public int dwPlatformID;[DllImport("shell32.dll")]static extern int DllGetVersion(ref DLLVERSIONINFO pdvi);第二個示例顯示瞭如何提取檔案中的圖示：Windows API中的“ExtractIcon”函式簽名：HICON ExtractIconHINSTANCE hInst,LPCTSTR lpszExeFileName,UINT nIconIndexP/Invoke C#程式碼以呼叫“ExtractIcon”函式：[DllImport("shell32.dll")]static extern IntPtr ExtractIcon(IntPtr hInst,[MarshalAs(UnmanagedType.LPStr)] string lpszExeFileName,uint nIconIndex);下一個複雜的示例顯示瞭如何在Windows平臺中的兩個程序之間共享事件：“CreateEvent”函式簽名：HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,BOOL bManualReset,BOOL bInitialState,LPCTSTR lpNameP/Invoke C#程式碼以呼叫“CreateEvent”函式：[DllImport("kernel32.dll", SetLastError=true)]static extern IntPtr CreateEvent(IntPtr lpEventAttributes,bool bManualReset,bool bInitialState,[MarshalAs(UnmanagedType.LPStr)] string lpName);一個更復雜的例子.// native declarationtypedef struct _PAIRDWORD Val1;DWORD Val2;} PAIR, *PPAIR;// Compiled with /clr; use of #pragma managed/unmanaged can lead to double thunking;// avoid by using a stand-alone .cpp with .h includes.// This would be located in a .h file.templateinline CLR_PAIR^ marshal_as (const PAIR&amp;Src) { // Note use of de/referencing. It must match your use.CLR_PAIR^ Dest = gcnew CLR_PAIR;Dest-&gt;Val1 = Src.Val1;Dest-&gt;Val2 = Src.Val2;return Dest;CLR_PAIR^ mgd_pair1;CLR_PAIR^ mgd_pair2;PAIR native0,*native1=&amp;native0;native0 = NativeCallGetRefToMemory();// Using marshal_as. It makes sense for large or frequently used types.mgd_pair1 = marshal_as(*native1);// Direct field usemgd_pair2-&gt;Val1 = native0.Val1;mgd_pair2-&gt;val2 = native0.val2;return(mgd_pair1); // Return to C#工具.有許多工具旨在幫助生成P/Invoke簽名。編寫一個實用程式來匯入C++標頭檔案和本機DLL檔案並自動生成介面程式集是相當困難的。為P/Invoke簽名生成這樣一個匯入器/匯出器的主要問題是某些C++函式呼叫引數型別的模糊性。Brad Abrams在這個問題上這樣說：問題在於C++函式，如下所示：__declspec(dllexport) void MyFunction(char *params);P/Invoke簽名中的引數params應該使用什麼型別？這可以是以C++null結尾的字串，也可以是#重定向 陣列或輸出#重定向 引數。那麼我們應該使用#重定向 ,#重定向 , #重定向 還是#重定向 呢？不管這個問題如何，有一些工具可以使P/Invoke簽名的生成更加簡單。下面列出的工具之一xInterop C++.NET Bridge透過在.NET世界中實現同一C++方法的多個重寫解決了這個問題，開發人員可以選擇正確的方法進行呼叫。PInvoke.net.PInvoke.net是一個包含大量標準Windows API的P/Invoke簽名的wiki。簽名由wiki使用者手動生成。在Microsoft Visual Studio的免費外掛可直接搜尋。PInvoker.PInvoker是一個匯入本機DLL和C++標頭檔案並匯出完全格式和編譯的P/Invoke互操作DLL的應用程式。它透過將本機指標函式引數封裝在PInvoker特定的.NET介面類中，克服了歧義問題。它沒有在P/Invoke方法定義中使用標準的.NET引數型別 (#重定向 , #重定向 , 等)，而是在P/Invokefunction呼叫中使用這些介面類。例如，如果我們考慮上面的示例程式碼，PInvoker將生成一個.NET P/Invoke函式，該函式接受一個包裝本機#重定向 指標的.NET介面類。此類的構造可以來自#重定向 或#重定向 陣列。兩者的實際本機記憶體結構是相同的，但每種型別的相應介面類建構函式將以不同的方式填充記憶體。因此，決定需要傳遞到函式中的.NET型別的責任將傳遞給開發人員。Microsoft Interop Assistant.Microsoft Interop Assistant是一個免費工具，提供二進位制檔案和原始碼，可在CodePlex上下載。它是根據微軟有限公司公共許可證許可證。它包括兩部分：因為該工具生成C#原始碼而不是編譯的dll，所以使用者可以在使用前對程式碼進行任何必要的更改。因此，透過應用程式選擇一個特定的.NET型別來在P/Invoke方法簽名中使用來解決模糊性問題。如果需要，使用者可以將其更改為所需的型別。P/Invoke嚮導.P/Invoke嚮導使用與Microsoft Interop Assistant類似的方法，因為它接受本機C++標頭檔案程式碼，並生成C#程式碼供貼上到.NET應用程式程式碼中。它還提供了要針對的框架的選項：用於桌面的.NET framework或用於Windows Mobile智慧裝置的.NET Compact framework。xInterop C++ .NET Bridge.xInterop C++ .NET Bridge是一個windows應用程式，用於為本機C++ DLL建立C#包裝器，並使用C++橋訪問.NET程式集，它附帶了一個C#/.NET庫，用於包裝標準C++類，如字串、iostream等。可以從.NET訪問C++類和物件。該工具從現有的本機C++ DLL和相關的標頭檔案生成C#包裝器DLL，這些標頭檔案是該工具構建C#包裝器DLL所需的。P/Invoke簽名和資料封送處理由應用程式生成。生成的C#包裝器具有與C++對應程式類似的介面，引數型別轉換為.NET程式碼。此工具識別未從C++DLL匯出的模板類，並例項化模板類並將其匯出到補充DLL中，相應的C++介面可以在.NET中使用。