理解DllImportAttribute中的屬性


     在對托管代碼進行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 調用時,也會面臨其他的挑戰 — 處理托管代碼中 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 : 被調用方清理堆棧。它是從托管代碼調用非托管函數的默認約定。

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屬性的理解。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM