c#調用DLL

來源:互聯網
上載者:User

     每種程式設計語言調用DLL的方法都不盡相同,在此只對用C#調用DLL的方法進行介紹。首先,您需要瞭解什麼是託管,什麼是非託管。一般可以認為:Unmanaged 程式碼主要是基於win 32平台開發的DLL,activeX的組件,Managed 程式碼是基於.net平台開發的。如果您想深入瞭解託管與非託管的關係與區別,及它們的運行機制,請您自行尋找資料,本檔案在此不作討論。

(一)     調用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”上面添加如下代碼聲明參數傳遞方式枚舉:
/// <summary>

/// 參數傳遞方式枚舉 ,ByValue 表示值傳遞 ,ByRef 表示址傳遞

/// </summary>

public enum ModePass

{

ByValue = 0x0001,

ByRef = 0x0002

}

3.       聲明LoadLibrary、GetProcAddress、FreeLibrary及私人變數hModule和farProc:
/// <summary>

/// 原型是 :HMODULE LoadLibrary(LPCTSTR lpFileName);

/// </summary>

/// <param name="lpFileName">DLL 檔案名稱 </param>

/// <returns> 函數庫模組的控制代碼 </returns>

[DllImport("kernel32.dll")]

static extern IntPtr LoadLibrary(string lpFileName);

/// <summary>

/// 原型是 : FARPROC GetProcAddress(HMODULE hModule, LPCWSTR lpProcName);

/// </summary>

/// <param name="hModule"> 包含需調用函數的函數庫模組的控制代碼 </param>

/// <param name="lpProcName"> 調用函數的名稱 </param>

/// <returns> 函數指標 </returns>

[DllImport("kernel32.dll")]

static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);

/// <summary>

/// 原型是 : BOOL FreeLibrary(HMODULE hModule);

/// </summary>

/// <param name="hModule"> 需釋放的函數庫模組的控制代碼 </param>

/// <returns> 是否已釋放指定的 Dll</returns>

[DllImport("kernel32",EntryPoint="FreeLibrary",SetLastError=true)]

static extern bool FreeLibrary(IntPtr hModule);

/// <summary>

/// Loadlibrary 返回的函數庫模組的控制代碼

/// </summary>

private IntPtr hModule=IntPtr.Zero;

/// <summary>

/// GetProcAddress 返回的函數指標

/// </summary>

private IntPtr farProc=IntPtr.Zero;

4.       添加LoadDll方法,並為了調用時方便,重載了這個方法:

/// <summary>

/// 裝載 Dll

/// </summary>

/// <param name="lpFileName">DLL 檔案名稱 </param>

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方法,並為了調用時方便,也重載了這個方法,方法的具體代碼及注釋如下:
/// <summary>

/// 獲得函數指標

/// </summary>

/// <param name="lpProcName"> 調用函數的名稱 </param>

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+" 這個函數的進入點 "));

}

/// <summary>

/// 獲得函數指標

/// </summary>

/// <param name="lpFileName"> 包含需調用函數的 DLL 檔案名稱 </param>

/// <param name="lpProcName"> 調用函數的名稱 </param>

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方法也進行了重載:
/// <summary>

/// 卸載 Dll

/// </summary>

public void UnLoadDll()

{

FreeLibrary(hModule);

hModule=IntPtr.Zero;

farProc=IntPtr.Zero;

}

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.