平臺叫用服務，或稱 PInvoke，是微軟的公共語言基礎設施實現的一個特性，類似於微軟的公共語言執行時提供的跨平臺呼叫方式，允許受控程式碼呼叫原生程式碼。 託管程式碼（例如 C 或 VBNET 或 CCLI）提供對NET Framework 的組成庫中定義的類、方法和型別的本機訪問。雖然NET Framework 提供了一組廣泛的功能，但它可能無法訪問通常以非託管程式碼編寫的許多較低級別的作業系統庫或也以非託管程式碼編寫的第三方庫。PInvoke 是程式設計師可以用來訪問這些庫中的函式的技術。透過在託管程式碼中宣告非託管函式的簽名來呼叫這些庫中的函式，該簽名充當可以像任何其他託管方法一樣呼叫的實際函式。該宣告引用庫的檔案路徑，並定義託管型別中的函式引數和返回值，這些託管型別最有可能由公共語言執行時 CLR 與非託管型別隱式封送。當非託管資料型別對於從託管型別到託管型別的簡單隱式轉換來說變得過於複雜時，框架允許使用者在函式、返回和 或引數上定義屬性，以顯式細化資料的編組方式，以免試圖隱式地這樣做會導致異常。 與使用非託管語言進行程式設計相比，託管程式碼程式設計師可以使用許多低階程式設計概念的抽象。因此，僅具有託管程式碼經驗的程式設計師需要溫習程式設計概念，例如指標、結構和引用傳遞，以克服使用 PInvoke 時的一些障礙。 平臺叫用服務這一特性與微軟的公共語言執行時提供的較為類似，因此一般提到 PInvoke 多數指微軟的NET 實現方案。這一方案能夠實現透過託管程式碼訪問原生程式碼。使用 PInvoke 可以透過 CLR 來控制 DLL 的載入，以及將非託管程式碼的資料型別轉換為託管資料型別。 Windows 在 Microsoft Windows 作業系統中，Native API 有時也是以 COM 介面方式來推出，像是 ADSI，FSRM（File Server Resource Manager）等，通常是新的服務或是介面才會廣泛使用 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 來存取。 體系架構 概述 有兩類 PInvoke 顯式 原生程式碼透過動態連結庫 DLL 匯入 嵌入到呼叫者的程式集（assembly）的，用來定義如何呼叫原生程式碼、如何訪問資料（通常需要屬性源說明符來幫助編譯器生成整集（marshal）程式碼 這種定義就是 顯式 部分。 隱式 使用 CCLI 程式語言的程式可以同時使用受管堆（以追蹤指標）和任何原生記憶體區域，不必顯式宣告。（即 隱式） 隱式方法最主要好處是如果原生資料結構改變了，只要保持命名兼任，就能保持向後相容。 即，只要結構成員名稱沒有改變，就可以透明地支援在原生程式碼標頭檔案中新增 刪除 重新排序結構。 細節 使用 PInvoke 時，CLR 處理 DLL 載入以及將非託管以前的型別轉換為 CTS 型別（也稱為 引數編組）。引數編組（Parameter marshaling）不應與通用術語 編組（marshalling）混淆，意思是序列化。 封送引數在轉換為 CTS 型別後將複製到 CLR 堆疊中，但不會序列化。要執行此操作，CLR： 定位包含該函式的 DLL。 將 DLL 載入到記憶體中。 定位記憶體中函式的地址，並將其引數壓入堆疊，根據需要封送（marshaling）資料。 PInvoke 對於使用標準（非託管）C 或 C DLL 非常有用。當程式設計師需要訪問廣泛的 Windows API 時，可以使用它，因為 Windows 系統函式庫提供的許多功能缺乏可用的包裝器。當 Win32 API 未由NET Framework 公開時，必須手動編寫此 API 的包裝器。 缺點 編寫 PInvoke 包裝器可能很困難並且容易出錯。使用本機 DLL 意味著程式設計師無法再像NET 環境中通常提供的那樣受益於型別安全和垃圾回收。 當它們使用不當時，可能會導致諸如記憶體區段錯誤或記憶體洩漏等問題。獲取在NET 環境中使用的原生函式的準確簽名可能很困難。 其他陷阱包括： 託管語言中使用者定義的型別的資料對齊不正確：根據 C 語言中的編譯器或編譯器指令，資料對齊的方式有不同，必須小心地顯式告訴 CLR 如何對齊非 blittable 型別的資料。 一個常見的例子是嘗試在NET 中定義資料型別以表示 C 語言中的 union，即多個不同的變數在記憶體中重疊。而在NET 中的型別中定義這兩個變數會導致它們位於記憶體中的不同位置，因此必須使用特殊屬性來糾正此問題。 託管語言的垃圾收集器對資料位置的干擾：如果引用是NET 中方法的本地引用並傳遞給原生函式，則當託管方法返回時，垃圾收集器可能會回收該引用。 需要注意的是，物件引用是 pinned，防止它被垃圾收集器收集或移動，從而導致本機模組的無效訪問。 當使用 CCLI 時，發出的 CIL 可以自由地與託管堆上的物件互動，同時與任何可定址的本機記憶體位置互動。可以使用簡單的 objectfield 表示法來分配值或指定方法呼叫，從而呼叫、修改或構造託管堆駐留物件。消除了任何不必要的上下文切換，降低了記憶體需求（更短的堆疊），從而顯著提高了效能。 CCLI 帶來了新的挑戰： 程式碼容易出現雙重雷電（Double Thunking）引用 weburlhttpsdocsmicrosoftcomenuscppdotnetdoublethunkingcpptitle＝Double Thunking（C）｝｝如果沒有特別說明 載入程式鎖定問題（Loader Lock issue）｛｛引用 weburlhttpsdocsmicrosoftcomenuscppdotnetinitializationofmixedassembliestitle＝初始化混合程式集｝｝ 如果遇到這些問題，這些參考資料會為每個問題指定解決方案。一個主要的好處是消除了結構宣告，欄位宣告的順序和對齊問題在 C 互操作的上下文中不存在。 例子 基本例子 第一個簡單示例顯示瞭如何獲取特定 DLL 的版本： Windows API 中的 DllGetVersion 函式簽名： HRESULT DllGetVersion DLLVERSIONINFO pdvi PInvoke C 程式碼以呼叫 DellGetVersion 函式： StructLayout LayoutKindSequential private struct DLLVERSIONINFO public int cbSize public int dwMajorVersion public int dwMinorVersion public int dwBuildNumber public int dwPlatformID DllImport shell32dll static extern int DllGetVersion ref DLLVERSIONINFO pdvi 第二個示例顯示瞭如何提取檔案中的圖示： Windows API 中的 ExtractIcon 函式簽名： HICON ExtractIcon HINSTANCE hInst LPCTSTR lpszExeFileName UINT nIconIndex PInvoke C 程式碼以呼叫 ExtractIcon 函式： DllImport shell32dll static extern IntPtr ExtractIcon IntPtr hInst MarshalAs UnmanagedTypeLPStr string lpszExeFileName uint nIconIndex 下一個複雜的示例顯示瞭如何在 Windows 平臺中的兩個程序之間共享事件： CreateEvent 函式簽名： HANDLE CreateEvent LPSECURITY_ATTRIBUTES lpEventAttributes BOOL bManualReset BOOL bInitialState LPCTSTR lpName PInvoke C 程式碼以呼叫 CreateEvent 函式： DllImport kernel32dll SetLastErrortrue static extern IntPtr CreateEvent IntPtr lpEventAttributes bool bManualReset bool bInitialState MarshalAs UnmanagedTypeLPStr string lpName 一個更復雜的例子 native declaration typedef struct _PAIR DWORD Val1 DWORD Val2 PAIR PPAIR Compiled with clr use of pragma managedunmanaged can lead to double thunking avoid by using a standalone cpp with h includes This would be located in a h file template inline CLR_PAIR marshal_asCLR_PAIR PAIR const PAIRSrc Note use of dereferencing It must match your use CLR_PAIR Dest gcnew CLR_PAIR DestVal1 SrcVal1 DestVal2 SrcVal2 return Dest CLR_PAIR mgd_pair1 CLR_PAIR mgd_pair2 PAIR native0native1native0 native0 NativeCallGetRefToMemory Using marshal_as It makes sense for large or frequently used types mgd_pair1 marshal_asCLR_PAIRnative1 Direct field use mgd_pair2Val1 native0Val1 mgd_pair2val2 native0val2 return mgd_pair1 Return to C 工具 有許多工具旨在幫助生成 PInvoke 簽名。 編寫一個實用程式來匯入 C 標頭檔案和本機 DLL 檔案並自動生成介面程式集是相當困難的。為 PInvoke 簽名生成這樣一個匯入器 匯出器的主要問題是某些 C 函式呼叫引數型別的模糊性。 Brad Abrams 在這個問題上這樣說： 問題在於 C 函式，如下所示： __declspec dllexport void MyFunction char params PInvoke 簽名中的引數 params 應該使用什麼型別？這可以是以 Cnull 結尾的字串，也可以是陣列或輸出引數。那麼我們應該使用，還是呢？ 不管這個問題如何，有一些工具可以使 PInvoke 簽名的生成更加簡單。 下面列出的工具之一 xInterop CNET Bridge 透過在NET 世界中實現同一 C 方法的多個重寫解決了這個問題，開發人員可以選擇正確的方法進行呼叫。 PInvokenet PInvokenet 是一個包含大量標準 Windows API 的 PInvoke 簽名的 wiki。簽名由 wiki 使用者手動生成。在 Microsoft Visual Studio 的免費外掛可直接搜尋。 PInvoker PInvoker 是一個匯入本機 DLL 和 C 標頭檔案並匯出完全格式和編譯的互操作 DLL 的應用程式。它透過將本機指標函式引數封裝在 PInvoker 特定的NET 介面類中，克服了歧義問題。它沒有在 PInvoke 方法定義中使用標準的NET 引數型別 等，而是在 PInvokefunction 呼叫中使用這些介面類。 例如，如果我們考慮上面的示例程式碼，PInvoker 將生成一個NET 函式，該函式接受一個包裝本機指標的NET 介面類。此類的構造可以來自或陣列。兩者的實際本機記憶體結構是相同的，但每種型別的相應介面類建構函式將以不同的方式填充記憶體。因此，決定需要傳遞到函式中的NET 型別的責任將傳遞給開發人員。 Microsoft Interop Assistant Microsoft Interop Assistant 是一個免費工具，提供二進位制檔案和原始碼，可在 CodePlex 上下載。它是根據微軟有限公司公共許可證（Ms LPL）許可證。 它包括兩部分： 一個轉換器，它接受包含和方法定義的本地 C 標頭檔案程式碼部分。然後，它生成 C PInvoke 程式碼，供複製並貼上到應用程式中。 轉換了 Windows API 常量、方法和結構定義的可搜尋資料庫。 因為該工具生成 C 原始碼而不是編譯的 dll，所以使用者可以在使用前對程式碼進行任何必要的更改。因此，透過應用程式選擇一個特定的NET 型別來在 PInvoke 方法簽名中使用來解決模糊性問題。如果需要，使用者可以將其更改為所需的型別。 PInvoke 嚮導 PInvoke 嚮導使用與 Microsoft Interop Assistant 類似的方法，因為它接受本機 C 標頭檔案程式碼，並生成 C（或 VBNET）程式碼供貼上到NET 應用程式程式碼中。 它還提供了要針對的框架的選項：用於桌面的NET framework 或用於 Windows Mobile 智慧裝置（和 Windows CE）的NET Compact framework。 xInterop C NET Bridge xInterop C NET Bridge 是一個 windows 應用程式，用於為本機 C DLL 建立 C 包裝器，並使用 C 橋訪問NET 程式集，它附帶了一個 CNET 庫，用於包裝標準 C 類，如字串、iostream 等。可以從NET 訪問 C 類和物件。 該工具從現有的本機 C DLL 和相關的標頭檔案生成 C 包裝器 DLL，這些標頭檔案是該工具構建 C 包裝器 DLL 所需的。PInvoke 簽名和資料封送處理由應用程式生成。生成的 C 包裝器具有與 C 對應程式類似的介面，引數型別轉換為NET 程式碼。 此工具識別未從 CDLL 匯出的模板類，並例項化模板類並將其匯出到補充 DLL 中，相應的 C 介面可以在NET 中使用。 參見 參考文獻 外部連結 A site devoted to PInvoke CategoryNET