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新增如下程式碼:
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
Codeusing 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");
詳細請參見程式碼
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 應用
[DllImport( " user32.dll ")]
public static extern int MessageBox( int hWnd, String text, String caption, uint type);
示例2 如何將 DllImportAttribute 應用於方法。
[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.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
// compile with: /LD /MD
int __declspec(dllexport) MyMethod( int i)
{
return i* 10;
}
使用命令列編譯 CM.cs:
csc CM.cs
這將建立可執行檔案 CM.exe。
檔案: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 介面。
*****************************************************************************************