標籤:
1 DLLImport的使用
using System;
using System.Runtime.InteropServices; //命名空間
class Example
{
//用DllImport 匯入Win32的MessageBox函數
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type);
//方法被聲明為 static。這是 P/Invoke 方法所要求的,因為在該 Windows API 中沒有//一致的執行個體概念。接下來,還要注意該方法被標記為 extern。這是提示編譯器該方法是通//過一個從 DLL 匯出的函數實現的,因此不需要提供方法體。
static void Main()
{
// Call the MessageBox function using platform invoke.
MessageBox(new IntPtr(0), "Hello World!", "Hello Dialog", 0);
}
}
使用非託管DLL函數並不困難,下面我們可以詳細的瞭解上面的代碼的含義。首先介紹什麼是Managed 程式碼,什麼是Unmanaged 程式碼。然後再詳細介紹DLLImport的使用方法和各欄位的意義。
2 Managed 程式碼 (managed code)
.NET Framework的核心是其運行庫的執行環境,稱為公用語言運行庫(CLR)或.NET運行庫。通常將在CLR的控制下啟動並執行代碼稱為Managed 程式碼(managed code)。
運行庫環境(而不是直接由作業系統)執行的代碼。Managed 程式碼應用程式可以獲得公用語言運行庫服務,例如自動記憶體回收、運行庫類型檢查和安全支援等。這些服務協助提供獨立於平台和語言的、統一的Managed 程式碼應用程式行為。
Managed 程式碼是可以使用20多種支援Microsoft .NET Framework的進階語言編寫的代碼,它們包括:C#, J#, Microsoft Visual Basic .NET, Microsoft JScript .NET, 以及C++。所有的語言共用統一的類庫集合,並能被編碼成為中繼語言(IL)。運行庫編譯器(runtime-aware ompiler)在託管執行環境下編譯中繼語言(IL)使之成為本地可執行檔代碼,並使用數組邊界和索引檢查,異常處理,記憶體回收等手段確保類型的安全。
在託管執行環境中使用Managed 程式碼及其編譯,可以避免許多典型的導致安全黑洞和不穩定程式的編程錯誤。同樣,許多不可靠的設計也自動的被增強了安全 性,例如型別安全檢查,記憶體管理和釋放無效對象。程式員可以花更多的精力關注程式的應用邏輯設計並可以減少代碼的編寫量。這就意味著更短的開發時間和更健 壯的程式。
簡單點說,Managed 程式碼是microsoft的中繼語言,他主要的作用是在.NET FRAMEWORK的CLR執行代碼前去編譯原始碼,也就是說Managed 程式碼充當著翻譯的作用,原始碼在運行時分為兩個階段:
1.原始碼編譯為Managed 程式碼;(所以原始碼可以有很多種,如VB,C#,J#)
2.Managed 程式碼編譯為microsoft系統的.net平台專用檔案(如類庫、可執行檔等)。
2.1 Unmanaged 程式碼 (unmanaged code)
在公用語言運行庫環境的外部,由作業系統直接執行的代碼。Unmanaged 程式碼必須提供自己的記憶體回收、類型檢查、安全支援等服務;它與Managed 程式碼不同,後者從公用語言運行庫中獲得這些服務。
.net中Managed 程式碼的含義
2.2 什麼是託管?託管是什麼意思?
Managed 程式碼就是基於.net中繼資料格式的代碼,運行於.net平台之上,所有的與作業系統的交換有.net來完成,就像是把這些功能委託給.net,所以稱之為Managed 程式碼。Unmanaged 程式碼則反之。
舉個例子l
Vc.net還可以使用mfc,atl來編寫程式,他們基於MFC或者ATL,而不是.NET,所以是Unmanaged 程式碼,如果基於.net比如C#,VB.net則是Managed 程式碼
Unmanaged 程式碼是指.NET解釋不了的
簡單的說,Managed 程式碼的話,.net可以自動釋放資料,Unmanaged 程式碼需要手動釋放資料.
什麼是託管C++
託管是.NET的一個專門概念,它倡導一種新的編程理念,因此我們完全可以把“託管”視為“.NET”。由託管概念所引發的C++應用程式套件組合括Managed 程式碼、管理的資料和託管類三個組成部分。
Managed 程式碼
.Net環境提供了許多核心的運行(RUNTIME)服務,比如異常處理和安全性原則。為了能使用這些服務,必須要給運行環境提供一些資訊代碼(元數 據),這種代碼就是Managed 程式碼。所有的C#、VB.NET、JScript.NET預設時都是託管的,但Visual C++預設時不是託管的,必須在編譯器中使用命令列選項(/CLR)才能產生Managed 程式碼。
管理的資料
與Managed 程式碼密切相關的是管理的資料。管理的資料是由公用語言啟動並執行記憶體回收行程進行分配和釋放的資料。預設情況下,C#、Visual Basic 和 JScript.NET 資料是管理的資料。不過,通過使用特殊的關鍵字,C# 資料可以被標記為非管理的資料。Visual C++資料在預設情況下是非管理的資料,即使在使用 /CLR 開關時也不是託管的。
託管類
儘管Visual C++資料在預設情況下是非管理的資料,但是在使用C++的託管擴充時,可以使用“__gc”關鍵字將類標記為託管類。就像該名稱所顯示的那樣,它表示類實 例的記憶體由記憶體回收行程管理。另外,一個託管類也完全可以成為 .NET 架構的成員,由此可以帶來的好處是,它可以與其他語言編寫的類正確地進行相互操作,如託管的C++類可以從Visual Basic類繼承等。但同時也有一些限制,如託管類只能從一個基類繼承等。
2.3 Managed 程式碼如何調用Unmanaged 程式碼(c sharp如何調用c++代碼)?
兩種常用的做法:
下載:
http://download.microsoft.com/download/f/2/7/f279e71e-efb0-4155-873d-5554a0608523/CLRInsideOut2007_01.exe
1. COM interop
具體操作:
a. 用atl寫com服務程式
b. 使用Tlbimp將atl寫的com程式轉換成 COM DLL
用如下命令:
tlbimp 你寫的com.dll
tlbimp是 .NET Framework SDK中附帶的類型庫匯入程式。用這個命令即是把產生一個非託管com dll的託管封裝。
c. 託管用戶端非常簡單
直接new一下,然後調用對應的方法即可。
2. P/Invoke
a. 在託管用戶端增加一條 DllImport語句和一個方法的調用。
介紹一個P/Invoke網站,http://pinvoke.net/
這個網站主要是一個wiki,允許開發人員發現,編輯,增加PInvoke的簽名,使用者自訂類型和從Managed 程式碼(指c#和VB.net開發語言)訪問win32和其他非託管api的資訊。
世界各地的.Net開發人員可以很容易分享自己有價值的東西給社區,
2.4 Managed 程式碼和Unmanaged 程式碼效率的對比
參加原文 來自http://www.cnblogs.com/loverswordsman/articles/1367131.html
更詳細的資訊http://www.cnblogs.com/loverswordsman/articles/1367131.html
3 DllImportAttribute 的欄位
在對Managed 程式碼進行 P/Invoke 調用時,DllImportAttribute 類型扮演著重要的角色。DllImportAttribute 的主要作用是給 CLR 指示哪個 DLL 匯出您想要調用的函數。相關 DLL 的名稱被作為一個建構函式參數傳遞給 DllImportAttribute。
下表列出了所有與平台叫用相關的特性欄位。 對於每個欄位,下表都將包含其預設值,並且會提供一個連結,用於擷取有關如何使用這些欄位定義非託管 DLL 函數的資訊。http://msdn.microsoft.com/zh-cn/library/w4byd5y4.aspx
欄位 |
說明 |
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。http://www.360doc.com/content/11/0105/09 /3877783_84071078.shtml
3.1 entrypoint
進入點用於標識函數在 DLL 中的位置。在託管對象中,目標函數的原名或序號進入點將標識跨越互動操作邊界的函數。此外,您可以將進入點映射到一個不同的名稱,這實際上是將函數重新命名。
以下列出了重新命名 DLL 函數的可能原因:
· 避免使用區分大小寫 API 函數名
· 符合現行的命名標準
· 提供採用不同資料類型的函數(通過聲明同一 DLL 函數的多個版本)
· 簡化對包含 ANSI 和 Unicode 版本的 API 的使用
您可以使用 DllImportAttribute.EntryPoint 欄位按名稱或序號指定 DLL 函數。如果函數在方法定義中的名稱與進入點在 DLL 的名稱相同,則不必用 EntryPoint 欄位來顯式地標識函數。否則,使用以下屬性形式之一來指示名稱或序號:
l [DllImport("dllname", EntryPoint="Functionname")]
l [DllImport("dllname", EntryPoint="#123")]
l 指定進入點名稱時,您可以提供一個字串來指示包含進入點的 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);
}
3.2 CharSet
以下來自http://msdn.microsoft.com/zh-cn/library/7b93s42f(v=VS.80).aspx
DllImportAttribute.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 格式。
· 名稱匹配
在 DllImportAttribute.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 格式之間進行選擇。
下面的樣本示範用於指定字元集的 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.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 返回的值傳遞給它。
3.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。
3.5 ExactSpelling
ExactSpelling 指示是否應修改非託管 DLL 中的進入點的名稱,以與 CharSet 欄位中指定的 CharSet 值相對應。如果為 true,則當 DllImportAttribute.CharSet 欄位設定為 CharSet 的 Ansi 值時,向方法名稱中追加字母 A,當 DllImportAttribute.CharSet 欄位設定為 CharSet 的 Unicode 值時,向方法的名稱中追加字母 W。此欄位的預設值是 false。
3.6 PreserveSig
PreserveSig指示託管方法簽名不應轉換成返回 HRESULT、並且可能有一個對應於傳回值的附加 [out, retval] 參數的非託管簽名。
4 參數類型
l 數值型直接用對應的就可。(DWORD -> int , WORD -> Int16)
l API中字串指標類型 -> .net中string
l API中控制代碼 (dWord) -> .net中IntPtr
l API中結構 -> .net中結構或者類。注意這種情況下,要先用StructLayout特性限定聲明結構或類
5 Win32 API 中幾個常用的 DLL
DLL |
內容說明 |
GDI32.dll |
用於裝置輸出的圖形裝置介面 (GDI) 函數,例如用於繪圖和字型管理的函數。 |
Kernel32.dll |
用於記憶體管理和資源處理的低層級作業系統函數。 |
User32.dll |
用於訊息處理、計時器、菜單和通訊的 Windows 管理函數。 |
6 wince 中的DLL
分類: 知識積累
【轉帖】C# DllImport 系統調用使用詳解 Managed 程式碼的介紹 EntryPoint的使用