1. 程式人生 > >C# DllImport用法和路徑問題

C# DllImport用法和路徑問題

DllImport是System.Runtime.InteropServices名稱空間下的一個屬性類,其功能是提供從非託管DLL匯出的函式的必要呼叫資訊。
DllImport屬性應用於方法,要求最少要提供包含入口點的dll的名稱。
DllImport的定義如下:

[AttributeUsage(AttributeTargets.Method)]
  public class DllImportAttribute: System.Attribute
  {
   public DllImportAttribute(string dllName) {…} //定位引數為dllName
   public CallingConvention CallingConvention; //入口點呼叫約定
   public CharSet CharSet; //入口點採用的字元接
   public string EntryPoint; //入口點名稱
   public bool ExactSpelling; //是否必須與指示的入口點拼寫完全一致,預設false
   public bool PreserveSig; //方法的簽名是被保留還是被轉換
   public bool SetLastError; //FindLastError方法的返回值儲存在這裡
   public string Value { get {…} }
  }

用法示例:

[DllImport("kernel32")]
private static extern long WritePrivateProfileString(string section,string key,string val,string filePath);

以上是用來寫入ini檔案的一個win32api。
用此方式呼叫Win32API的資料型別對應:DWORD=int或uint,BOOL=bool,預定義常量=enum,結構=struct。

DllImport會按照順序自動去尋找的地方:
1、exe所在目錄
2、System32目錄
3、環境變數目錄
所以只需要你把引用的DLL 拷貝到這三個目錄下 就可以不用寫路徑了或者可以這樣Server.MapPath(.\bin*.dll)

Web中的,同時也是應用程式中的
後來發現用[DllImport(@“C:\OJ\Bin\Judge.dll”)]這樣指定DLL的絕對路徑就可以正常裝載。
這個問題最常出現在使用第三方非託管DLL元件的時候,我的也同樣是這時出的問題,Asp.NET Team的官方解決方案如下:
首先需要確認你引用了哪些元件,那些是託管的,哪些是非託管的.託管的很好辦,直接被使用的需要引用,間接使用的需要拷貝到bin目錄下.非託管的處理
會比較麻煩.實際上,你拷貝到bin沒有任何幫助,因為CLR會把檔案拷貝到一個臨時目錄下,然後在那執行web,而CLR只會拷貝託管檔案,這就是為什
麼我們明明把非託管的dll放在了bin下卻依然提示不能載入模組了.

具體做法如下:
  1、首先我們在伺服器上隨便找個地方新建一個目錄,假如為C:\DLL

2、然後,在環境變數中,給Path變數新增這個目錄

3、最後,把所有的非託管檔案都拷貝到C:\DLL中.

4、或者更乾脆的把DLL放到system32目錄

對於可以自己部署的應用程式,這樣未償不是一個解決辦法,然而,如果我們用的是虛擬空間,我們是沒辦法把註冊PATH變數或者把我們自己的DLL拷到system32目錄的。同時我們也不一定知道我們的Dll的物理路徑。

DllImport裡面只能用字串常量,而不能夠用Server.MapPath(@"~/Bin/Judge.dll")來確定物理路徑。

ASP.Net中要使用DllImport的,必須在先“using System.Runtime.InteropServices;”

不過,我發現,呼叫這種"非託管Dll”相當的慢,可能是因為我的方法需要遠端驗證吧,但是實在是太慢了。

經過一翻研究,終於想到了一個完美的解決辦法

首先我們用

[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);

分別取得了LoadLibrary和GetProcAddress函式的地址,再通過這兩個函式來取得我們的DLL裡面的函式。
我們可以先用Server.MapPath(@"~/Bin/Judge.dll")來取得我們的DLL的物理路徑,然後再用LoadLibrary進行載入,最後用GetProcAddress取得要用的函式地址

以下自定義類的程式碼完成LoadLibrary的裝載和函式呼叫:

