如何使用C#呼叫非託管DLL函式
如何使用C#呼叫非託管DLL函式
由於工作需要,學習了GDI+程式設計的一些知識。其中看到了一個比較好的Demo,深入的瞭解後,卻發現自己對如何用C#呼叫非託管DLL函式也有了更好的理解,於是整理了一下,跟大家一起分享。
引用: 用C#來捕獲螢幕的源程式程式碼(Capture.cs)
上面的例子中,應用C#呼叫非託管DLL的函式如下:
在上面這段程式碼中,我將以分別介紹 DllImportAttribute屬性、extern關鍵字 、IntPtr型別 這三個方面,向大家介紹如何應用C#呼叫非託管DLL函式。
C# 如何使用 DllImport Attribute(屬性) 標識 DLL 和函式
[ System.Runtime.InteropServices.DllImportAttribute ( " gdi32.dll " ) ]
private static extern bool BitBlt (
IntPtr hdcDest , // 目標 DC的控制代碼
int nXDest ,
int nYDest ,
int nWidth ,
int nHeight ,
IntPtr hdcSrc , // 源DC的控制代碼
int nXSrc ,
int nYSrc ,
System.Int32 dwRop // 光柵的處理數值
) ;
System.Runtime.InteropServices.DllImportAttribute
從託管程式碼中訪問非託管 DLL 函式之前,需要知道該函式的名稱以及該 DLL 的名稱,然後為 DLL 的非託管函式 編寫 託管定義。
它將用到 static 和 extern 修飾符,此型別的公共靜態成員對於多執行緒操作是安全的。
DllImport 屬性提供非託管 DLL 函式的呼叫資訊。
示例1 簡單DllImportAttribute 應用
[DllImport( " user32.dll " )]
public static extern int MessageBox( int hWnd, String text, String caption, uint type);
示例2 如何將 DllImportAttribute 應用於方法。
[DllImport( " KERNEL32.DLL " ,
EntryPoint = " MoveFileW " ,
SetLastError = true ,
CharSet = CharSet.Unicode,
ExactSpelling = true ,
CallingConvention = CallingConvention.StdCall
)
]
public static extern bool MoveFile(String src, String dst);
引數說明:
EntryPoint 指定要呼叫的 DLL 入口點。
SetLastError 判斷在執行該方法時是否出錯(使用 Marshal.GetLastWin32Error API 函式來確定)。
C#中預設值為 false。
CharSet 控制名稱及函式中字串引數的編碼方式。預設值為 CharSet.Ansi。
ExactSpelling 是否修改入口點以對應不同的字元編碼方式。
CallingConvention 指定用於傳遞方法引數的呼叫約定。預設值為 WinAPI。
該值對應於基於32位Intel平臺的 __stdcall。
BestFitMapping 是否啟用最佳對映功能,預設為 true。
最佳對映功能提供在沒有匹配項時,自動提供匹配的字元。
無法對映的字元通常轉換為預設的“?”。
PreserveSig 託管方法簽名是否轉換成返回 HRESULT,預設值為 true(不應轉換籤名)。
並且返回值有一個附加的 [out, retval] 引數的非託管簽名。
ThrowOnUnmappableChar 控制對轉換為 ANSI '?' 字元的不可對映的 Unicode 字元引發異常。
C# 關鍵字 extern 的使用
public static extern int MyMethod(int x);
外部修飾符 extern 用於指示外部實現方法,常與 DllImport 屬性一起使用(DllImport 屬性提供非託管 DLL 函式的呼叫資訊)。
若將 abstract 和 extern 修飾符一起使用來修改同一成員是錯誤的。extern 將方法在 C# 程式碼的外部實現,而 abstract 意味著在此類中未提供此方法的實現。
因為外部方法宣告不提供具體實現,所以沒有方法體;
此方法宣告只是以一個分號結束,並且在簽名後沒有大括號{ }。
示例3 接收使用者輸入的字串並顯示在訊息框中
程式從 User32.dll 庫匯入的 MessageBox 方法。
using System.Runtime.InteropServices;
class MyClass
{
[DllImport( " User32.dll " )]
public static extern int MessageBox( int h, string m, string c, int type);
public static int Main()
{
string myString;
Console.Write( " Enter your message: " );
myString = Console.ReadLine();
return MessageBox( 0 , myString, " My Message Box " , 0 );
}
}
執行結果: 輸入"Hello"文字後,螢幕上將彈出一個包含該文字的訊息框。
Enter your message: Hello
示例4 呼叫DLL進行計算
該示例使用兩個檔案 CM.cs 和 Cmdll.c 來說明 extern。
C 檔案是從 C# 程式中呼叫的外部 DLL。
使用 Visual C++ 命令列將 Cmdll.c 編譯為 DLL:
cl /LD /MD Cmdll.c
檔案:Cmdll.c
// compile with: /LD /MD
int __declspec(dllexport) MyMethod( int i)
{
return i * 10 ;
}
使用命令列編譯 CM.cs:
csc CM.cs
這將建立可執行檔案 CM.exe。
檔案:CM.cs
using System;
using System.Runtime.InteropServices;
public class MyClass
{
[DllImport( " Cmdll.dll " )]
public static extern int MyMethod( int x);
public static void Main()
{
Console.WriteLine( " MyMethod() returns {0}. " , MyMethod( 5 ));
}
}
執行此程式,MyMethod 將值 5 傳遞到 DLL 檔案,該檔案將此值乘以 10 返回。
執行結果:MyMethod() returns 50.
IntPtr 型別的說明
對於平臺呼叫,應讓引數為 IntPtr 型別,而不是 String 型別。使用 System.Runtime.InteropServices.Marshal 類所提供的方法,可將型別手動轉換為字串並手動將其釋放。
IntPtr 型別被設計成整數,其大小適用於特定平臺。
在 32 位硬體和作業系統中將是 32 位;
在 64 位硬體和作業系統中將是 64 位。
IntPtr 型別由支援指標的語言使用,並作為在支援與不支援指標的語言間引用資料的一種通用方式。它也可用於保持控制代碼。例如,IntPtr 的例項廣泛地用在 System.IO.FileStream 類中來保持檔案控制代碼。
IntPtr 型別符合 CLS,而 UIntPtr 型別卻不符合。只有 IntPtr 型別可用在公共語言執行庫中。此型別實現 ISerializable 介面。