一、DLL介紹:
動態連結程式庫(DLL,即“Dynamic Link Library”)是Microsoft Windows最重要的組成元素之一,開啟windows系統檔案夾,會發現很多DLL檔案,windows就是將一些主要的系統功能以DLL模組的形式實現。動態連結程式庫是不能直接執行的,也不能接收訊息,它是一個獨立的檔案,其中包含被程式或其他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);}
}
三、訊息回調
函數的兩種調用方式:
StdCall:stdcall呼叫慣例又稱為passcal呼叫慣例,其呼叫慣例申明的文法為:int_stdcall function(int a,int b).stdcall的呼叫慣例意味著:1.參數從右向左壓入堆棧。2.函數自身修改堆棧。3.函數名自動加前置的底線,後面緊跟一個@符號。其後緊跟著參數的尺寸。在C#中,函數只支援stdcall的調用方式。
Cdecl:cdecl呼叫慣例又稱為C呼叫慣例,是C語言預設的呼叫慣例,它的定義文法是:int_cdecl function(int a,int b).cdecal呼叫慣例的參數壓棧順序和stdcall是一樣的。參數首先由右向左壓入堆棧。所不同的是,函數本身不清理堆棧,調用者複製清理堆棧。由於這種變化,C呼叫慣例允許函數的參數的個數是不固定的,這也是C語言的一大特色。VC++ 6.0預設使用cdecl的調用方式。
所以當C#通過訊息回調dll函數時候,由於函數調用的約定不同,函數不能正確的調用。
採取方式:1.修改dll檔案,支援stdCall的調用方式。2.手工方式修改.il中間檔案(C#不支援_cdecl修飾符,可.net中間檔案.il是支援cdecl的調用方式。)
3.用VC++ 7.0給windows dll封裝一層外殼。(在VC++ 7.0中,可以通過_cdecl修飾符,指定函數的調用方式)
四、複雜類型Demo樣本(修改PC機的系統時間)
public struct SystemTime
{
public short wYear;
public short wMonth;
public short wDayOfWeek;
public short wDay;
public short wHour;
public short wMinute;
public short wSecond;
public short wMilliseconds;
}
[DllImport("kernel32.dll", SetLastError = true, EntryPoint = "SetLocalTime")]
public static extern int SetSystemTime(ref SystemTime lpSystemTime);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern long GetLastError();
private void button1_Click(object sender, EventArgs e)
{
DateTime setdt=DateTime.Parse(this.textBox1.Text);
SystemTime st = new SystemTime();
st.wYear = (short)setdt.Year;
st.wMonth = (short)setdt.Month;
st.wDay =(short) setdt.Day;
st.wHour = (short)setdt.Hour;
st.wMinute =(short)setdt.Minute;
st.wSecond =(short) setdt.Second;
int result = SetSystemTime(ref st);
if (result == 1)
{
MessageBox.Show("修改成功!");
}
else
{
long errorCode = GetLastError();
//int code = Marshal.GetLastWin32Error();//.net常用這種方式代替GetLastError API
MessageBox.Show("修改失敗,Win32錯誤碼是{0},請查看GetLastError傳回值的意義列表或調用FormatMessage查看" + errorCode.ToString());
}
}
PS:
1、SetLocalTime與SetSystemTime的區別:SetLocalTime的用法與SetSystemTime基本相同,差別在於SetSystemTime所帶的參數指定的是UTC時間(國際標準時間),也就是說,針對我們地區的電腦(東八區),這樣的話,使用SysteSystemTime設定後,系統的時間,會比參數所設定的時間快8個小時)
2、GetLastError傳回值的意義(http://blog.chinaunix.net/u2/82288/showart_1335456.html)
Best Regards,
Charles Chen