C#直接使用DllImport外部Dll的方法

C#.Net調用基本格式:
[DLLImport(「DLL文件路徑」)]
修飾符 extern 返回值類型 方法名稱(參數列表) 如: 
[DllImport("kernel32.dll", SetLastError = true, EntryPoint = "SetLocalTime")] public static extern int SetSystemTime(ref SystemTime lpSystemTime);

PS:
一、DLL文件必須位於程序當前目錄或系統定義的查詢路徑中(即:系統環境變量中Path所設置的路徑)。
二、DLLImport會按照順序去查找DLL文件(程序當前目錄>System32目錄>環境變量Path所設置路徑)。
三、返回類型變量、方法名稱、參數列表必定要與DLL文件中的定義相一致。
四、Asp.net DLLImport路徑----使用第三方非託管的DLL(Charles.dll)組件的時候,當把Charles.dll拷貝到Bin目錄下,提示仍然提示仍然找不到該dll.(而這樣[DLLImport(@「C:\ProgramDir\Charles.dll」)]能夠正常加載)。Asp.Net Team的官方解決方案以下:

首先須要確認引用了哪些組件?哪些是託管的?那些是非託管的?
託管的很方便,直接被使用的須要引用,間接使用的須要拷貝到Bin目錄下。非託管的就特殊處理(實際上你拷貝到bin是沒有任何做用的,由於CLR會把文件拷貝到一個臨時目錄下,而後在那運行Web,而CLR只會拷貝託管文件,這就是爲何把非託管的DLL放到bin目錄下仍然提示找不到該模塊)。

解決方案:首先在服務器上創建一個新建的目錄,假設是(C:\ProgramDir\WinDLL\).而後在環境變量中,給Path變量添加這個目錄,最後把非託管的DLL文件都拷貝到該目錄下。或者更乾脆把DLL放到System32目錄中。對於本身部署的應用程序,這樣的確能很好的解決問題。然而若是咱們用的是虛擬空間,咱們有沒有辦法吧註冊Path變量或者把咱們本身的DLL拷貝System32目錄下。同時咱們也不必定知道咱們DLL的物理路徑.編程

DLLImport裏面只能用字符常量,而不能使用Server.MapPath來確認物理絕對路徑。

這樣的話咱們須要動態的取得咱們DLL的物理路徑(Server.MapPath),並經過API來取得DLL裏面的函數(先加載LoadLibrary後得到函數地址GetProcAddress)。相關的API以下:api

Public Class CustomDLLInvoke { [DLLImport(「kernel32.dll」)] private extern static IntPtr LoadLibrary(string path); [DLLImport(kernel32.dll)] private extern static IntPtr GetProcAddress(IntPtr lib,String funcName); [DLLImport(Kernel32.dll)] private extern static bool FreeLibrary(IntPtr lib); private IntPtr MLib; public CustomDLLInvoke(string dllPath) {MLib=LoadLibrary(DLLPath)} ~CustomDLLInvoke(){FreeLibrary(MLib);} public Delegate Invoke(string APIName,Type t) {IntPtr api=GetProAddress(MLib,APIName);return (Delegate)Marshal.GetDelegateForFunctionPointer(api,t);} }

 

C#調用DLL服務器

  每種編程語言調用DLL的方法都不盡相同,在此只對用C#調用DLL的方法進行介紹。首先,您須要瞭解什麼是託管,什麼是非託管。通常能夠認爲:非託管代碼主要是基於win 32平臺開發的DLL,activeX的組件,託管代碼是基於.net平臺開發的。若是您想深刻了解託管與非託管的關係與區別,及它們的運行機制,請您自行查找資料,本文件在此不做討論。

(一)     調用DLL中的非託管函數通常方法編程語言

首先,應該在C#語言源程序中聲明外部方法,其基本形式是:函數

[DLLImport(「DLL文件」)]工具

修飾符 extern 返回變量類型 方法名稱 (參數列表)post

其中:spa

DLL文件:包含定義外部方法的庫文件。.net

修飾符: 訪問修飾符,除了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; }

 

 
***********轉載:https://blog.csdn.net/u011981242/article/details/52622923/
相關文章
相關標籤/搜索