1. 程式人生 > >[DllImport("kernel32.dll")]是什麼意思??

[DllImport("kernel32.dll")]是什麼意思??

DLL Import 屬性

現在是更深入地進行探討的時候了。在對託管程式碼進行 P/Invoke 呼叫時,DllImportAttribute 型別扮演著重要的角色。DllImportAttribute 的主要作用是給 CLR 指示哪個 DLL 匯出您想要呼叫的函式。相關 DLL 的名稱被作為一個建構函式引數傳遞給 DllImportAttribute。

如果您無法肯定哪個 DLL 定義了您要使用的 Windows API 函式,Platform SDK 文件將為您提供最好的幫助資源。在 Windows API 函式主題文字臨近結尾的位置,SDK 文件指定了 C 應用程式要使用該函式必須連結的 .lib 檔案。在幾乎所有的情況下,該 .lib 檔案具有與定義該函式的系統 DLL 檔案相同的名稱。例如,如果該函式需要 C 應用程式連結到 Kernel32.lib,則該函式就定義在 Kernel32.dll 中。您可以在 MessageBeep 中找到有關 MessageBeep 的 Platform SDK 文件主題。在該主題結尾處,您會注意到它指出庫檔案是 User32.lib;這表明 MessageBeep 是從 User32.dll 中匯出的。

可選的 DllImportAttribute 屬性

除了指出宿主 DLL 外,DllImportAttribute 還包含了一些可選屬性,其中四個特別有趣:EntryPoint、CharSet、SetLastError 和 CallingConvention。

EntryPoint 在不希望外部託管方法具有與 DLL 匯出相同的名稱的情況下,可以設定該屬性來指示匯出的 DLL 函式的入口點名稱。當您定義兩個呼叫相同非託管函式的外部方法時,這特別有用。另外,在 Windows 中還可以通過它們的序號值繫結到匯出的 DLL 函式。如果您需要這樣做,則諸如“#1”或“#129”的 EntryPoint 值指示 DLL 中非託管函式的序號值而不是函式名。

CharSet 對於字符集,並非所有版本的 Windows 都是同樣建立的。Windows 9x 系列產品缺少重要的 Unicode 支援,而 Windows NT 和 Windows CE 系列則一開始就使用 Unicode。在這些作業系統上執行的 CLR 將Unicode 用於 String 和 Char 資料的內部表示。但也不必擔心 — 當呼叫 Windows 9x API 函式時,CLR 會自動進行必要的轉換,將其從 Unicode轉換為 ANSI。

如果 DLL 函式不以任何方式處理文字,則可以忽略 DllImportAttribute 的 CharSet 屬性。然而,當 Char 或 String 資料是等式的一部分時,應該將 CharSet 屬性設定為 CharSet.Auto。這樣可以使 CLR 根據宿主 OS 使用適當的字符集。如果沒有顯式地設定 CharSet 屬性,則其預設值為 CharSet.Ansi。這個預設值是有缺點的,因為對於在 Windows 2000、Windows XP 和 Windows NT® 上進行的 interop 呼叫,它會消極地影響文字引數封送處理的效能。

應該顯式地選擇 CharSet.Ansi 或 CharSet.Unicode 的 CharSet 值而不是使用 CharSet.Auto 的唯一情況是:您顯式地指定了一個匯出函式,而該函式特定於這兩種 Win32 OS 中的某一種。ReadDirectoryChangesW API 函式就是這樣的一個例子,它只存在於基於 Windows NT 的作業系統中,並且只支援 Unicode;在這種情況下,您應該顯式地使用 CharSet.Unicode。

有時,Windows API 是否有字符集關係並不明顯。一種決不會有錯的確認方法是在 Platform SDK 中檢查該函式的 C 語言標頭檔案。(如果您無法肯定要看哪個標頭檔案,則可以檢視 Platform SDK 文件中列出的每個 API 函式的標頭檔案。)如果您發現該 API 函式確實定義為一個對映到以 A 或 W 結尾的函式名的巨集,則字符集與您嘗試呼叫的函式有關係。Windows API 函式的一個例子是在 WinUser.h 中宣告的 GetMessage API,您也許會驚訝地發現它有 A 和 W 兩種版本。

SetLastError 錯誤處理非常重要,但在程式設計時經常被遺忘。當您進行 P/Invoke 呼叫時,也會面臨其他的挑戰 — 處理託管程式碼中 Windows API 錯誤處理和異常之間的區別。我可以給您一點建議。

如果您正在使用 P/Invoke 呼叫 Windows API 函式,而對於該函式,您使用 GetLastError 來查詢擴充套件的錯誤資訊,則應該在外部方法的 DllImportAttribute 中將 SetLastError 屬性設定為 true。這適用於大多數外部方法。

這會導致 CLR 在每次呼叫外部方法之後快取由 API 函式設定的錯誤。然後,在包裝方法中,可以通過呼叫類庫的 System.Runtime.InteropServices.Marshal 型別中定義的 Marshal.GetLastWin32Error 方法來獲取快取的錯誤值。我的建議是檢查這些期望來自 API 函式的錯誤值,併為這些值引發一個可感知的異常。對於其他所有失敗情況(包括根本就沒意料到的失敗情況),則引發在 System.ComponentModel 名稱空間中定義的 Win32Exception,並將 Marshal.GetLastWin32Error 返回的值傳遞給它。如果您回頭看一下圖 1 中的程式碼,您會看到我在 extern MessageBeep 方法的公共包裝中就採用了這種方法。

CallingConvention 我將在此介紹的最後也可能是最不重要的一個 DllImportAttribute 屬性是 CallingConvention。通過此屬性,可以給 CLR 指示應該將哪種函式呼叫約定用於堆疊中的引數。CallingConvention.Winapi 的預設值是最好的選擇,它在大多數情況下都可行。然而,如果該呼叫不起作用,則可以檢查 Platform SDK 中的宣告標頭檔案,看看您呼叫的 API 函式是否是一個不符合呼叫約定標準的異常 API。

通常,本機函式(例如 Windows API 函式或 C- 執行時 DLL 函式)的呼叫約定描述瞭如何將引數推入執行緒堆疊或從執行緒堆疊中清除。大多數 Windows API 函式都是首先將函式的最後一個引數推入堆疊,然後由被呼叫的函式負責清理該堆疊。相反,許多 C-執行時 DLL 函式都被定義為按照方法引數在方法簽名中出現的順序將其推入堆疊,將堆疊清理工作交給呼叫者。

幸運的是,要讓 P/Invoke 呼叫工作只需要讓外圍裝置理解呼叫約定即可。通常,從預設值 CallingConvention.Winapi 開始是最好的選擇。然後,在 C 執行時 DLL 函式和少數函式中,可能需要將約定更改為 CallingConvention.Cdecl。