1. 程式人生 > >C#呼叫DLL庫的方法

C#呼叫DLL庫的方法

net平臺上,呼叫dll檔案有2種含義1、呼叫託管dll,即你使用。net平臺開發的dll,屬於託管程式碼2、呼叫非託管dll,即傳統的dll,一般是C++,VB,DELPHI等等開發出來的,屬於非託管程式碼。從你的意思中看出來你現在是呼叫託管的dll,方法是 “在解決方案管理器” - “解決方案”(或專案) 中的任意地方, 右鍵“新增引用”,“瀏覽”,選擇你需要呼叫的dll檔案,確定即可,該dll會自動複製到bin目錄,打包時也會自動複製到你釋出的地方。新增完了引用,現在如何呼叫呢?如果有名稱空間則引入名稱空間,比如你的y。dll裡面,是a名稱空間,有一個b類,然後有一個無引數靜態方法c那麼呼叫方法就是a.b.c(),跟你普通的使用類是一樣的然後是非託管dll需要新增dll的名稱,以及方法,也就是你所用到的dll的每個方法都需要新增一次,[DllImport("msvcrt.dll")]    public static extern int puts(string c);

*******************************************************************************************

C#託管程式碼與C++非託管程式碼互相呼叫一(C#呼叫C++程式碼&.net 程式碼安全)

在最近的專案中,牽涉到專案原始碼保密問題,由於程式碼是C#寫的,容易被反編譯,因此決定抽取核心演算法部分使用C++編寫,C++到目前為止好像還不能被很好的反編譯,當然如果你是反彙編高手的話,也許還是有可能反編譯。這樣一來,就涉及C#託管程式碼與C++非託管程式碼互相呼叫,於是調查了一些資料,順便與大家分享一下:

原始碼下載

一. C# 中靜態呼叫C++動態連結


   1. 建立VC工程CppDemo,建立的時候選擇Win32 Console(dll),選擇Dll。

   2. 在DllDemo.cpp檔案中新增這些程式碼。

Code
extern"C" __declspec(dllexport) int Add(int a,int b)
{

return a+b;
}

   3. 編譯工程。

   4. 建立新的C#工程,選擇Console應用程式,建立測試程式InteropDemo
   5. 在Program.cs中新增引用:using System.Runtime.InteropServices;

   6. 在pulic class Program新增如下程式碼:


Code
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace InteropDemo
{
class Program
   {
       [DllImport("CppDemo.dll", EntryPoint = "Add", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl)]
publicstaticexternint Add(int a, int b); //DllImport請參照MSDN

staticvoid Main(string[] args)
       {
           Console.WriteLine(Add(1, 2));
           Console.Read();
       }
   }
}

  好了,現在您可以測試Add程式了,是不是可以在C# 中呼叫C++動態連結了,當然這是靜態呼叫,需要將CppDemo編譯生成的Dll放在DllDemo程式的Bin目錄下

二. C# 中動態呼叫C++動態連結

在第一節中,講了靜態呼叫C++動態連結,由於Dll路徑的限制,使用的不是很方便,C#中我們經常通過配置動態的呼叫託管Dll,例如常用的一些設計模式:Abstract Factory, Provider, Strategy模式等等,那麼是不是也可以這樣動態呼叫C++動態連結呢?只要您還記得在C++中,通過LoadLibrary, GetProcess, FreeLibrary這幾個函式是可以動態呼叫動態連結的(它們包含在kernel32.dll中),那麼問題迎刃而解了,下面我們一步一步實驗

   1.   將kernel32中的幾個方法封裝成本地呼叫類NativeMethod

Code
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace InteropDemo
{
publicstaticclass NativeMethod
   {
       [DllImport("kernel32.dll", EntryPoint = "LoadLibrary")]
publicstaticexternint LoadLibrary(
           [MarshalAs(UnmanagedType.LPStr)] string lpLibFileName);

       [DllImport("kernel32.dll", EntryPoint = "GetProcAddress")]
publicstaticextern IntPtr GetProcAddress(int hModule,
           [MarshalAs(UnmanagedType.LPStr)] string lpProcName);

       [DllImport("kernel32.dll", EntryPoint = "FreeLibrary")]
publicstaticexternbool FreeLibrary(int hModule);
   }
}


   2. 使用NativeMethod類動態讀取C++Dll,獲得函式指標,並且將指標封裝成C#中的委託。原因很簡單,C#中已經不能使用指標了,如下          
           int hModule = NativeMethod.LoadLibrary(@"c:"CppDemo.dll");

           IntPtr intPtr = NativeMethod.GetProcAddress(hModule, "Add");

詳細請參見程式碼

Code
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace InteropDemo
{
class Program
   {
//[DllImport("CppDemo.dll", EntryPoint = "Add", ExactSpelling = false, CallingConvention = CallingConvention.Cdecl)]
//public static extern int Add(int a, int b); //DllImport請參照MSDN


staticvoid Main(string[] args)
       {
//1. 動態載入C++ Dll
int hModule = NativeMethod.LoadLibrary(@"c:\CppDemo.dll");
if (hModule == 0) return;

//2. 讀取函式指標
           IntPtr intPtr = NativeMethod.GetProcAddress(hModule, "Add");

//3. 將函式指標封裝成委託
           Add addFunction = (Add)Marshal.GetDelegateForFunctionPointer(intPtr, typeof(Add));

//4. 測試
           Console.WriteLine(addFunction(1, 2));
           Console.Read();
       }

///<summary>
/// 函式指標
///</summary>
///<param name="a"></param>
///<param name="b"></param>
///<returns></returns>
delegateint Add(int a, int b);

   }
}

*****************************************************************************************

分別介紹 DllImportAttribute屬性、extern關鍵字 、IntPtr型別 這三個方面,向大家介紹如何應用C#呼叫非託管DLL函式。


C# 如何使用 DllImport Attribute(屬性) 標識 DLL 和函式

System.Runtime.InteropServices.DllImportAttribute

從託管程式碼中訪問非託管 DLL 函式之前,需要知道該函式的名稱以及該 DLL 的名稱,然後為 DLL 的非託管函式 編寫 託管定義。

它將用到 static 和 extern 修飾符,此型別的公共靜態成員對於多執行緒操作是安全的。

DllImport 屬性提供非託管 DLL 函式的呼叫資訊。


示例1  簡單DllImportAttribute 應用

using System.Runtime.InteropServices;
[DllImport( " user32.dll ")]    
public static extern int MessageBox( int hWnd, String text, String caption, uint type);

示例2  如何將 DllImportAttribute 應用於方法。

using System.Runtime.InteropServices;
[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;
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

// cmdll.c
// compile with: /LD /MD
int __declspec(dllexport) MyMethod( int i)
{
return i* 10;
}


使用命令列編譯 CM.cs:
csc CM.cs
這將建立可執行檔案 CM.exe。


檔案:CM.cs

// 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 介面。

*****************************************************************************************