理解DllImportAttribute中的屬性

來源:互聯網
上載者:User

     在對Managed 程式碼進行P/Invoke調用時,DllImportAttribute類型是一個重要的角色,DllImportAttribute的主要作用是給CLR指示哪個DLL匯出你想要的調用的函數。相關的DLL名稱被作為一個建構函式參數傳遞給DllImportAttribute.

欄位

說明

BestFitMapping

啟用或禁用首選映射。

CallingConvention

指定用於傳遞方法參數的呼叫慣例。 預設值為 WinAPI,該值對應於基於 32 位 Intel 的平台的 __stdcall。

CharSet

控制名稱重整以及將字串參數封送到函數中的方式。 預設值為 CharSet.Ansi。

EntryPoint

指定要調用的 DLL 進入點。

ExactSpelling

控制是否應修改進入點以對應於字元集。 對於不同的程式設計語言,預設值將有所不同。

PreserveSig

控制託管方法簽名是否應轉換成返回 HRESULT 並且傳回值有一個附加的 [out, retval] 參數的非託管簽名。

預設值為 true(不應轉換籤名)。

SetLastError

允許調用方使用 Marshal.GetLastWin32Error API 函數來確定執行該方法時是否發生了錯誤。 在 Visual Basic 中,預設值為 true;在 C# 和 C++ 中,預設值為 false。

ThrowOnUnmappableChar

控制項引發的異常,將無法映射的 Unicode 字元轉換成一個 ANSI"?"字元。

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

1、entrypoint

進入點用於標識函數在 DLL 中的位置。在託管對象中,目標函數的原名或序號進入點將標識跨越互動操作邊界的函數。此外,您可以將進入點映射到一個不同的名稱,這實際上是將函數重新命名。

以下列出了重新命名 DLL 函數的可能原因:

·        避免使用區分大小寫 API 函數名

·        符合現行的命名標準

·        提供採用不同資料類型的函數(通過聲明同一 DLL 函數的多個版本)

·        簡化對包含 ANSI 和 Unicode 版本的 API 的使用

您可以使用 EntryPoint 欄位按名稱或序號指定 DLL 函數。如果函數在方法定義中的名稱與進入點在 DLL 的名稱相同,則不必用 EntryPoint 欄位來顯式地標識函數。否則,使用以下屬性形式之一來指示名稱或序號:

[DllImport("dllname", EntryPoint="MyFunctionname")]
[DllImport("dllname", EntryPoint="#123")]

 指定進入點名稱時,您可以提供一個字串來指示包含進入點的 DLL 的名稱,或者也可以按序號來標識進入點。序號以 # 符號為首碼,如 #1。如果省略此欄位,則公用語言運行庫將使用以DllImportAttribute 標記的 .NET 方法的名稱。

下面的樣本示範如何使用 EntryPoint 欄位將代碼中的 MessageBoxA 替換為 MsgBox

 

using System.Runtime.InteropServices;
public class Win32 {
    [DllImport("user32.dll", EntryPoint="MessageBoxA")]
    public static extern int MsgBox(int hWnd, String text, String caption, uint type);
}     

 2、CharSet(以下來自http://msdn.microsoft.com/zh-cn/library/7b93s42f(v=VS.80).aspx)

CharSet 欄位控制字元串封送處理並確定平台叫用在 DLL 中尋找函數名的方式。本主題將介紹這兩種行為。

對於採用字串參數的函數,有些 API 將匯出它們的兩個版本:窄版本 (ANSI) 和寬版本 (Unicode)。例如,Win32 API 包含 MessageBox 函數的以下進入點名稱:

·        MessageBoxA

提供單位元組字元 ANSI 格式,其特徵是在進入點名稱後附加一個“A”。對 MessageBoxA 的調用始終會以 ANSI 格式封送字串,它常見於 Windows 95 和 Windows 98 平台。

·        MessageBoxW

提供雙位元組字元 Unicode 格式,其特徵是在進入點名稱後附加一個“W”。對 MessageBoxW 的調用始終會以 Unicode 格式封送字串,它常見於 Windows NT、Windows 2000 和 Windows XP 平台。

CharSet 欄位接受以下值:

CharSet.Ansi(預設值)

·        字串封送處理

平台叫用將字串從託管格式 (Unicode) 封送為 ANSI 格式。

·        名稱匹配

ExactSpelling 欄位為 true(它是 Visual Basic 2005 中的預設值)時,平台叫用將只搜尋您指定的名稱。例如,如果指定MessageBox,則平台叫用將搜尋 MessageBox,如果它找不到完全相同的拼字則失敗。

ExactSpelling 欄位為 false(它是 C++ 和 C# 中的預設值)時,平台叫用將首先搜尋未處理的別名 (MessageBox),如果找不到未處理的別名,則將搜尋已處理的名稱 (MessageBoxA)。請注意,ANSI 名稱匹配行為與 Unicode 名稱匹配行為不同。

CharSet.Unicode

·        字串封送處理

平台叫用會將字串從託管格式 (Unicode) 複製為 Unicode 格式。

·        名稱匹配

ExactSpelling 欄位為 true(它是 Visual Basic 2005 中的預設值)時,平台叫用將只搜尋您指定的名稱。例如,如果指定MessageBox,則平台叫用將搜尋 MessageBox,如果它找不到完全相同的拼字則失敗。

ExactSpelling 欄位為 false(它是 C++ 和 C# 中的預設值)時,平台叫用將首先搜尋已處理的名稱 (MessageBoxW),如果找不到已處理的名稱,則將搜尋未處理的別名 (MessageBox)。請注意,Unicode 名稱匹配行為與 ANSI 名稱匹配行為不同。

CharSet.Auto

·        平台叫用在運行時根據目標平台在 ANSI 和 Unicode 格式之間進行選擇。( 針對目標作業系統適當地自動封送字串。在 Windows NT、Windows 2000、Windows XP 和 Windows Server2003 系列上預設值為 System.Runtime.InteropServices.CharSet.Unicode;在 Windows 98
和 Windows Me 上預設值為 System.Runtime.InteropServices.CharSet.Ansi。)

下面的樣本示範用於指定字元集的 MessageBox 函數的三個託管定義。在第一個定義中,通過省略,使 CharSet 欄位預設為 ANSI 字元集。

[DllImport("user32.dll")]

public static extern int MessageBoxA(int hWnd, String text, String caption, uint type);

[DllImport("user32.dll", CharSet=CharSet.Unicode)]

public static extern int MessageBoxW(int hWnd, String text, String caption, uint type);

[DllImport("user32.dll", CharSet=CharSet.Auto)]

public static extern int MessageBox(int hWnd, String text, String caption, uint type);

CharSet.Ansi 和 CharSet.Unicode 的名稱匹配規則大不相同。對於 Ansi 來說,如果將 EntryPoint 設定為“MyMethod”且它存在的話,則返回“MyMethod”。如果 DLL 中沒有“MyMethod”,但存在“MyMethodA”,則返回“MyMethodA”。對於 Unicode 來說則正好相反。如果將 EntryPoint 設定為“MyMethod”且它存在的話,則返回“MyMethodW”。如果 DLL 中不存在“MyMethodW”,但存在“MyMethod”,則返回“MyMethod”。如果使用的是 Auto,則匹配規則與平台有關(在 Windows NT 上為 Unicode,在 Windows 98 上為 Ansi)。如果 ExactSpelling 設定為 true,則只有當 DLL 中存在“MyMethod”時才返回“MyMethod”。

如果 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 兩種版本。

 3、SetLastError

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

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

這會導致 CLR 在每次調用外部方法之後緩衝由 API 函數設定的錯誤。然後,在封裝方法中,可以通過調用類庫的 System.Runtime.InteropServices.Marshal 類型中定義的 Marshal.GetLastWin32Error 方法來擷取緩衝的錯誤值。我的建議是檢查這些期望來自 API 函數的錯誤值,並為這些值引發一個可感知的異常。對於其他所有失敗情況(包括根本就沒意料到的失敗情況),則引發在 System.ComponentModel 命名空間中定義的 Win32Exception,並將 Marshal.GetLastWin32Error 返回的值傳遞給它。

4、CallingConvention

CallingConvention.Cdecl : 調用方清理堆棧。它使您能夠調用具有 varargs 的函數。

CallingConvention.StdCall : 被呼叫者清理堆棧。它是從Managed 程式碼調用非託管函數的預設約定。

CallingConvention 欄位的預設值為 Winapi,而後者又預設為 StdCall 約定。

可能是最不重要的一個 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。

5、ExactSpelling

ExactSpelling 指示是否應修改非託管 DLL 中的進入點的名稱,以與 CharSet 欄位中指定的 CharSet 值相對應。如果為 true,則當 DllImportAttribute.CharSet 欄位設定為 CharSet 的 Ansi 值時,向方法名稱中追加字母 A,當 DllImportAttribute.CharSet 欄位設定為 CharSet 的 Unicode 值時,向方法的名稱中追加字母 W。此欄位的預設值是 false。

6、PreserveSig

PreserveSig指示託管方法簽名不應轉換成返回 HRESULT、並且可能有一個對應於傳回值的附加 [out, retval] 參數的非託管簽名。

 

本文並非原創,參照網路資源整理出來,便於以後方便尋找,鞏固對DllImport屬性的理解。

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.