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


 

C#.Net調用基本格式:

[DLLImport(“DLL文件路徑”)]
修飾符 extern 返回值類型 方法名稱(參數列表)

如:

[DllImport("kernel32.dll", SetLastError = true, EntryPoint = "SetLocalTime")] public static extern int SetSystemTime(ref SystemTime lpSystemTime);

 

PS:

1、DLL文件必須位於程序當前目錄或系統定義的查詢路徑中(即:系統環境變量中Path所設置的路徑)。
2、DLLImport會按照順序去查找DLL文件(程序當前目錄>System32目錄>環境變量Path所設置路徑)。
3、返回類型變量、方法名稱、參數列表一定要與DLL文件中的定義相一致。
4、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如下:

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 返回變量類型 方法名稱 (參數列表)

 

其中:

  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;

 
        

此外,關於“數據封送處理”及“封送數字和邏輯標量”請參閱其它一些文章。

 

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。

    結果,當需要重新計數時,就不能得出想要的結果。下面將用例子說明:

 

    2.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.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中的函數。

 

     2.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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM