C#呼叫外部DLL
(一) 呼叫DLL中的非託管函式一般方法
首先,應該在C#語言源程式中宣告外部方法,其基本形式是:
[DLLImport(“DLL檔案”)]
修飾符 extern 返回變數型別 方法名稱 (引數列表)
其中:
DLL檔案:包含定義外部方法的庫檔案。
修飾符: 訪問修飾符,除了abstract以外在宣告方法時可以使用的修飾符。
返回變數型別:在DLL檔案中你需呼叫方法的返回變數型別。
方法名稱:在DLL檔案中你需呼叫方法的名稱。
引數列表:在DLL檔案中你需呼叫方法的列表。
注意:需要在程式宣告中使用System.Runtime.InteropServices名稱空間。
DllImport只能放置在方法宣告上。
DLL檔案必須位於程式當前目錄或系統定義的查詢路徑中(即:系統環境變數中Path所設定的路徑)。
返回變數型別、方法名稱、引數列表一定要與DLL檔案中的定義相一致。
若要使用其它函式名,可以使用EntryPoint屬性設定,如:
[DllImport("user32.dll", EntryPoint="MessageBoxA")]
static extern int MsgBox(int hWnd, string msg, string caption, int type);
其它可選的 DllImportAttribute 屬性:
CharSet 指示用在入口點中的字符集,如:CharSet=CharSet.Ansi;
SetLastError 指示方法是否保留 Win32"上一錯誤",如:SetLastError=true;
ExactSpelling 指示 EntryPoint 是否必須與指示的入口點的拼寫完全匹配,如:ExactSpelling=false;
PreserveSig指示方法的簽名應當被保留還是被轉換, 如:PreserveSig=true;
CallingConvention指示入口點的呼叫約定, 如:CallingConvention=CallingConvention.Winapi;
此外,關於“資料封送處理”及“封送數字和邏輯標量”請參閱其它一些文章[2]。
C#例子:
1. 啟動VS.NET,新建一個專案,專案名稱為“Tzb”,模板為“Windows 應用程式”。
2. 在“工具箱”的“ Windows 窗體”項中雙擊“Button”項,向“Form1”窗體中新增一個按鈕。
3. 改變按鈕的屬性:Name為 “B1”,Text為 “用DllImport呼叫DLL彈出提示框”,並將按鈕B1調整到適當大小,移到適當位置。
4. 在類檢視中雙擊“Form1”,開啟“Form1.cs”程式碼檢視,在“namespace Tzb”上面輸入“using System.Runtime.InteropServices;”,以匯入該名稱空間。
5. 在“Form1.cs[設計]”檢視中雙擊按鈕B1,在“B1_Click”方法上面使用關鍵字 static 和 extern 宣告方法“MsgBox”,將 DllImport 屬性附加到該方法,這裡我們要使用的是“user32.dll”中的“MessageBoxA”函式,具體程式碼如下:
[DllImport("user32.dll", EntryPoint="MessageBoxA")]
static extern int MsgBox(int hWnd, string msg, string caption, int type);
然後在“B1_Click”方法體內新增如下程式碼,以呼叫方法“MsgBox”:
MsgBox(0," 這就是用 DllImport 呼叫 DLL 彈出的提示框哦! "," 挑戰杯 ",0x30);
6. 按“F5”執行該程式,並點選按鈕B1,便彈出如下提示框:
(二) 動態裝載、呼叫DLL中的非託管函式
在上面已經說明了如何用DllImport呼叫DLL中的非託管函式,但是這個是全域性的函式,假若DLL中的非託管函式有一個靜態變數S,每次呼叫這個函式的時候,靜態變數S就自動加1。結果,當需要重新計數時,就不能得出想要的結果。下面將用例子說明:
1. DLL的建立
1) 啟動Visual C++ 6.0;
2) 新建一個“Win32 Dynamic-Link Library”工程,工程名稱為“Count”;
3) 在“Dll kind”選擇介面中選擇“A simple dll project”;
4) 開啟Count.cpp,新增如下程式碼:
// 匯出函式,使用“ _stdcall ” 標準呼叫
extern "C" _declspec(dllexport)int _stdcall count(int init);
int _stdcall count(int init)
{//count 函式,使用引數 init 初始化靜態的整形變數 S ,並使 S 自加 1 後返回該值
static int S=init;
S++;
return S;
}
5) 按“F7”進行編譯,得到Count.dll(在工程目錄下的Debug資料夾中)。
2. 用DllImport呼叫DLL中的count函式
1) 開啟專案“Tzb”,向“Form1”窗體中新增一個按鈕。
2) 改變按鈕的屬性:Name為 “B2”,Text為 “用DllImport呼叫DLL中count函式”,並將按鈕B1調整到適當大小,移到適當位置。
3) 開啟“Form1.cs”程式碼檢視,使用關鍵字 static 和 extern 宣告方法“count”,並使其具有來自 Count.dll 的匯出函式count的實現,程式碼如下:
[DllImport("Count.dll")]
static extern int count(int init);
4) 在“Form1.cs[設計]”檢視中雙擊按鈕B2,在“B2_Click”方法體內新增如下程式碼:
MessageBox.Show(" 用 DllImport 呼叫 DLL 中的 count 函式, n 傳入的實參為 0 ,得到的結果是: "+count(0).ToString()," 挑戰杯 ");
MessageBox.Show(" 用 DllImport 呼叫 DLL 中的 count 函式, n 傳入的實參為 10 ,得到的結果是: "+count(10).ToString()+"n 結果可不是想要的 11 哦!!! "," 挑戰杯 ");
MessageBox.Show(" 所得結果表明: n 用 DllImport 呼叫 DLL 中的非託管 n 函式是全域性的、靜態的函式!!! "," 挑戰杯 ");
5) 把Count.dll複製到專案“Tzb”的binDebug資料夾中,按“F5”執行該程式,並點選按鈕B2,便彈出如下三個提示框:
第1個提示框顯示的是呼叫“count(0)”的結果,第2個提示框顯示的是呼叫“count(10)”的結果,由所得結果可以證明“用DllImport呼叫DLL中的非託管函式是全域性的、靜態的函式”。所以,有時候並不能達到我們目的,因此我們需要使用下面所介紹的方法:C#動態呼叫DLL中的函式。
3. C#動態呼叫DLL中的函式
因為C#中使用DllImport是不能像動態load/unload assembly那樣,所以只能藉助API函式了。在kernel32.dll中,與動態庫呼叫有關的函式包括[3]:
①LoadLibrary(或MFC 的AfxLoadLibrary),裝載動態庫。
②GetProcAddress,獲取要引入的函式,將符號名或標識號轉換為DLL內部地址。
③FreeLibrary(或MFC的AfxFreeLibrary),釋放動態連結庫。
它們的原型分別是:
HMODULE LoadLibrary(LPCTSTR lpFileName);
FARPROC GetProcAddress(HMODULE hModule, LPCWSTR lpProcName);
BOOL FreeLibrary(HMODULE hModule);
現在,我們可以用IntPtr hModule=LoadLibrary(“Count.dll”);來獲得Dll的控制程式碼,用IntPtr farProc=GetProcAddress(hModule,”_count@4”);來獲得函式的入口地址。
但是,知道函式的入口地址後,怎樣呼叫這個函式呢?因為在C#中是沒有函式指標的,沒有像C++那樣的函式指標呼叫方式來呼叫函式,所以我們得藉助其它方法。經過研究,發現我們可以通過結合使用System.Reflection.Emit及System.Reflection.Assembly裡的類和函式達到我們的目的。為了以後使用方便及實現程式碼的複用,我們可以編寫一個類。
1) dld類的編寫:
1. 開啟專案“Tzb”,開啟類檢視,右擊“Tzb”,選擇“新增”-->“類”,類名設定為“dld”,即dynamic loading dll 的每個單詞的開頭字母。
2. 新增所需的名稱空間及宣告引數傳遞方式列舉:
using System.Runtime.InteropServices; // 用 DllImport 需用此 名稱空間
using System.Reflection; // 使用 Assembly 類需用此 名稱空間
using System.Reflection.Emit; // 使用 ILGenerator 需用此 名稱空間
在“public class dld”上面新增如下程式碼宣告引數傳遞方式列舉:
///
/// 引數傳遞方式列舉 ,ByValue 表示值傳遞 ,ByRef 表示址傳遞
///
public enum ModePass
{
ByValue = 0x0001,
ByRef = 0x0002
}
3. 宣告LoadLibrary、GetProcAddress、FreeLibrary及私有變數hModule和farProc:
///
/// 原型是 :HMODULE LoadLibrary(LPCTSTR lpFileName);
///
/// DLL 檔名
///
[DllImport("kernel32.dll")]
static extern IntPtr LoadLibrary(string lpFileName);
///
/// 原型是 : FARPROC GetProcAddress(HMODULE hModule, LPCWSTR lpProcName);
///
/// 包含需呼叫函式的函式庫模組的控制程式碼
/// 呼叫函式的名稱
///
[DllImport("kernel32.dll")]
static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
///
/// 原型是 : BOOL FreeLibrary(HMODULE hModule);
///
/// 需釋放的函式庫模組的控制程式碼
///
[DllImport("kernel32",EntryPoint="FreeLibrary",SetLastError=true)]
static extern bool FreeLibrary(IntPtr hModule);
///
/// Loadlibrary 返回的函式庫模組的控制程式碼
///
private IntPtr hModule=IntPtr.Zero;
///
/// GetProcAddress 返回的函式指標
///
private IntPtr farProc=IntPtr.Zero;
4. 新增LoadDll方法,併為了呼叫時方便,過載了這個方法:
///
/// 裝載 Dll
///
/// DLL 檔名
public void LoadDll(string lpFileName)
{
hModule=LoadLibrary(lpFileName);
if(hModule==IntPtr.Zero)
throw(new Exception(" 沒有找到 :"+lpFileName+"." ));
}
若已有已裝載Dll的控制程式碼,可以使用LoadDll方法的第二個版本:
public void LoadDll(IntPtr HMODULE)
{
if(HMODULE==IntPtr.Zero)
throw(new Exception(" 所傳入的函式庫模組的控制程式碼 HMODULE 為空 ." ));
hModule=HMODULE;
}
5. 新增LoadFun方法,併為了呼叫時方便,也過載了這個方法,方法的具體程式碼及註釋如下:
///
/// 獲得函式指標
///
/// 呼叫函式的名稱
public void LoadFun(string lpProcName)
{ // 若函式庫模組的控制程式碼為空,則丟擲異常
if(hModule==IntPtr.Zero)
throw(new Exception(" 函式庫模組的控制程式碼為空 , 請確保已進行 LoadDll 操作 !"));
// 取得函式指標
farProc = GetProcAddress(hModule,lpProcName);
// 若函式指標,則丟擲異常
if(farProc==IntPtr.Zero)
throw(new Exception(" 沒有找到 :"+lpProcName+" 這個函式的入口點 "));
}
///
/// 獲得函式指標
///
/// 包含需呼叫函式的 DLL 檔名
/// 呼叫函式的名稱
public void LoadFun(string lpFileName,string lpProcName)
{ // 取得函式庫模組的控制程式碼
hModule=LoadLibrary(lpFileName);
// 若函式庫模組的控制程式碼為空,則丟擲異常
if(hModule==IntPtr.Zero)
throw(new Exception(" 沒有找到 :"+lpFileName+"." ));
// 取得函式指標
farProc = GetProcAddress(hModule,lpProcName);
// 若函式指標,則丟擲異常
if(farProc==IntPtr.Zero)
throw(new Exception(" 沒有找到 :"+lpProcName+" 這個函式的入口點 "));
}
6. 新增UnLoadDll及Invoke方法,Invoke方法也進行了過載:
///
/// 解除安裝 Dll
///
public void UnLoadDll()
{
FreeLibrary(hModule);
hModule=IntPtr.Zero;
farProc=IntPtr.Zero;
}
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-589267/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- C# 生成DLL 並 呼叫C#
- C#呼叫 C++的DLLC#C++
- C++呼叫 c#生成的dllC++C#
- C++呼叫C#的動態庫dllC++C#
- c#呼叫C++DLL EntryPointNotFoundException 找不到入口點C#C++Exception
- P/Invoke之C#呼叫動態連結庫DLLC#
- C# 呼叫 C++ 生成的 dll 關鍵實現部分C#C++
- 如何建立Electron + Vue3專案, 並呼叫C# dllVueC#
- C# 客戶端程式呼叫外部程式的三種實現C#客戶端
- exe呼叫DLL的方式
- 外部函式的呼叫函式
- Nodejs如何呼叫Dll模組NodeJS
- php 呼叫dll 裡面的方法PHP
- VS2012生成C的dll並呼叫以及Python呼叫C的DLLPython
- c# 呼叫 C++ dll 傳入傳出型別對應說明(轉)C#C++型別
- NX二次開發-C#使用DllImport呼叫libufun.dll裡的UF函式(反編譯.net.dll)呼叫loop等UF函式C#Import函式編譯OOP
- httprunner yml 呼叫外部函式HTTP函式
- Java魔法堂:呼叫外部程式Java
- C# 程式修改dll引用路徑C#
- C#呼叫PythonC#Python
- C#呼叫pydC#
- java呼叫DLL,呼叫北洋印表機列印二維碼標籤Java
- JAVA呼叫C語言下的DLL檔案JavaC語言
- VS中呼叫DLL動態庫的方法
- java中呼叫dll檔案的步驟Java
- 實現通過COM元件方式實現java呼叫C#寫的DLL檔案的完整demo元件JavaC#
- C#配置程式引用的dll的位置C#
- 外部js呼叫vue的methods中的方法JSVue
- QT6編寫外部庫並呼叫QT
- [Golang]呼叫外部shell程式處理檔案Golang
- c# 反射呼叫方法C#反射
- 使用使用rundll32 呼叫指定dll的方法
- C#中Emgucv呼叫HalconC#
- C#呼叫python的方法C#Python
- CefSharp ——js呼叫c#方法JSC#
- xLua中C#呼叫LuaC#
- xLua中Lua呼叫C#C#
- C#/.net程式呼叫pythonC#Python
- C# 呼叫Python程式碼C#Python