從C#程序中調(diào)用非受管DLLs的方法
本文實(shí)例講述了從C#程序中調(diào)用非受管DLLs的方法。分享給大家供大家參考。具體方法如下:
前言:
從所周知,.NET已經(jīng)漸漸成為一種技術(shù)時(shí)尚,那么C#很自然也成為一種編程時(shí)尚。如何利用浩如煙海的Win32 API以及以前所編寫的 Win32 代碼已經(jīng)成為越來越多的C#程序員所關(guān)注的問題。本文將介紹如何從C#代碼中調(diào)用非受管DLLs。如果某個(gè)函數(shù)是一個(gè)帶有串類型(char*)輸出參數(shù)的Win32 API 或者是DLL輸出函數(shù),那么從C#中如何調(diào)用它呢?對(duì)于輸入?yún)?shù)的情形問題到不大,但如何獲取從參數(shù)中返回的串呢?此外,如何調(diào)用有結(jié)構(gòu)(struct)和回調(diào)(callback)作為參數(shù)的函數(shù),如GetWindowsRect 和EnumWindows?那我們又如何將參數(shù)從C++和MFC中轉(zhuǎn)換成C# 所要的類型呢?下面就讓我們來一一解決這些問題。
微軟.NET的一個(gè)最主要的優(yōu)勢(shì)是它提供一個(gè)語(yǔ)言無關(guān)的開發(fā)系統(tǒng)。我們可以用Visual Basic、C++、C#等等語(yǔ)言來編寫類,然后在其它語(yǔ)言中使用,我們甚至可以用不同的語(yǔ)言來派生類。但是如何調(diào)用以前開發(fā)的非受管DLL呢?方法是必須將.NET對(duì)象轉(zhuǎn)化成結(jié)構(gòu)、char*以及C語(yǔ)言的指針。用行話說就是參數(shù)必須被列集(marshal)。說到列集,用一兩句話也說不清楚。所幸的是實(shí)現(xiàn)列集并不要我們知道太多的東西。
為了從C# 中調(diào)用DLL函數(shù),首先必須要有一個(gè)聲明,就象長(zhǎng)期以來使用Visual Basic的程序員所做的那樣,只不過在C#中使用的是DllImport關(guān)鍵字:
public class Win32 {
[DllImport("User32.Dll")]
public static extern void SetWindowText(int h, String s);
}
在C#中,DllImport關(guān)鍵字作用是告訴編譯器入口點(diǎn)在哪里,并將打包函數(shù)捆綁在一個(gè)類中。我們可以為這類取任何名字,這里不妨將類名取為 Win32。我們甚至可以將這個(gè)類放到一個(gè)名字空間中,就象下面的代碼這樣:
Win32API.cs 源代碼
// 編譯方法:
// csc /t:library /out:Win32API.dll Win32API.cs
//
using System;
using System.Drawing;
using System.Text;
using System.Runtime.InteropServices;
/////////////////////////////////////////////////////////////////
// 包裝Win32 API函數(shù)的名字空間。想用哪個(gè)Win32 API,往里添加即可。
//
namespace Win32API {
[StructLayout(LayoutKind.Sequential)]
public struct POINT {
public POINT(int xx, int yy) { x=xx; y=yy; }
public int x;
public int y;
public override string ToString() {
String s = String.Format("({0},{1})", x, y);
return s;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct SIZE {
public SIZE(int cxx, int cyy) { cx=cxx; cy=cyy; }
public int cx;
public int cy;
public override string ToString() {
String s = String.Format("({0},{1})", cx, cy);
return s;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT {
public int left;
public int top;
public int right;
public int bottom;
public int Width() { return right - left; }
public int Height() { return bottom - top; }
public POINT TopLeft() { return new POINT(left,top); }
public SIZE Size() { return new SIZE(Width(), Height()); }
public override string ToString() {
String s = String.Format("{0}x{1}", TopLeft(), Size());
return s;
}
}
public class Win32 {
[DllImport("user32.dll")]
public static extern bool IsWindowVisible(int hwnd);
[DllImport("user32.dll")]
public static extern int GetWindowText(int hwnd,
StringBuilder buf, int nMaxCount);
[DllImport("user32.dll")]
public static extern int GetClassName(int hwnd,
[MarshalAs(UnmanagedType.LPStr)] StringBuilder buf,
int nMaxCount);
[DllImport("user32.dll")]
public static extern int GetWindowRect(int hwnd, ref RECT rc);
[DllImport("user32.dll")]
// 注意,運(yùn)行時(shí)知道如何列集一個(gè)矩形
public static extern int GetWindowRect(int hwnd, ref Rectangle rc);
}
}
用下面的命令行可以編譯這段代碼: csc /t:library /out:Win32API.dll Win32API.cs
成功編譯后,我們就有了一個(gè)可以在C#工程中使用的動(dòng)態(tài)庫(kù)了(Win32API.dll)。
int hwnd = // get it
String s = "I''''m so cute." ;
Win32.SetWindowText(hwnd, s);
編譯器知道在user32.dll中找到SetWindowText,并在調(diào)用前自動(dòng)將串轉(zhuǎn)換為L(zhǎng)PTSTR (TCHAR*)。真是神奇!.NET是如何實(shí)現(xiàn)的呢?其實(shí),每一個(gè)C#類型都有一個(gè)缺省的列集類型。對(duì)于串來說,它的列集類型就是LPTSTR。但如果調(diào)用的是GetWindowText,它的串參數(shù)是一個(gè)輸出參數(shù),而非輸入?yún)?shù),因?yàn)榇遣蛔兊模傧笊厦孢@樣處理就行不通了。我們可能一點(diǎn)都沒有注意到,不論什么時(shí)候處理一個(gè)串時(shí),都會(huì)創(chuàng)建一個(gè)新串。要想修改這個(gè)串,必須用StringBuilder:
public class Win32 {
[DllImport("user32.dll")]
public static extern int GetWindowText(int hwnd,
StringBuilder buf, int nMaxCount);
}
StringBuilder缺省的列集類型是LPTSTR,但是GetWindowText現(xiàn)在可以修改實(shí)際的串。
StringBuilder sb = new StringBuilder(256);
Win32.GetWindowText(hwnd, sb, sb.Capacity);
所以我們第一個(gè)問題的答案就是:使用StringBuilder。 前面討論的方法固然可以行得通,但有一種情況沒有考慮,那就是如果缺省的列集類型不是你想要的類型怎么辦?例如想要調(diào)用GetClassName,Windows編程高手都知道,GetClassName的參數(shù)與大多數(shù)其它的API函數(shù)的參數(shù)有所不同,它的串參數(shù)是LPSTR (char*),甚至是Unicode串。如果傳遞一個(gè)串,公共語(yǔ)言運(yùn)行時(shí)(CLR)將把它轉(zhuǎn)換成TCHARs——是不是很糟啊!不用害怕,我們可以用MarshalAs來改寫缺省的處理:
public static extern int GetClassName(int hwnd,
[MarshalAs(UnmanagedType.LPStr)] StringBuilder buf,
int nMaxCount);
現(xiàn)在我們調(diào)用GetClassName,.NET將串作為ANSI字符傳遞,而不是寬字符,搞掂! 以上我們解決了如何獲取函數(shù)載參數(shù)中返回的字符串。下面我們來看看結(jié)構(gòu)參數(shù)和回調(diào)參數(shù)的情形。不用說,.NET肯定有辦法處理它們。就拿GetWindowRect為例。這個(gè)函數(shù)用窗口屏幕坐標(biāo)填充一個(gè)RECT。
在C/C++中
HWND hwnd = FindWindow("foo",NULL);
::GetWindowRect(hwnd, &rc);
在C#中如何調(diào)用呢?如何傳遞RECT呢?方法是將它作為一個(gè)C#結(jié)構(gòu),用另一個(gè)屬性:它就是StructLayout:
[StructLayout(LayoutKind.Sequential)]
public struct RECT {
public int left;
public int top;
public int right;
public int bottom;
}
一旦有了結(jié)構(gòu)定義,便可以象下面這樣來打包實(shí)現(xiàn):
public static extern int
GetWindowRect(int hwnd, ref RECT rc);
注意這里用到了ref,這一點(diǎn)很重要,CLR會(huì)將RECT作為引用傳遞,以便函數(shù)可以修改我們的對(duì)象,而不是無名字的堆??截悺6x了GetWindowRect之后,我們可以象下面這樣調(diào)用:
int hwnd = // get it
Win32.GetWindowRect(hwnd, ref rc);
注意這里必須聲明并使用ref——羅嗦!C# 結(jié)構(gòu)的缺省列集類型還能是什么?——LPStruct,所以就不必再用MarshalAs了。但如果RECT是個(gè)類,而非結(jié)構(gòu)的話,那就必須象下面這樣實(shí)現(xiàn)打包:
[DllImport("user32.dll")]
public static extern int
GetWindowRect(int hwnd,
[MarshalAs(UnmanagedType.LPStruct)] RECT rc);
C#與C++類似,許多事情都可以殊途同歸,System.Drawing中已經(jīng)有了一個(gè)Rectangle結(jié)構(gòu)用來處理矩形,所以為什么要重新發(fā)明輪子呢?
public static extern int GetWindowRect(int hwnd, ref Rectangle rc);
運(yùn)行時(shí)既然已經(jīng)知道如何將Rectangle作為Win32 RECT進(jìn)行列集。請(qǐng)注意,在實(shí)際的代碼中就沒有必要再調(diào)用GetWindowRect(Get/SetWindowText亦然),因?yàn)閃indows.Forms.Control類已具有這樣的屬性:用Control.DisplayRectangle獲取窗口矩形,用Control.Text設(shè)置/獲取控件文本
mywnd.Text = "I''''m so cute";
如果出于某種原因已知的是某個(gè)HWND,而不是一個(gè)控件派生對(duì)象,那么只需要象示范的那樣來打包API。以上我們已經(jīng)搞掂了串、結(jié)構(gòu)以及矩形……還有什么呢?對(duì)了,還有回調(diào)(callbacks)。如何將回調(diào)從C#傳遞到非受管代碼呢?記住只要用委托(delegate)即可: delegate bool EnumWindowsCB(int hwnd, int lparam);
一旦聲明了委托/回調(diào)類型,就可以象下面這樣打包:
public static extern int
EnumWindows(EnumWindowsCB cb, int lparam);
上面的delegate僅僅是聲明了一個(gè)委托類型,我們還必須在類中提供一個(gè)實(shí)際的委托實(shí)現(xiàn):
public static bool MyEWP(int hwnd, int lparam) {
// do something
return true;
}
然后對(duì)它進(jìn)行打包處理:
Win32.EnumWindows(cb, 0);
聰明的讀者回注意到我們這里掩飾了lparam的問題,在C中,如果你給EnumWindows一個(gè)LPARAM,則Windows會(huì)用它通知回調(diào)函數(shù)。一般典型的lparam是一個(gè)結(jié)構(gòu)或類指針,其中包含著我們需要的上下文信息。但是記住,在.NET中絕對(duì)不能提到"指針"!那么如何做呢?這是可以將lparam聲明為IntPtr并用GCHandle對(duì)它進(jìn)行打包:
delegate bool EnumWindowsCB(int hwnd, IntPtr lparam);
// 在GCHandle中打包對(duì)象
MyClass obj = new MyClass();
GCHandle gch = GCHandle.Alloc(obj);
EnumWindowsCB cb = new EnumWindowsCB(MyEWP);
Win32.EnumWindows(cb, (IntPtr)gch);
gch.Free();
最后不要忘了調(diào)用Free! C#中有時(shí)也需要與以往一樣必須要我們自己釋放占用的內(nèi)存。為了存取載枚舉器中的lparam"指針",必須使用
GCHandle gch = (GCHandle)param;
MyClass c = (MyClass)gch.Target;
// use it
return true;
}
下面是一個(gè)窗口數(shù)組類:
WinArray.cs
//
using System;
using System.Collections;
using System.Runtime.InteropServices;
namespace WinArray {
public class WindowArray : ArrayList {
private delegate bool EnumWindowsCB(int hwnd, IntPtr param);
// 這里聲明的是private類型的委托,因?yàn)橹挥形沂褂盟?,其?shí)沒必要這樣做。
[DllImport("user32")]
private static extern int EnumWindows(EnumWindowsCB cb,
IntPtr param);
private static bool MyEnumWindowsCB(int hwnd, IntPtr param) {
GCHandle gch = (GCHandle)param;
WindowArray itw = (WindowArray)gch.Target;
itw.Add(hwnd);
return true;
}
// 這是唯一的public 類型方法,你需要調(diào)用的唯一方法
public WindowArray() {
GCHandle gch = GCHandle.Alloc(this);
EnumWindowsCB ewcb = new EnumWindowsCB(MyEnumWindowsCB);
EnumWindows(ewcb, (IntPtr)gch);
gch.Free();
}
}
}
這個(gè)類將EnumWindows封裝在一個(gè)數(shù)組中,不用我們?cè)偃ミM(jìn)行繁瑣的委托和回調(diào),我們可以象下面這樣輕松使用這個(gè)類:
foreach (int hwnd in wins) {
// do something
}
是不是很帥啊!我們甚至還可以在受管C++中使用DllImport風(fēng)格的包裝類。在.NET環(huán)境中,只要能進(jìn)行相應(yīng)的轉(zhuǎn)換,便可以在受管和非受管世界之間隨心所欲地聘馳, 大多數(shù)情況下的轉(zhuǎn)換是自動(dòng)的,不必關(guān)心太多的事情。需要進(jìn)行MarshalAs或者打包GCHandle的情況很少。有關(guān)C#和非受管C++之間的平臺(tái)調(diào)用的其它細(xì)節(jié)問題,可以參考.NET的有關(guān)文檔。 下面是本文提供的一個(gè)帶有開關(guān)的控制臺(tái)小程序ListWin。它的功能是列出所有頂層窗口,輸出可以顯示HWNDs、窗口類名、窗口標(biāo)題以及窗口矩形,用RECT或Rectangle。這些內(nèi)容的顯示可用開關(guān)控制。
希望本文所述對(duì)大家的C#程序設(shè)計(jì)有所幫助。
相關(guān)文章
C#制作網(wǎng)站掛機(jī)程序的實(shí)現(xiàn)示例
本文主要介紹了C#制作網(wǎng)站掛機(jī)程序,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10