public class DllInvoke
{
  [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 hLib;
  public DllInvoke(String DLLPath)
  {
    hLib = LoadLibrary(DLLPath);
  }
  ~DllInvoke()
  {
    FreeLibrary(hLib);           
  }
  //將要執行的函式轉換為委託
  public Delegate Invoke(String APIName,Type t) 
  {
    IntPtr api = GetProcAddress(hLib, APIName);
    return (Delegate)Marshal.GetDelegateForFunctionPointer(api,t);
  }
}

下面程式碼進行呼叫

public delegate int Compile(String command, StringBuilder inf);//編譯
DllInvoke dll = new DllInvoke(Server.MapPath(@"~/Bin/Judge.dll"));
Compile compile = (Compile)dll.Invoke("Compile", typeof(Compile));
StringBuilder inf;
compile(@“gcc a.c -o a.exe“,inf); //這裡就是呼叫我的DLL裡定義的Compile函式

大家在實際工作學習C#的時候,可能會問:為什麼我們要為一些已經存在的功能(比如Windows中的一些功能,C++中已經編寫好的一些方法)要重新編寫程式碼,C#有沒有方法可以直接都用這些原本已經存在的功能呢?答案是肯定的,大家可以通過C#中的DllImport直接呼叫這些功能。DllImport所在的名字空間 using System.Runtime.InteropServices;
MSDN中對DllImportAttribute的解釋是這樣的:可將該屬性應用於方法。DllImportAttribute 屬性提供對從非託管 DLL 匯出的函式進行呼叫所必需的資訊。作為最低要求,必須提供包含入口點的 DLL 的名稱。
DllImport 屬性定義如下:

namespace System.Runtime.InteropServices 
{ 
    [AttributeUsage(AttributeTargets.Method)] 
    public class DllImportAttribute: System.Attribute 
    { 
     public DllImportAttribute(string dllName) {...} 
     public CallingConvention CallingConvention; 
     public CharSet CharSet; 
     public string EntryPoint; 
     public bool ExactSpelling; 
     public bool PreserveSig; 
     public bool SetLastError; 
     public string Value { get {...} } 
    } 
  }    

說明:
  1、DllImport只能放置在方法宣告上。
  2、DllImport具有單個定位引數:指定包含被匯入方法的 dll 名稱的 dllName 引數。
  3、DllImport具有五個命名引數:
  a、CallingConvention 引數指示入口點的呼叫約定。如果未指定 CallingConvention,則使用預設值 CallingConvention.Winapi。
  b、CharSet 引數指示用在入口點中的字符集。如果未指定 CharSet,則使用預設值 CharSet.Auto。
  c、EntryPoint 引數給出 dll 中入口點的名稱。如果未指定 EntryPoint,則使用方法本身的名稱。
  d、ExactSpelling 引數指示 EntryPoint 是否必須與指示的入口點的拼寫完全匹配。如果未指定 ExactSpelling,則使用預設值 false。
  e、PreserveSig 引數指示方法的簽名應當被保留還是被轉換。當簽名被轉換時,它被轉換為一個具有 HRESULT返回值和該返回值的一個名為 retval 的附加輸出引數的簽名。如果未指定 PreserveSig,則使用預設值 true。
  f、SetLastError 引數指示方法是否保留 Win32"上一錯誤"。如果未指定 SetLastError,則使用預設值 false。
  4、它是一次性屬性類。
  5、此外,用 DllImport 屬性修飾的方法必須具有 extern 修飾符。

DllImport的用法:

DllImport("MyDllImport.dll")]
private static extern int mySum(int a,int b);

一 在C#程式設計中使用Win32類庫
常用對應型別:
1、DWORD 是 4 位元組的整數,因此我們可以使用 int 或 uint 作為 C# 對應型別。
2、bool 型別與 BOOL 對應。

示例一:呼叫 Beep() API 來發出聲音
  Beep() 是在 kernel32.lib 中定義的,在MSDN 中的定義,Beep具有以下原型:

BOOL Beep(DWORD dwFreq, // 聲音訊率
DWORD dwDuration // 聲音持續時間);
  用 C# 編寫以下原型:

[DllImport(“kernel32.dll”)]
public static extern bool Beep(int frequency, int duration);

示例二:列舉型別和常量
  MessageBeep() 是在 user32.lib 中定義的,在MSDN 中的定義,MessageBeep具有以下原型:

BOOL MessageBeep(UINT uType // 聲音型別
);
  用C#編寫一下原型:

public enum BeepType
{
  SimpleBeep = -1,
  IconAsterisk = 0x00000040,
  IconExclamation = 0x00000030,
  IconHand = 0x00000010,
  IconQuestion = 0x00000020,
  Ok = 0x00000000,
} 

uType 引數實際上接受一組預先定義的常量,對於 uType 引數,使用 enum 型別是合乎情理的。

[DllImport(“user32.dll”)]
public static extern bool MessageBeep(BeepType beepType);

示例三:處理結構
  有時我需要確定我筆記本的電池狀況。Win32 為此提供了電源管理函式,搜尋 MSDN 可以找到GetSystemPowerStatus() 函式。

BOOL GetSystemPowerStatus(
LPSYSTEM_POWER_STATUS lpSystemPowerStatus
);
  此函式包含指向某個結構的指標,我們尚未對此進行過處理。要處理結構,我們需要用 C# 定義結構。我們從非託管的定義開始:

typedef struct _SYSTEM_POWER_STATUS { 
BYTE  ACLineStatus; 
BYTE  BatteryFlag; 
BYTE  BatteryLifePercent; 
BYTE  Reserved1; 
DWORD BatteryLifeTime; 
DWORD BatteryFullLifeTime; 
} SYSTEM_POWER_STATUS, *LPSYSTEM_POWER_STATUS; 

然後,通過用 C# 型別代替 C 型別來得到 C# 版本。

struct SystemPowerStatus 
{ 
  byte ACLineStatus; 
  byte batteryFlag; 
  byte batteryLifePercent; 
  byte reserved1; 
  int batteryLifeTime; 
  int batteryFullLifeTime; 
} 

這樣,就可以方便地編寫出 C# 原型:

[DllImport("kernel32.dll")] 
public static extern bool GetSystemPowerStatus(ref SystemPowerStatus systemPowerStatus); 

在此原型中,我們用“ref”指明將傳遞結構指標而不是結構值。這是處理通過指標傳遞的結構的一般方法。
  此函式執行良好,但是最好將 ACLineStatus 和 batteryFlag 欄位定義為 enum:

enum ACLineStatus: byte 
{ 
    Offline = 0, 
    Online = 1, 
    Unknown = 255, 
} 

enum BatteryFlag: byte 
{ 
    High = 1, 
    Low = 2, 
    Critical = 4, 
    Charging = 8, 
    NoSystemBattery = 128, 
    Unknown = 255, 
} 

請注意,由於結構的欄位是一些位元組,因此我們使用 byte 作為該 enum 的基本型別

示例四:處理字串

DLL返回int 型別

[DllImport(“MyDLL.dll")] 
//返回個int 型別 
public static extern int mySum (int a1,int b1); 
//DLL中申明 
extern “C” __declspec(dllexport)  int WINAPI mySum(int a2,int b2) 
{ 
//a2 b2不能改變a1 b1
//a2=..
//b2=...
return a+b; 
}

//引數傳遞int 型別
public static extern int mySum (ref int a1,ref int b1);
//DLL中申明
extern “C” __declspec(dllexport) int WINAPI mySum(int *a2,int *b2)
{ 
//可以改變 a1, b1
*a2=...
*b2=...
return a+b;
}

DLL 需傳入char *型別

[DllImport(“MyDLL.dll")] 
//傳入值
public static extern int mySum (string astr1,string bstr1);
//DLL中申明
extern “C” __declspec(dllexport) int WINAPI mySum(char * astr2,char * bstr2)
{
//改變astr2 bstr 2 ,astr1 bstr1不會被改變
return a+b;
}

DLL 回撥函式

[DllImport(“MyDLL.dll")]
// 傳出值
public static extern int mySum (StringBuilder abuf, StringBuilder bbuf );
//DLL中申明
extern “C” __declspec(dllexport) int WINAPI mySum(char * astr,char * bstr)
{
//傳出char * 改變astr bstr -->abuf, bbuf可以被改變
return a+b;
}

二 C# 中呼叫C++程式碼

BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam)

using System;
using System.Runtime.InteropServices;
public delegate bool CallBack(int hwnd, int lParam); //定義委託函式型別
public class EnumReportApp
{
[DllImport("user32")]
public static extern int EnumWindows(CallBack x, int y);
public static void Main() {
CallBack myCallBack = new CallBack(EnumReportApp.Report); EnumWindows(myCallBack, 0);
}
public static bool Report(int hwnd, int lParam)
{
Console.Write("Window handle is ");
Console.WriteLine(hwnd); return true;
}
}

DLL 傳遞結構

BOOL PtInRect(const RECT *lprc, POINT pt);

using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
public struct Point {
public int x;
public int y;
}
[StructLayout(LayoutKind.Explicit)]
public struct Rect
{
[FieldOffset(0)] public int left;
[FieldOffset(4)] public int top;
[FieldOffset(8)] public int right;
[FieldOffset(12)] public int bottom;
}
Class XXXX {
[DllImport("User32.dll")]
public static extern bool PtInRect(ref Rect r, Point p);
}