国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 開發(fā) > 綜合 > 正文

C# API應(yīng)用整理文檔

2024-07-21 02:25:51
字體:
供稿:網(wǎng)友
c# api
c:/programfiles/microsoftvisual studio .net/ frameworksdk/samples/ technologies/ interop/platforminvoke/ winapis/cs目錄下有大量的調(diào)用api的例子。
一、調(diào)用格式
using system.runtime.interopservices; //引用此名稱空間,簡(jiǎn)化后面的代碼
//使用dllimportattribute特性來引入api函數(shù),注意聲明的是空方法,即方法體為空。
[dllimport("user32.dll")]
public static extern returntype functionname(type arg1,type arg2,...);
//調(diào)用時(shí)與調(diào)用其他方法并無區(qū)別
可以使用字段進(jìn)一步說明特性,用逗號(hào)隔開,如:
[ dllimport( "kernel32", entrypoint="getversionex" )]
dllimportattribute特性的公共字段如下:
1、callingconvention 指示向非托管實(shí)現(xiàn)傳遞方法參數(shù)時(shí)所用的 callingconvention 值。
callingconvention.cdecl : 調(diào)用方清理堆棧。它使您能夠調(diào)用具有 varargs 的函數(shù)。
callingconvention.stdcall : 被調(diào)用方清理堆棧。它是從托管代碼調(diào)用非托管函數(shù)的默認(rèn)約定。
2charset 控制調(diào)用函數(shù)的名稱版本及指示如何向方法封送 string 參數(shù)。
此字段被設(shè)置為 charset 值之一。如果 charset 字段設(shè)置為 unicode,則所有字符串參數(shù)在傳遞到非托管實(shí)現(xiàn)之前都轉(zhuǎn)換成 unicode 字符。這還導(dǎo)致向 dll entrypoint 的名稱中追加字母“w”。如果此字段設(shè)置為 ansi,則字符串將轉(zhuǎn)換成 ansi 字符串,同時(shí)向 dll entrypoint 的名稱中追加字母“a”。大多數(shù) win32 api 使用這種追加“w”或“a”的約定。如果 charset 設(shè)置為 auto,則這種轉(zhuǎn)換就是與平臺(tái)有關(guān)的(在 windows nt 上為 unicode,在 windows 98 上為 ansi)。charset 的默認(rèn)值為 ansi。charset 字段也用于確定將從指定的 dll 導(dǎo)入哪個(gè)版本的函數(shù)。charset.ansi charset.unicode 的名稱匹配規(guī)則大不相同。對(duì)于 ansi 來說,如果將 entrypoint 設(shè)置為“mymethod”且它存在的話,則返回“mymethod”。如果 dll 中沒有“mymethod”,但存在“mymethoda”,則返回“mymethoda”。對(duì)于 unicode 來說則正好相反。如果將 entrypoint 設(shè)置為“mymethod”且它存在的話,則返回“mymethodw”。如果 dll 中不存在“mymethodw”,但存在“mymethod”,則返回“mymethod”。如果使用的是 auto,則匹配規(guī)則與平臺(tái)有關(guān)(在 windows nt 上為 unicode,在 windows 98 上為 ansi)。如果 exactspelling 設(shè)置為 true,則只有當(dāng) dll 中存在“mymethod”時(shí)才返回“mymethod”。
 
 
3、entrypoint 指示要調(diào)用的 dll 入口點(diǎn)的名稱或序號(hào)。
如果你的方法名不想與api函數(shù)同名的話,一定要指定此參數(shù),例如:
[dllimport("user32.dll",charset="charset.auto",entrypoint="messagebox")]
public static extern int msgbox(intptr hwnd,string txt,string caption, int type);
 
 
4exactspelling 指示是否應(yīng)修改非托管 dll 中的入口點(diǎn)的名稱,以與 charset 字段中指定的 charset 值相對(duì)應(yīng)。如果為 true,則當(dāng) dllimportattribute.charset 字段設(shè)置為 charset ansi 值時(shí),向方法名稱中追加字母 a,當(dāng) dllimportattribute.charset 字段設(shè)置為 charset unicode 值時(shí),向方法的名稱中追加字母 w。此字段的默認(rèn)值是 false。
5、preservesig 指示托管方法簽名不應(yīng)轉(zhuǎn)換成返回 hresult、并且可能有一個(gè)對(duì)應(yīng)于返回值的附加 [out, retval] 參數(shù)的非托管簽名。
6、setlasterror 指示被調(diào)用方在從屬性化方法返回之前將調(diào)用 win32 api setlasterror。 true 指示調(diào)用方將調(diào)用 setlasterror,默認(rèn)為 false。運(yùn)行時(shí)封送拆收器將調(diào)用 getlasterror 并緩存返回的值,以防其被其他 api 調(diào)用重寫。用戶可通過調(diào)用 getlastwin32error 來檢索錯(cuò)誤代碼。
 
二、參數(shù)類型:
1、數(shù)值型直接用對(duì)應(yīng)的就可。(dword -> int , word -> int16
2、api中字符串指針類型 -> .netstring
3、api中句柄 (dword) -> .netintptr
4、api中結(jié)構(gòu) -> .net中結(jié)構(gòu)或者類。注意這種情況下,要先用structlayout特性限定聲明結(jié)構(gòu)或類
公共語言運(yùn)行庫(kù)利用structlayoutattribute控制類或結(jié)構(gòu)的數(shù)據(jù)字段在托管內(nèi)存中的物理布局,即類或結(jié)構(gòu)需要按某種方式排列。如果要將類傳遞給需要指定布局的非托管代碼,則顯式控制類布局是重要的。它的構(gòu)造函數(shù)中用layoutkind值初始化 structlayoutattribute 類的新實(shí)例。 layoutkind.sequential 用于強(qiáng)制將成員按其出現(xiàn)的順序進(jìn)行順序布局。
layoutkind.explicit 用于控制每個(gè)數(shù)據(jù)成員的精確位置。利用 explicit, 每個(gè)成員必須使用 fieldoffsetattribute 指示此字段在類型中的位置。如:
[structlayout(layoutkind.explicit, size=16, charset=charset.ansi)]
public class mysystemtime
{
[fieldoffset(0)]public ushort wyear;
[fieldoffset(2)]public ushort wmonth;
[fieldoffset(4)]public ushort wdayofweek;
[fieldoffset(6)]public ushort wday;
[fieldoffset(8)]public ushort whour;
[fieldoffset(10)]public ushort wminute;
[fieldoffset(12)]public ushort wsecond;
[fieldoffset(14)]public ushort wmilliseconds;
}
下面是針對(duì)apiosversioninfo結(jié)構(gòu),在.net中定義對(duì)應(yīng)類或結(jié)構(gòu)的例子:
/**********************************************
* api中定義原結(jié)構(gòu)聲明
* osversioninfoa struct
* dwosversioninfosize dword ?
* dwmajorversion dword ?
* dwminorversion dword ?
* dwbuildnumber dword ?
* dwplatformid dword ?
* szcsdversion byte 128 dup (?)
* osversioninfoa ends
*
* osversioninfo equ <osversioninfoa>
*********************************************/
 
 
//.net中聲明為類
[ structlayout( layoutkind.sequential )]
public class osversioninfo
{
public int osversioninfosize;
public int majorversion;
public int minorversion;
public int buildnumber;
public int platformid;
 
 
[ marshalas( unmanagedtype.byvaltstr, sizeconst=128 )]
public string versionstring;
}
//或者
//.net中聲明為結(jié)構(gòu)
[ structlayout( layoutkind.sequential )]
public struct osversioninfo2
{
public int osversioninfosize;
public int majorversion;
public int minorversion;
public int buildnumber;
public int platformid;
 
 
[ marshalas( unmanagedtype.byvaltstr, sizeconst=128 )]
public string versionstring;
}
 
此例中用到mashalas特性,它用于描述字段、方法或參數(shù)的封送處理格式。用它作為參數(shù)前綴并指定目標(biāo)需要的數(shù)據(jù)類型。例如,以下代碼將兩個(gè)參數(shù)作為數(shù)據(jù)類型長(zhǎng)指針封送給 windows api 函數(shù)的字符串 (lpstr)
[marshalas(unmanagedtype.lpstr)]
string existingfile;
[marshalas(unmanagedtype.lpstr)]
string newfile;
 
注意結(jié)構(gòu)作為參數(shù)時(shí)候,一般前面要加上ref修飾符,否則會(huì)出現(xiàn)錯(cuò)誤:對(duì)象的引用沒有指定對(duì)象的實(shí)例。
[ dllimport( "kernel32", entrypoint="getversionex" )]
public static extern bool getversionex2( ref osversioninfo2 osvi );
 
三、如何保證使用托管對(duì)象的平臺(tái)調(diào)用成功?
如果在調(diào)用平臺(tái) invoke 后的任何位置都未引用托管對(duì)象,則垃圾回收器可能將完成該托管對(duì)象。這將釋放資源并使句柄無效,從而導(dǎo)致平臺(tái)invoke 調(diào)用失敗。用 handleref 包裝句柄可保證在平臺(tái) invoke 調(diào)用完成前,不對(duì)托管對(duì)象進(jìn)行垃圾回收。
例如下面:
filestream fs = new filestream( "a.txt", filemode.open );
stringbuilder buffer = new stringbuilder( 5 );
int read = 0;
readfile(fs.handle, buffer, 5, out read, 0 ); //調(diào)用win api中的readfile函數(shù)
由于fs是托管對(duì)象,所以有可能在平臺(tái)調(diào)用還未完成時(shí)候被垃圾回收站回收。將文件流的句柄用handleref包裝后,就能避免被垃圾站回收:
[ dllimport( "kernel32.dll" )]
public static extern bool readfile(
handleref hndref,
stringbuilder buffer,
int numberofbytestoread,
out int numberofbytesread,
ref overlapped flag );
......
......
filestream fs = new filestream( "handleref.txt", filemode.open );
handleref hr = new handleref( fs, fs.handle );
stringbuilder buffer = new stringbuilder( 5 );
int read = 0;
// platform invoke will hold reference to handleref until call ends
readfile( hr, buffer, 5, out read, 0 );
 
 
 
 
我在自己最近的編程中注意到一個(gè)趨勢(shì),正是這個(gè)趨勢(shì)才引出本月的專欄主題。最近,我在基于 microsoft? .net framework 的應(yīng)用程序中完成了大量的 win32? interop。我并不是要說我的應(yīng)用程序充滿了自定義的 interop 代碼,但有時(shí)我會(huì)在 .net framework 類庫(kù)中碰到一些次要但又繁絮、不充分的內(nèi)容,通過調(diào)用該 windows? api,可以快速減少這樣的麻煩。
 
因此我認(rèn)為,.net framework 1.0 1.1 版類庫(kù)中存在任何 windows 所沒有的功能限制都不足為怪。畢竟,32 位的 windows(不管何種版本)是一個(gè)成熟的操作系統(tǒng),為廣大客戶服務(wù)了十多年。相比之下,.net framework 卻是一個(gè)新事物。
 
隨著越來越多的開發(fā)人員將生產(chǎn)應(yīng)用程序轉(zhuǎn)到托管代碼,開發(fā)人員更頻繁地研究底層操作系統(tǒng)以圖找出一些關(guān)鍵功能顯得很自然 至少目前是如此。
 
值得慶幸的是,公共語言運(yùn)行庫(kù) (clr) interop 功能(稱為平臺(tái)調(diào)用 (p/invoke))非常完善。在本專欄中,我將重點(diǎn)介紹如何實(shí)際使用 p/invoke 來調(diào)用 windows api 函數(shù)。當(dāng)指 clr com interop 功能時(shí),p/invoke 當(dāng)作名詞使用;當(dāng)指該功能的使用時(shí),則將其當(dāng)作動(dòng)詞使用。我并不打算直接介紹 com interop,因?yàn)樗?font face="times new roman"> p/invoke 具有更好的可訪問性,卻更加復(fù)雜,這有點(diǎn)自相矛盾,這使得將 com interop 作為專欄主題來討論不太簡(jiǎn)明扼要。
 
走進(jìn) p/invoke
 
首先從考察一個(gè)簡(jiǎn)單的 p/invoke 示例開始。讓我們看一看如何調(diào)用 win32 messagebeep 函數(shù),它的非托管聲明如以下代碼所示:
 
bool messagebeep(
  uint utype   // beep type
);
 
為了調(diào)用 messagebeep,您需要在 c# 中將以下代碼添加到一個(gè)類或結(jié)構(gòu)定義中:
 
[dllimport("user32.dll")]
static extern boolean messagebeep(uint32 beeptype);
 
令人驚訝的是,只需要這段代碼就可以使托管代碼調(diào)用非托管的 messagebeep api。它不是一個(gè)方法調(diào)用,而是一個(gè)外部方法定義。(另外,它接近于一個(gè)來自 c c# 允許的直接端口,因此以它為起點(diǎn)來介紹一些概念是有幫助的。)來自托管代碼的可能調(diào)用如下所示:
 
messagebeep(0);
 
請(qǐng)注意,現(xiàn)在 messagebeep 方法被聲明為 static。這是 p/invoke 方法所要求的,因?yàn)樵谠?font face="times new roman"> windows api 中沒有一致的實(shí)例概念。接下來,還要注意該方法被標(biāo)記為 extern。這是提示編譯器該方法是通過一個(gè)從 dll 導(dǎo)出的函數(shù)實(shí)現(xiàn)的,因此不需要提供方法體。
 
說到缺少方法體,您是否注意到 messagebeep 聲明并沒有包含一個(gè)方法體?與大多數(shù)算法由中間語言 (il) 指令組成的托管方法不同,p/invoke 方法只是元數(shù)據(jù),實(shí)時(shí) (jit) 編譯器在運(yùn)行時(shí)通過它將托管代碼與非托管的 dll 函數(shù)連接起來。執(zhí)行這種到非托管世界的連接所需的一個(gè)重要信息就是導(dǎo)出非托管方法的 dll 的名稱。這一信息是由 messagebeep 方法聲明之前的 dllimport 自定義屬性提供的。在本例中,可以看到,messagebeep 非托管 api 是由 windows 中的 user32.dll 導(dǎo)出的。
 
到現(xiàn)在為止,關(guān)于調(diào)用 messagebeep 就剩兩個(gè)話題沒有介紹,請(qǐng)回顧一下,調(diào)用的代碼與以下所示代碼片段非常相似:
 
[dllimport("user32.dll")]
static extern boolean messagebeep(uint32 beeptype);
 
最后這兩個(gè)話題是與數(shù)據(jù)封送處理 (data marshaling) 和從托管代碼到非托管函數(shù)的實(shí)際方法調(diào)用有關(guān)的話題。調(diào)用非托管 messagebeep 函數(shù)可以由找到作用域內(nèi)的extern messagebeep 聲明的任何托管代碼執(zhí)行。該調(diào)用類似于任何其他對(duì)靜態(tài)方法的調(diào)用。它與其他任何托管方法調(diào)用的共同之處在于帶來了數(shù)據(jù)封送處理的需要。
 
c# 的規(guī)則之一是它的調(diào)用語法只能訪問 clr 數(shù)據(jù)類型,例如 system.uint32 system.boolean。c# 顯然不識(shí)別 windows api 中使用的基于 c 的數(shù)據(jù)類型(例如 uint bool),這些類型只是 c 語言類型的類型定義而已。所以當(dāng) windows api 函數(shù) messagebeep 按以下方式編寫時(shí)
 
bool messagebeep( uint utype )
 
外部方法就必須使用 clr 類型來定義,如您在前面的代碼片段中所看到的。需要使用與基礎(chǔ) api 函數(shù)類型不同但與之兼容的 clr 類型是 p/invoke 較難使用的一個(gè)方面。因此,在本專欄的后面我將用完整的章節(jié)來介紹數(shù)據(jù)封送處理。
 
樣式
 
c# 中對(duì) windows api 進(jìn)行 p/invoke 調(diào)用是很簡(jiǎn)單的。但如果類庫(kù)拒絕使您的應(yīng)用程序發(fā)出嘟聲,應(yīng)該想方設(shè)法調(diào)用 windows 使它進(jìn)行這項(xiàng)工作,是嗎?
 
是的。但是與選擇的方法有關(guān),而且關(guān)系甚大!通常,如果類庫(kù)提供某種途徑來實(shí)現(xiàn)您的意圖,則最好使用 api 而不要直接調(diào)用非托管代碼,因?yàn)?font face="times new roman"> clr 類型和 win32 之間在樣式上有很大的不同。我可以將關(guān)于這個(gè)問題的建議歸結(jié)為一句話。當(dāng)您進(jìn)行 p/invoke 時(shí),不要使應(yīng)用程序邏輯直接屬于任何外部方法或其中的構(gòu)件。如果您遵循這個(gè)小規(guī)則,從長(zhǎng)遠(yuǎn)看經(jīng)常會(huì)省去許多的麻煩。
 
1 中的代碼顯示了我所討論的 messagebeep 外部方法的最少附加代碼。圖 1 中并沒有任何顯著的變化,而只是對(duì)無包裝的外部方法進(jìn)行一些普通的改進(jìn),這可以使工作更加輕松一些。從頂部開始,您會(huì)注意到一個(gè)名為 sound 的完整類型,它專用于 messagebeep。如果我需要使用 windows api 函數(shù) playsound 來添加對(duì)播放波形的支持,則可以重用 sound 類型。然而,我不會(huì)因公開單個(gè)公共靜態(tài)方法的類型而生氣。畢竟這只是應(yīng)用程序代碼而已。還應(yīng)該注意到,sound 是密封的,并定義了一個(gè)空的私有構(gòu)造函數(shù)。這些只是一些細(xì)節(jié),目的是使用戶不會(huì)錯(cuò)誤地從 sound 派生類或者創(chuàng)建它的實(shí)例。
 
1 中的代碼的下一個(gè)特征是,p/invoke 出現(xiàn)位置的實(shí)際外部方法是 sound 的私有方法。這個(gè)方法只是由公共 messagebeep 方法間接公開,后者接受 beeptypes 類型的參數(shù)。這個(gè)間接的額外層是一個(gè)很關(guān)鍵的細(xì)節(jié),它提供了以下好處。首先,應(yīng)該在類庫(kù)中引入一個(gè)未來的 beep 托管方法,可以重復(fù)地通過公共 messagebeep 方法來使用托管 api,而不必更改應(yīng)用程序中的其余代碼。
 
該包裝方法的第二個(gè)好處是:當(dāng)您進(jìn)行 p/invoke 調(diào)用時(shí),您放棄了免受訪問沖突和其他低級(jí)破壞的權(quán)利,這通常是由 clr 提供的。緩沖方法可以保護(hù)您的應(yīng)用程序的其余部分免受訪問沖突及類似問題的影響(即使它不做任何事而只是傳遞參數(shù))。該緩沖方法將由 p/invoke 調(diào)用引入的任何潛在的錯(cuò)誤本地化。
 
將私有外部方法隱藏在公共包裝后面的第三同時(shí)也是最后的一個(gè)好處是,提供了向該方法添加一些最小的 clr 樣式的機(jī)會(huì)。例如,在圖 1 中,我將 windows api 函數(shù)返回的 boolean 失敗轉(zhuǎn)換成更像 clr 的異常。我還定義了一個(gè)名為 beeptypes 的枚舉類型,它的成員對(duì)應(yīng)于同該 windows api 一起使用的定義值。由于 c# 不支持定義,因此可以使用托管枚舉類型來避免幻數(shù)向整個(gè)應(yīng)用程序代碼擴(kuò)散。
 
包裝方法的最后一個(gè)好處對(duì)于簡(jiǎn)單的 windows api 函數(shù)(如 messagebeep)誠(chéng)然是微不足道的。但是當(dāng)您開始調(diào)用更復(fù)雜的非托管函數(shù)時(shí),您會(huì)發(fā)現(xiàn),手動(dòng)將 windows api 樣式轉(zhuǎn)換成對(duì) clr 更加友好的方法所帶來的好處會(huì)越來越多。越是打算在整個(gè)應(yīng)用程序中重用 interop 功能,越是應(yīng)該認(rèn)真地考慮包裝的設(shè)計(jì)。同時(shí)我認(rèn)為,在非面向?qū)ο蟮撵o態(tài)包裝方法中使用對(duì) clr 友好的參數(shù)也并非不可以。
 
dll import 屬性
 
現(xiàn)在是更深入地進(jìn)行探討的時(shí)候了。在對(duì)托管代碼進(jìn)行 p/invoke 調(diào)用時(shí),dllimportattribute 類型扮演著重要的角色。dllimportattribute 的主要作用是給 clr 指示哪個(gè) dll 導(dǎo)出您想要調(diào)用的函數(shù)。相關(guān) dll 的名稱被作為一個(gè)構(gòu)造函數(shù)參數(shù)傳遞給 dllimportattribute。
 
如果您無法肯定哪個(gè) dll 定義了您要使用的 windows api 函數(shù),platform sdk 文檔將為您提供最好的幫助資源。在 windows api 函數(shù)主題文字臨近結(jié)尾的位置,sdk 文檔指定了 c 應(yīng)用程序要使用該函數(shù)必須鏈接的 .lib 文件。在幾乎所有的情況下,該 .lib 文件具有與定義該函數(shù)的系統(tǒng) dll 文件相同的名稱。例如,如果該函數(shù)需要 c 應(yīng)用程序鏈接到 kernel32.lib,則該函數(shù)就定義在 kernel32.dll 中。您可以在 messagebeep 中找到有關(guān) messagebeep platform sdk 文檔主題。在該主題結(jié)尾處,您會(huì)注意到它指出庫(kù)文件是 user32.lib;這表明 messagebeep 是從 user32.dll 中導(dǎo)出的。
 
可選的 dllimportattribute 屬性
 
除了指出宿主 dll 外,dllimportattribute 還包含了一些可選屬性,其中四個(gè)特別有趣:entrypoint、charset、setlasterror callingconvention。
 
entrypoint 在不希望外部托管方法具有與 dll 導(dǎo)出相同的名稱的情況下,可以設(shè)置該屬性來指示導(dǎo)出的 dll 函數(shù)的入口點(diǎn)名稱。當(dāng)您定義兩個(gè)調(diào)用相同非托管函數(shù)的外部方法時(shí),這特別有用。另外,在 windows 中還可以通過它們的序號(hào)值綁定到導(dǎo)出的 dll 函數(shù)。如果您需要這樣做,則諸如“#1”或“#129”的 entrypoint 值指示 dll 中非托管函數(shù)的序號(hào)值而不是函數(shù)名。
 
charset 對(duì)于字符集,并非所有版本的 windows 都是同樣創(chuàng)建的。windows 9x 系列產(chǎn)品缺少重要的 unicode 支持,而 windows nt windows ce 系列則一開始就使用 unicode。在這些操作系統(tǒng)上運(yùn)行的 clr unicode 用于 string char 數(shù)據(jù)的內(nèi)部表示。但也不必?fù)?dān)心 當(dāng)調(diào)用 windows 9x api 函數(shù)時(shí),clr 會(huì)自動(dòng)進(jìn)行必要的轉(zhuǎn)換,將其從 unicode轉(zhuǎn)換為 ansi。
 
如果 dll 函數(shù)不以任何方式處理文本,則可以忽略 dllimportattribute charset 屬性。然而,當(dāng) char string 數(shù)據(jù)是等式的一部分時(shí),應(yīng)該將 charset 屬性設(shè)置為 charset.auto。這樣可以使 clr 根據(jù)宿主 os 使用適當(dāng)?shù)淖址?。如果沒有顯式地設(shè)置 charset 屬性,則其默認(rèn)值為 charset.ansi。這個(gè)默認(rèn)值是有缺點(diǎn)的,因?yàn)閷?duì)于在 windows 2000、windows xp windows nt? 上進(jìn)行的 interop 調(diào)用,它會(huì)消極地影響文本參數(shù)封送處理的性能。
 
應(yīng)該顯式地選擇 charset.ansi charset.unicode charset 值而不是使用 charset.auto 的唯一情況是:您顯式地指定了一個(gè)導(dǎo)出函數(shù),而該函數(shù)特定于這兩種 win32 os 中的某一種。readdirectorychangesw api 函數(shù)就是這樣的一個(gè)例子,它只存在于基于 windows nt 的操作系統(tǒng)中,并且只支持 unicode;在這種情況下,您應(yīng)該顯式地使用 charset.unicode。
 
有時(shí),windows api 是否有字符集關(guān)系并不明顯。一種決不會(huì)有錯(cuò)的確認(rèn)方法是在 platform sdk 中檢查該函數(shù)的 c 語言頭文件。(如果您無法肯定要看哪個(gè)頭文件,則可以查看 platform sdk 文檔中列出的每個(gè) api 函數(shù)的頭文件。)如果您發(fā)現(xiàn)該 api 函數(shù)確實(shí)定義為一個(gè)映射到以 a w 結(jié)尾的函數(shù)名的宏,則字符集與您嘗試調(diào)用的函數(shù)有關(guān)系。windows api 函數(shù)的一個(gè)例子是在 winuser.h 中聲明的 getmessage api,您也許會(huì)驚訝地發(fā)現(xiàn)它有 a w 兩種版本。
 
setlasterror 錯(cuò)誤處理非常重要,但在編程時(shí)經(jīng)常被遺忘。當(dāng)您進(jìn)行 p/invoke 調(diào)用時(shí),也會(huì)面臨其他的挑戰(zhàn) 處理托管代碼中 windows api 錯(cuò)誤處理和異常之間的區(qū)別。我可以給您一點(diǎn)建議。
 
如果您正在使用 p/invoke 調(diào)用 windows api 函數(shù),而對(duì)于該函數(shù),您使用 getlasterror 來查找擴(kuò)展的錯(cuò)誤信息,則應(yīng)該在外部方法的 dllimportattribute 中將 setlasterror 屬性設(shè)置為 true。這適用于大多數(shù)外部方法。
 
這會(huì)導(dǎo)致 clr 在每次調(diào)用外部方法之后緩存由 api 函數(shù)設(shè)置的錯(cuò)誤。然后,在包裝方法中,可以通過調(diào)用類庫(kù)的 system.runtime.interopservices.marshal 類型中定義的 marshal.getlastwin32error 方法來獲取緩存的錯(cuò)誤值。我的建議是檢查這些期望來自 api 函數(shù)的錯(cuò)誤值,并為這些值引發(fā)一個(gè)可感知的異常。對(duì)于其他所有失敗情況(包括根本就沒意料到的失敗情況),則引發(fā)在 system.componentmodel 命名空間中定義的 win32exception,并將 marshal.getlastwin32error 返回的值傳遞給它。如果您回頭看一下圖 1 中的代碼,您會(huì)看到我在 extern messagebeep 方法的公共包裝中就采用了這種方法。
 
callingconvention 我將在此介紹的最后也可能是最不重要的一個(gè) dllimportattribute 屬性是 callingconvention。通過此屬性,可以給 clr 指示應(yīng)該將哪種函數(shù)調(diào)用約定用于堆棧中的參數(shù)。callingconvention.winapi 的默認(rèn)值是最好的選擇,它在大多數(shù)情況下都可行。然而,如果該調(diào)用不起作用,則可以檢查 platform sdk 中的聲明頭文件,看看您調(diào)用的 api 函數(shù)是否是一個(gè)不符合調(diào)用約定標(biāo)準(zhǔn)的異常 api
 
通常,本機(jī)函數(shù)(例如 windows api 函數(shù)或 c- 運(yùn)行時(shí) dll 函數(shù))的調(diào)用約定描述了如何將參數(shù)推入線程堆?;驈木€程堆棧中清除。大多數(shù) windows api 函數(shù)都是首先將函數(shù)的最后一個(gè)參數(shù)推入堆棧,然后由被調(diào)用的函數(shù)負(fù)責(zé)清理該堆棧。相反,許多 c-運(yùn)行時(shí) dll 函數(shù)都被定義為按照方法參數(shù)在方法簽名中出現(xiàn)的順序?qū)⑵渫迫攵褩#瑢⒍褩G謇砉ぷ鹘唤o調(diào)用者。
 
幸運(yùn)的是,要讓 p/invoke 調(diào)用工作只需要讓外圍設(shè)備理解調(diào)用約定即可。通常,從默認(rèn)值 callingconvention.winapi 開始是最好的選擇。然后,在 c 運(yùn)行時(shí) dll 函數(shù)和少數(shù)函數(shù)中,可能需要將約定更改為 callingconvention.cdecl
 
數(shù)據(jù)封送處理
 
數(shù)據(jù)封送處理是 p/invoke 具有挑戰(zhàn)性的方面。當(dāng)在托管和非托管代碼之間傳遞數(shù)據(jù)時(shí),clr 遵循許多規(guī)則,很少有開發(fā)人員會(huì)經(jīng)常遇到它們直至可將這些規(guī)則記住。除非您是一名類庫(kù)開發(fā)人員,否則在通常情況下沒有必要掌握其細(xì)節(jié)。為了最有效地在 clr 上使用 p/invoke,即使只偶爾需要 interop 的應(yīng)用程序開發(fā)人員仍然應(yīng)該理解數(shù)據(jù)封送處理的一些基礎(chǔ)知識(shí)。
 
在本月專欄的剩余部分中,我將討論簡(jiǎn)單數(shù)字和字符串?dāng)?shù)據(jù)的數(shù)據(jù)封送處理。我將從最基本的數(shù)字?jǐn)?shù)據(jù)封送處理開始,然后介紹簡(jiǎn)單的指針封送處理和字符串封送處理。
 
封送數(shù)字和邏輯標(biāo)量
 
windows os 大部分是用 c 編寫的。因此,windows api 所用到的數(shù)據(jù)類型要么是 c 類型,要么是通過類型定義或宏定義重新標(biāo)記的 c 類型。讓我們看看沒有指針的數(shù)據(jù)封送處理。簡(jiǎn)單起見,首先重點(diǎn)討論的是數(shù)字和布爾值。
 
當(dāng)通過值向 windows api 函數(shù)傳遞參數(shù)時(shí),需要知道以下問題的答案:
 
? 數(shù)據(jù)從根本上講是整型的還是浮點(diǎn)型的?
 
? 如果數(shù)據(jù)是整型的,則它是有符號(hào)的還是無符號(hào)的?
 
? 如果數(shù)據(jù)是整型的,則它的位數(shù)是多少?
 
? 如果數(shù)據(jù)是浮點(diǎn)型的,則它是單精度的還是雙精度的?
 
 
有時(shí)答案很明顯,但有時(shí)卻不明顯。windows api 以各種方式重新定義了基本的 c 數(shù)據(jù)類型。圖 2 列出了 c win32 的一些公共數(shù)據(jù)類型及其規(guī)范,以及一個(gè)具有匹配規(guī)范的公共語言運(yùn)行庫(kù)類型。
 
通常,只要您選擇一個(gè)其規(guī)范與該參數(shù)的 win32 類型相匹配的 clr 類型,您的代碼就能夠正常工作。不過也有一些特例。例如,在 windows api 中定義的 bool 類型是一個(gè)有符號(hào)的 32 位整型。然而,bool 用于指示 boolean true false。雖然您不用將 bool 參數(shù)作為 system.int32 值封送,但是如果使用 system.boolean 類型,就會(huì)獲得更合適的映射。字符類型的映射類似于 bool,因?yàn)橛幸粋€(gè)特定的 clr 類型 (system.char) 指出字符的含義。
 
在了解這些信息之后,逐步介紹示例可能是有幫助的。依然采用 beep 主題作為例子,讓我們來試一下 kernel32.dll 低級(jí) beep,它會(huì)通過計(jì)算機(jī)的揚(yáng)聲器發(fā)生嘟聲。這個(gè)方法的 platform sdk 文檔可以在 beep 中找到。本機(jī) api 按以下方式進(jìn)行記錄:
 
bool beep(
  dword dwfreq,      // frequency
  dword dwduration   // duration in milliseconds
);
 
在參數(shù)封送處理方面,您的工作是了解什么 clr 數(shù)據(jù)類型與 beep api 函數(shù)所使用的 dword bool 數(shù)據(jù)類型相兼容?;仡櫼幌聢D 2 中的圖表,您將看到 dword 是一個(gè) 32 位的無符號(hào)整數(shù)值,如同 clr 類型 system.uint32。這意味著您可以使用 uint32 值作為送往 beep 的兩個(gè)參數(shù)。bool 返回值是一個(gè)非常有趣的情況,因?yàn)樵搱D表告訴我們,在 win32 中,bool 是一個(gè) 32 位的有符號(hào)整數(shù)。因此,您可以使用 system.int32 值作為來自 beep 的返回值。然而,clr 也定義了 system.boolean 類型作為 boolean 值的語義,所以應(yīng)該使用它來替代。clr 默認(rèn)將 system.boolean 值封送為 32 位的有符號(hào)整數(shù)。此處所顯示的外部方法定義是用于 beep 的結(jié)果 p/invoke 方法:
 
[dllimport("kernel32.dll", setlasterror=true)]
static extern boolean beep(
   uint32 frequency, uint32 duration);
 
指針參數(shù)
 
許多 windows api 函數(shù)將指針作為它們的一個(gè)或多個(gè)參數(shù)。指針增加了封送數(shù)據(jù)的復(fù)雜性,因?yàn)樗鼈冊(cè)黾恿艘粋€(gè)間接層。如果沒有指針,您可以通過值在線程堆棧中傳遞數(shù)據(jù)。有了指針,則可以通過引用傳遞數(shù)據(jù),方法是將該數(shù)據(jù)的內(nèi)存地址推入線程堆棧中。然后,函數(shù)通過內(nèi)存地址間接訪問數(shù)據(jù)。使用托管代碼表示此附加間接層的方式有多種。
 
c# 中,如果將方法參數(shù)定義為 ref out,則數(shù)據(jù)通過引用而不是通過值傳遞。即使您沒有使用 interop 也是這樣,但只是從一個(gè)托管方法調(diào)用到另一個(gè)托管方法。例如,如果通過 ref 傳遞 system.int32 參數(shù),則在線程堆棧中傳遞的是該數(shù)據(jù)的地址,而不是整數(shù)值本身。下面是一個(gè)定義為通過引用接收整數(shù)值的方法的示例:
 
void flipint32(ref int32 num){
   num = -num;
}
 
這里,flipint32 方法獲取一個(gè) int32 值的地址、訪問數(shù)據(jù)、對(duì)它求反,然后將求反過的值賦給原始變量。在以下代碼中,flipint32 方法會(huì)將調(diào)用程序的變量 x 的值從 10 更改為 -10
 
int32 x = 10;
flipint32(ref x);
 
在托管代碼中可以重用這種能力,將指針傳遞給非托管代碼。例如,fileencryptionstatus api 函數(shù)以 32 位無符號(hào)位掩碼的形式返回文件加密狀態(tài)。該 api 按以下所示方式進(jìn)行記錄:
 
bool fileencryptionstatus(
  lpctstr lpfilename,  // file name
  lpdword lpstatus     // encryption status
);
 
請(qǐng)注意,該函數(shù)并不使用它的返回值返回狀態(tài),而是返回一個(gè) boolean 值,指示調(diào)用是否成功。在成功的情況下,實(shí)際的狀態(tài)值是通過第二個(gè)參數(shù)返回的。它的工作方式是調(diào)用程序向該函數(shù)傳遞指向一個(gè) dword 變量的指針,而該 api 函數(shù)用狀態(tài)值填充指向的內(nèi)存位置。以下代碼片段顯示了一個(gè)調(diào)用非托管 fileencryptionstatus 函數(shù)的可能外部方法定義:
 
[dllimport("advapi32.dll", charset=charset.auto)]
static extern boolean fileencryptionstatus(string filename,
   out uint32 status);
 
該定義使用 out 關(guān)鍵字來為 uint32 狀態(tài)值指示 by-ref 參數(shù)。這里我也可以選擇 ref 關(guān)鍵字,實(shí)際上在運(yùn)行時(shí)會(huì)產(chǎn)生相同的機(jī)器碼。out 關(guān)鍵字只是一個(gè) by-ref 參數(shù)的規(guī)范,它向 c# 編譯器指示所傳遞的數(shù)據(jù)只在被調(diào)用的函數(shù)外部傳遞。相反,如果使用 ref 關(guān)鍵字,則編譯器會(huì)假定數(shù)據(jù)可以在被調(diào)用的函數(shù)的內(nèi)部和外部傳遞。
 
托管代碼中 out ref 參數(shù)的另一個(gè)很好的方面是,地址作為 by-ref 參數(shù)傳遞的變量可以是線程堆棧中的一個(gè)本地變量、一個(gè)類或結(jié)構(gòu)的元素,也可以是具有合適數(shù)據(jù)類型的數(shù)組中的一個(gè)元素引用。調(diào)用程序的這種靈活性使得 by-ref 參數(shù)成為封送緩沖區(qū)指針以及單數(shù)值指針的一個(gè)很好的起點(diǎn)。只有在我發(fā)現(xiàn) ref out 參數(shù)不符合我的需要的情況下,我才會(huì)考慮將指針封送為更復(fù)雜的 clr 類型(例如類或數(shù)組對(duì)象)。
 
如果您不熟悉 c 語法或者調(diào)用 windows api 函數(shù),有時(shí)很難知道一個(gè)方法參數(shù)是否需要指針。一個(gè)常見的指示符是看參數(shù)類型是否是以字母 p lp 開頭的,例如 lpdword pint。在這兩個(gè)例子中,lp p 指示參數(shù)是一個(gè)指針,而它們指向的數(shù)據(jù)類型分別為 dword int。然而,在有些情況下,可以直接使用 c 語言語法中的星號(hào) (*) api 函數(shù)定義為指針。以下代碼片段展示了這方面的示例:
 
void takesapointer(dword* pnum);
 
可以看到,上述函數(shù)的唯一一個(gè)參數(shù)是指向 dword 變量的指針。
 
當(dāng)通過 p/invoke 封送指針時(shí),ref out 只用于托管代碼中的值類型。當(dāng)一個(gè)參數(shù)的 clr 類型使用 struct 關(guān)鍵字定義時(shí),可以認(rèn)為該參數(shù)是一個(gè)值類型。out ref 用于封送指向這些數(shù)據(jù)類型的指針,因?yàn)橥ǔV殿愋妥兞渴菍?duì)象或數(shù)據(jù),而在托管代碼中并沒有對(duì)值類型的引用。相反,當(dāng)封送引用類型對(duì)象時(shí),并不需要 ref out 關(guān)鍵字,因?yàn)樽兞恳呀?jīng)是對(duì)象的引用了。
 
如果您對(duì)引用類型和值類型之間的差別不是很熟悉,請(qǐng)查閱 2000 12 發(fā)行的 msdn? magazine,在 .net 專欄的主題中可以找到更多信息。大多數(shù) clr 類型都是引用類型;然而,除了 system.string system.object,所有的基元類型(例如 system.int32 system.boolean)都是值類型。
 
封送不透明 (opaque) 指針:一種特殊情況
 
有時(shí)在 windows api 中,方法傳遞或返回的指針是不透明的,這意味著該指針值從技術(shù)角度講是一個(gè)指針,但代碼卻不直接使用它。相反,代碼將該指針返回給 windows 以便隨后進(jìn)行重用。
 
一個(gè)非常常見的例子就是句柄的概念。在 windows 中,內(nèi)部數(shù)據(jù)結(jié)構(gòu)(從文件到屏幕上的按鈕)在應(yīng)用程序代碼中都表示為句柄。句柄其實(shí)就是不透明的指針或有著指針寬度的數(shù)值,應(yīng)用程序用它來表示內(nèi)部的 os 構(gòu)造。
 
少數(shù)情況下,api 函數(shù)也將不透明指針定義為 pvoid lpvoid 類型。在 windows api 的定義中,這些類型意思就是說該指針沒有類型。
 
當(dāng)一個(gè)不透明指針返回給您的應(yīng)用程序(或者您的應(yīng)用程序期望得到一個(gè)不透明指針)時(shí),您應(yīng)該將參數(shù)或返回值封送為 clr 中的一種特殊類型 system.intptr。當(dāng)您使用 intptr 類型時(shí),通常不使用 out ref 參數(shù),因?yàn)?font face="times new roman"> intptr 意為直接持有指針。不過,如果您將一個(gè)指針封送為一個(gè)指針,則對(duì) intptr 使用 by-ref 參數(shù)是合適的。
 
clr 類型系統(tǒng)中,system.intptr 類型有一個(gè)特殊的屬性。不像系統(tǒng)中的其他基類型,intptr 并沒有固定的大小。相反,它在運(yùn)行時(shí)的大小是依底層操作系統(tǒng)的正常指針大小而定的。這意味著在 32 位的 windows 中,intptr 變量的寬度是 32 位的,而在 64 位的 windows 中,實(shí)時(shí)編譯器編譯的代碼會(huì)將 intptr 值看作 64 位的值。當(dāng)在托管代碼和非托管代碼之間封送不透明指針時(shí),這種自動(dòng), 調(diào)節(jié)大小的特點(diǎn)十分有用。
 
請(qǐng)記住,任何返回或接受句柄的 api 函數(shù)其實(shí)操作的就是不透明指針。您的代碼應(yīng)該將 windows 中的句柄封送成 system.intptr 值。
 
您可以在托管代碼中將 intptr 值強(qiáng)制轉(zhuǎn)換為 32 位或 64 位的整數(shù)值,或?qū)⒑笳邚?qiáng)制轉(zhuǎn)換為前者。然而,當(dāng)使用 windows api 函數(shù)時(shí),因?yàn)橹羔槕?yīng)是不透明的,所以除了存儲(chǔ)和傳遞給外部方法外,不能將它們另做它用。這種“只限存儲(chǔ)和傳遞”規(guī)則的兩個(gè)特例是當(dāng)您需要向外部方法傳遞 null 指針值和需要比較 intptr 值與 null 值的情況。為了做到這一點(diǎn),您不能將零強(qiáng)制轉(zhuǎn)換為 system.intptr,而應(yīng)該在 intptr 類型上使用 int32.zero 靜態(tài)公共字段,以便獲得用于比較或賦值的 null 值。
 
封送文本
 
在編程時(shí)經(jīng)常要對(duì)文本數(shù)據(jù)進(jìn)行處理。文本為 interop 制造了一些麻煩,這有兩個(gè)原因。首先,底層操作系統(tǒng)可能使用 unicode 來表示字符串,也可能使用 ansi。在極少數(shù)情況下,例如 multibytetowidechar api 函數(shù)的兩個(gè)參數(shù)在字符集上是不一致的。
 
第二個(gè)原因是,當(dāng)需要進(jìn)行 p/invoke 時(shí),要處理文本還需要特別了解到 c clr 處理文本的方式是不同的。在 c 中,字符串實(shí)際上只是一個(gè)字符值數(shù)組,通常以 null 作為結(jié)束符。大多數(shù) windows api 函數(shù)是按照以下條件處理字符串的:對(duì)于 ansi,將其作為字符值數(shù)組;對(duì)于 unicode,將其作為寬字符值數(shù)組。
 
幸運(yùn)的是,clr 被設(shè)計(jì)得相當(dāng)靈活,當(dāng)封送文本時(shí)問題得以輕松解決,而不用在意 windows api 函數(shù)期望從您的應(yīng)用程序得到的是什么。這里是一些需要記住的主要考慮事項(xiàng):
 
? 是您的應(yīng)用程序向 api 函數(shù)傳遞文本數(shù)據(jù),還是 api 函數(shù)向您的應(yīng)用程序返回字符串?dāng)?shù)據(jù)?或者二者兼有?
 
? 您的外部方法應(yīng)該使用什么托管類型?
 
? api 函數(shù)期望得到的是什么格式的非托管字符串?
 
 
我們首先解答最后一個(gè)問題。大多數(shù) windows api 函數(shù)都帶有 lptstr lpctstr 值。(從函數(shù)角度看)它們分別是可修改和不可修改的緩沖區(qū),包含以 null 結(jié)束的字符數(shù)組。“c”代表常數(shù),意味著使用該參數(shù)信息不會(huì)傳遞到函數(shù)外部。lptstr 中的“t”表明該參數(shù)可以是 unicode ansi,取決于您選擇的字符集和底層操作系統(tǒng)的字符集。因?yàn)樵?font face="times new roman"> windows api 中大多數(shù)字符串參數(shù)都是這兩種類型之一,所以只要在 dllimportattribute 中選擇 charset.auto,clr 就按默認(rèn)的方式工作。
 
然而,有些 api 函數(shù)或自定義的 dll 函數(shù)采用不同的方式表示字符串。如果您要用到一個(gè)這樣的函數(shù),就可以采用 marshalasattribute 修飾外部方法的字符串參數(shù),并指明一種不同于默認(rèn) lptstr 的字符串格式。有關(guān) marshalasattribute 的更多信息,請(qǐng)參閱位于 marshalasattribute class platform sdk 文檔主題。
 
現(xiàn)在讓我們看一下字符串信息在您的代碼和非托管函數(shù)之間傳遞的方向。有兩種方式可以知道處理字符串時(shí)信息的傳遞方向。第一個(gè)也是最可靠的一個(gè)方法就是首先理解參數(shù)的用途。例如,您正調(diào)用一個(gè)參數(shù),它的名稱類似 createmutex 并帶有一個(gè)字符串,則可以想像該字符串信息是從應(yīng)用程序向 api 函數(shù)傳遞的。同時(shí),如果您調(diào)用 getusername,則該函數(shù)的名稱表明字符串信息是從該函數(shù)向您的應(yīng)用程序傳遞的。
 
除了這種比較合理的方法外,第二種查找信息傳遞方向的方式就是查找 api 參數(shù)類型中的字母“c”。例如,getusername api 函數(shù)的第一個(gè)參數(shù)被定義為 lptstr 類型,它代表一個(gè)指向 unicode ansi 字符串緩沖區(qū)的長(zhǎng)指針。但是 createmutex 的名稱參數(shù)被類型化為 ltctstr。請(qǐng)注意,這里的類型定義是一樣的,但增加一個(gè)字母“c”來表明緩沖區(qū)為常數(shù),api 函數(shù)不能寫入。
 
一旦明確了文本參數(shù)是只用作輸入還是用作輸入/輸出,就可以確定使用哪種 clr 類型作為參數(shù)類型。這里有一些規(guī)則。如果字符串參數(shù)只用作輸入,則使用 system.string 類型。在托管代碼中,字符串是不變的,適合用于不會(huì)被本機(jī) api 函數(shù)更改的緩沖區(qū)。
 
如果字符串參數(shù)可以用作輸入和/或輸出,則使用 system.stringbuilder 類型。stringbuilder 類型是一個(gè)很有用的類庫(kù)類型,它可以幫助您有效地構(gòu)建字符串,也正好可以將緩沖區(qū)傳遞給本機(jī)函數(shù),由本機(jī)函數(shù)為您填充字符串?dāng)?shù)據(jù)。一旦函數(shù)調(diào)用返回,您只需要調(diào)用 stringbuilder 對(duì)象的 tostring 就可以得到一個(gè) string 對(duì)象。
 
getshortpathname api 函數(shù)能很好地用于顯示什么時(shí)候使用 string、什么時(shí)候使用 stringbuilder,因?yàn)樗粠в腥齻€(gè)參數(shù):一個(gè)輸入字符串、一個(gè)輸出字符串和一個(gè)指明輸出緩沖區(qū)的字符長(zhǎng)度的參數(shù)。
 
3 所示為加注釋的非托管 getshortpathname 函數(shù)文檔,它同時(shí)指出了輸入和輸出字符串參數(shù)。它引出了托管的外部方法定義,也如圖 3 所示。請(qǐng)注意第一個(gè)參數(shù)被封送為 system.string,因?yàn)樗且粋€(gè)只用作輸入的參數(shù)。第二個(gè)參數(shù)代表一個(gè)輸出緩沖區(qū),它使用了 system.stringbuilder。
 
小結(jié)
 
本月專欄所介紹的 p/invoke 功能足夠調(diào)用 windows 中的許多 api 函數(shù)。然而,如果您大量用到 interop,則會(huì)最終發(fā)現(xiàn)自己封送了很復(fù)雜的數(shù)據(jù)結(jié)構(gòu),甚至可能需要在托管代碼中通過指針直接訪問內(nèi)存。實(shí)際上,本機(jī)代碼中的 interop 可以是一個(gè)將細(xì)節(jié)和低級(jí)比特藏在里面的真正的潘多拉盒子。clr、c# 和托管 c++ 提供了許多有用的功能;也許以后我會(huì)在本專欄介紹高級(jí)的 p/invoke 話題。
 
同時(shí),只要您覺得 .net framework 類庫(kù)無法播放您的聲音或者為您執(zhí)行其他一些功能,您可以知道如何向原始而優(yōu)秀的 windows api 尋求一些幫助。
 
api(應(yīng)用編程接口)是程序與處理器接口的命令集。最常用的就是在外部調(diào)用微軟windows內(nèi)部的進(jìn)程。windows api包括成千的你可以使用的函數(shù)、結(jié)構(gòu)、常量。這些函數(shù)是用c語言寫的,在使用他們之前,你必須聲明。 定義dll的進(jìn)程將相當(dāng)?shù)膹?fù)雜,甚至比vb還復(fù)雜。你可以使用api viewer工具得到api函數(shù)的聲明,但是必須注意的是,它的參數(shù)類型跟c#的不一樣。
大部分的高級(jí)語言都支持api,微軟函數(shù)類庫(kù)(mfc)封裝了大部分的win32 api。odbc api對(duì)提高數(shù)據(jù)庫(kù)的操作速度大有好處。使用api,可以請(qǐng)求更底層的系統(tǒng)服務(wù)。api從簡(jiǎn)單的對(duì)話框到復(fù)雜的加密運(yùn)算都提供支持。開發(fā)者應(yīng)該知道如何在他們程序中使用api
api有許多類型,(針對(duì)不同的操作系統(tǒng)、處理器…………)
os specific api:
操作系統(tǒng)特有api:
每種操作系統(tǒng)都有一套公用api和專有api。比如:windows nt 支持ms-dos, win16, win32, posix (便攜式操作系統(tǒng)接口),os/2 console api ;同時(shí)windows 95 supports ms-dos, win16 win32 api
win16 win32 api:
win16 是基于16位的處理器,并使用16位的值,它是一個(gè)獨(dú)立的平臺(tái)。比如:你可以運(yùn)行tsr 程序在ms-dos環(huán)境下。
win32 是基于32位的處理器,并使用32位的值。他可用于任何操作系統(tǒng),它的使用范圍更廣。
win32 api has 32 prefix after the library name e.g. kernel32, user32 etc?
win32 apidll一般都具有32的后綴,比如:kernel32, user32等。
所有的api都在下面3個(gè)dll中實(shí)現(xiàn)的。
kernel
user
gdi
1. kernel
它的庫(kù)名是:kernel32.dll,它是操作系統(tǒng)管理的api
process loading. 加載進(jìn)程
context switching.
file i/o. 文件操作
memory management. 內(nèi)存管理
比如:globalmemorystatus 函數(shù)獲得目前系統(tǒng)物理虛擬內(nèi)存的使用信息。
2. user
win32下,它的庫(kù)名是 user32.dll
this allows managing the entire user interfaces such as
它管理全部的用戶界面,比如:
windows 窗口
menus 菜單
dialog boxes 對(duì)話框
icons etc., 圖標(biāo)等
比如:drawicon 畫一個(gè)圖標(biāo)在指定的設(shè)備上。
3. gdi (graphical device interface)
這個(gè)dllgdi32.dll,它負(fù)責(zé)圖像的輸出,使用gdi繪出窗口,菜單,對(duì)話框
it can create graphical output. 輸出圖像
比如:createbitmap 函數(shù)創(chuàng)建一個(gè)指定寬度、高度和顏色格式的位圖。
c#api的工具對(duì)初學(xué)者是相當(dāng)不錯(cuò)的。在c#使用中使用api之前,你應(yīng)該先知道c#中如何使用結(jié)構(gòu)、類型轉(zhuǎn)換,安全與不安全代碼等。
使用復(fù)雜的api之前,我們先用一個(gè)簡(jiǎn)單的messagebox api作為列子。打開一個(gè)c#工程,增加一個(gè)按鈕,在按鈕的點(diǎn)擊事件中,我們將顯示一個(gè)信息框。
增加使用外部庫(kù)的命名空間:
using system.runtime.interopservices;
下面聲明api
[dllimport("user32.dll")]
dllimport屬性用來指定包含外部方法的動(dòng)態(tài)連接庫(kù)的位置。 "user32.dll"指出了庫(kù)名,static 指明它不屬于特定的對(duì)象。extern 指明是一個(gè)外部的方法。帶有dllimport 屬性的方法必須帶有修飾符extern 。
messagebox 是一個(gè)漢數(shù)名,帶四個(gè)參數(shù)返回一個(gè)int型值。
許多api使用結(jié)構(gòu)來傳遞、返回參數(shù),這樣可以減少?gòu)?fù)雜度。它也允許使用象messagebox 函數(shù)那樣,使用固定的參數(shù)。
 
在按鈕的點(diǎn)擊事件中增加下面代碼:
protected void button1_click (object sender, system.eventargs e)
{
messagebox (0,"api message box","api demo",0);
}
編譯并運(yùn)行程序,點(diǎn)擊按鈕以后,你就可以看到一個(gè)由api調(diào)用的信息框。
using structure 使用結(jié)構(gòu)
api中經(jīng)常使用復(fù)雜的結(jié)構(gòu)。不過一旦你明白了他們,將是很簡(jiǎn)單的。
in next example we will use getsysteminfo api which returns information about the current system.
下面的列子,我們用getsysteminfo api得到當(dāng)前系統(tǒng)的信息。
第一步:增加一個(gè)c#窗口,并在上面增加一個(gè)按鈕,在窗口代碼頁增加一個(gè)命名空間:
using system.runtime.interopservices;
聲明 getsysteminfo 的參數(shù)結(jié)構(gòu):
[structlayout(layoutkind.sequential)]
public struct system_info {
public uint dwoemid;
public uint dwpagesize;
public uint lpminimumapplicationaddress;
public uint lpmaximumapplicationaddress;
public uint dwactiveprocessormask;
public uint dwnumberofprocessors;
public uint dwprocessortype;
public uint dwallocationgranularity;
public uint dwprocessorlevel;
public uint dwprocessorrevision;
}
聲明api函數(shù):
[dllimport("kernel32")]
static extern void getsysteminfo(ref system_info psi);
ref是一個(gè)標(biāo)志參量傳遞形式的關(guān)鍵字,它使傳入傳出的變量指向同一個(gè)變量(傳址傳遞)
在按鈕點(diǎn)擊事件中增加下面的代碼,
protected void button1_click (object sender, system.eventargs e)
{
try
{
system_info psi = new system_info();
getsysteminfo(ref psi);
once you retrieve the structure perform operations on required parameter
比如:
listbox1.items.insert(0,psi.dwactiveprocessormask.tostring());
}
catch(exception er)
{
messagebox.show (er.message);
}
}
 
 
visual c#調(diào)用windows api函數(shù)
 
北京機(jī)械工業(yè)學(xué)院研00級(jí)(100085)冉林倉(cāng) 
 
    api函數(shù)是構(gòu)筑windws應(yīng)用程序的基石,每一種windows應(yīng)用程序開發(fā)工具,它提供的底層函數(shù)都間接或直接地調(diào)用了windows api函數(shù),同時(shí)為了實(shí)現(xiàn)功能擴(kuò)展,一般也都提供了調(diào)用windowsapi函數(shù)的接口, 也就是說具備調(diào)用動(dòng)態(tài)連接庫(kù)的能力。visual c#和其它開發(fā)工具一樣也能夠調(diào)用動(dòng)態(tài)鏈接庫(kù)的api函數(shù)。.net框架本身提供了這樣一種服務(wù),允許受管轄的代碼調(diào)用動(dòng)態(tài)鏈接庫(kù)中實(shí)現(xiàn)的非受管轄函數(shù),包括操作系統(tǒng)提供的windows api函數(shù)。它能夠定位和調(diào)用輸出函數(shù),根據(jù)需要,組織其各個(gè)參數(shù)(整型、字符串類型、數(shù)組、和結(jié)構(gòu)等等)跨越互操作邊界。
 
下面以c#為例簡(jiǎn)單介紹調(diào)用api的基本過程: 
動(dòng)態(tài)鏈接庫(kù)函數(shù)的聲明 
 動(dòng)態(tài)鏈接庫(kù)函數(shù)使用前必須聲明,相對(duì)于vb,c#函數(shù)聲明顯得更加羅嗦,前者通過 api viewer粘貼以后,可以直接使用,而后者則需要對(duì)參數(shù)作些額外的變化工作。
 
 動(dòng)態(tài)鏈接庫(kù)函數(shù)聲明部分一般由下列兩部分組成,一是函數(shù)名或索引號(hào),二是動(dòng)態(tài)鏈接庫(kù)的文件名。 
  譬如,你想調(diào)用user32.dll中的messagebox函數(shù),我們必須指明函數(shù)的名字messageboxamessageboxw,以及庫(kù)名字user32.dll,我們知道win32 api對(duì)每一個(gè)涉及字符串和字符的函數(shù)一般都存在兩個(gè)版本,單字節(jié)字符的ansi版本和雙字節(jié)字符的unicode版本。
 
 下面是一個(gè)調(diào)用api函數(shù)的例子: 
[dllimport("kernel32.dll", entrypoint="movefilew", setlasterror=true, 
charset=charset.unicode, exactspelling=true, 
callingconvention=callingconvention.stdcall)] 
public static extern bool movefile(string src, string dst); 
 
 其中入口點(diǎn)entrypoint標(biāo)識(shí)函數(shù)在動(dòng)態(tài)鏈接庫(kù)的入口位置,在一個(gè)受管轄的工程中,目標(biāo)函數(shù)的原始名字和序號(hào)入口點(diǎn)不僅標(biāo)識(shí)一個(gè)跨越互操作界限的函數(shù)。而且,你還可以把這個(gè)入口點(diǎn)映射為一個(gè)不同的名字,也就是對(duì)函數(shù)進(jìn)行重命名。重命名可以給調(diào)用函數(shù)帶來種種便利,通過重命名,一方面我們不用為函數(shù)的大小寫傷透腦筋,同時(shí)它也可以保證與已有的命名規(guī)則保持一致,允許帶有不同參數(shù)類型的函數(shù)共存,更重要的是它簡(jiǎn)化了對(duì)ansiunicode版本的調(diào)用。charset用于標(biāo)識(shí)函數(shù)調(diào)用所采用的是unicode或是ansi版本,exactspellingfalse將告訴編譯器,讓編譯器決定使用unicode或者是ansi版本。其它的參數(shù)請(qǐng)參考msdn在線幫助.
 
 在c#中,你可以在entrypoint域通過名字和序號(hào)聲明一個(gè)動(dòng)態(tài)鏈接庫(kù)函數(shù),如果在方法定義中使用的函數(shù)名與dll入口點(diǎn)相同,你不需要在entrypoint域顯示聲明函數(shù)。否則,你必須使用下列屬性格式指示一個(gè)名字和序號(hào)。
 
[dllimport("dllname", entrypoint="functionname")] 
[dllimport("dllname", entrypoint="#123")] 
值得注意的是,你必須在數(shù)字序號(hào)前加“#” 
下面是一個(gè)用msgbox替換messagebox名字的例子: 
[c#] 
using system.runtime.interopservices; 
 
public class win32 { 
[dllimport("user32.dll", entrypoint="messagebox")] 
public static extern int msgbox(int hwnd, string text, string caption, uint type); 
許多受管轄的動(dòng)態(tài)鏈接庫(kù)函數(shù)期望你能夠傳遞一個(gè)復(fù)雜的參數(shù)類型給函數(shù),譬如一個(gè)用戶定義的結(jié)構(gòu)類型成員或者受管轄代碼定義的一個(gè)類成員,這時(shí)你必須提供額外的信息格式化這個(gè)類型,以保持參數(shù)原有的布局和對(duì)齊。
 
c#提供了一個(gè)structlayoutattribute類,通過它你可以定義自己的格式化類型,在受管轄代碼中,格式化類型是一個(gè)用structlayoutattribute說明的結(jié)構(gòu)或類成員,通過它能夠保證其內(nèi)部成員預(yù)期的布局信息。布局的選項(xiàng)共有三種:
 
布局選項(xiàng) 
描述 
layoutkind.automatic 
為了提高效率允許運(yùn)行態(tài)對(duì)類型成員重新排序。 
注意:永遠(yuǎn)不要使用這個(gè)選項(xiàng)來調(diào)用不受管轄的動(dòng)態(tài)鏈接庫(kù)函數(shù)。 
layoutkind.explicit 
對(duì)每個(gè)域按照fieldoffset屬性對(duì)類型成員排序 
layoutkind.sequential 
對(duì)出現(xiàn)在受管轄類型定義地方的不受管轄內(nèi)存中的類型成員進(jìn)行排序。 
傳遞結(jié)構(gòu)成員 
下面的例子說明如何在受管轄代碼中定義一個(gè)點(diǎn)和矩形類型,并作為一個(gè)參數(shù)傳遞給user32.dll庫(kù)中的ptinrect函數(shù), 
函數(shù)的不受管轄原型聲明如下: 
bool ptinrect(const rect *lprc, point pt); 
注意你必須通過引用傳遞rect結(jié)構(gòu)參數(shù),因?yàn)楹瘮?shù)需要一個(gè)rect的結(jié)構(gòu)指針。 
[c#] 
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 win32api { 
[dllimport("user32.dll")] 
public static extern bool ptinrect(ref rect r, point p); 
類似你可以調(diào)用getsysteminfo函數(shù)獲得系統(tǒng)信息: 
? using system.runtime.interopservices; 
[structlayout(layoutkind.sequential)] 
public struct system_info { 
public uint dwoemid; 
public uint dwpagesize; 
public uint lpminimumapplicationaddress; 
public uint lpmaximumapplicationaddress; 
public uint dwactiveprocessormask; 
public uint dwnumberofprocessors; 
public uint dwprocessortype; 
public uint dwallocationgranularity; 
public uint dwprocessorlevel; 
public uint dwprocessorrevision; 
 
 
 
 
[dllimport("kernel32")] 
static extern void getsysteminfo(ref system_info psi); 
 
system_info psi = new system_info(); 
getsysteminfo(ref psi); 
 
類成員的傳遞 
同樣只要類具有一個(gè)固定的類成員布局,你也可以傳遞一個(gè)類成員給一個(gè)不受管轄的動(dòng)態(tài)鏈接庫(kù)函數(shù),下面的例子主要說明如何傳遞一個(gè)sequential順序定義的mysystemtime類給user32.dllgetsystemtime函數(shù), 函數(shù)用c/c++調(diào)用規(guī)范如下:
 
void getsystemtime(systemtime* systemtime); 
不像傳值類型,類總是通過引用傳遞參數(shù)
[c#] 
[structlayout(layoutkind.sequential)] 
public class mysystemtime { 
public ushort wyear; 
public ushort wmonth; 
public ushort wdayofweek; 
public ushort wday; 
public ushort whour; 
public ushort wminute; 
public ushort wsecond; 
public ushort wmilliseconds; 
class win32api { 
[dllimport("user32.dll")] 
public static extern void getsystemtime(mysystemtime st); 
回調(diào)函數(shù)的傳遞
從受管轄的代碼中調(diào)用大多數(shù)動(dòng)態(tài)鏈接庫(kù)函數(shù),你只需創(chuàng)建一個(gè)受管轄的函數(shù)定義,然后調(diào)用它即可,這個(gè)過程非常直接。 
如果一個(gè)動(dòng)態(tài)鏈接庫(kù)函數(shù)需要一個(gè)函數(shù)指針作為參數(shù),你還需要做以下幾步: 
首先,你必須參考有關(guān)這個(gè)函數(shù)的文檔,確定這個(gè)函數(shù)是否需要一個(gè)回調(diào);第二,你必須在受管轄代碼中創(chuàng)建一個(gè)回調(diào)函數(shù);最后,你可以把指向這個(gè)函數(shù)的指針作為一個(gè)參數(shù)創(chuàng)遞給dll函數(shù),.
 
回調(diào)函數(shù)及其實(shí)現(xiàn)
回調(diào)函數(shù)經(jīng)常用在任務(wù)需要重復(fù)執(zhí)行的場(chǎng)合,譬如用于枚舉函數(shù),譬如win32 api 中的enumfontfamilies(字體枚舉), enumprinters(打印機(jī)), enumwindows (窗口枚舉)函數(shù). 下面以窗口枚舉為例,談?wù)勅绾瓮ㄟ^調(diào)用enumwindow 函數(shù)遍歷系統(tǒng)中存在的所有窗口
 
分下面幾個(gè)步驟
1. 在實(shí)現(xiàn)調(diào)用前先參考函數(shù)的聲明 
bool enumwindows(wndenumproc lpenumfunc, lparmam iparam) 
顯然這個(gè)函數(shù)需要一個(gè)回調(diào)函數(shù)地址作為參數(shù)
2. 創(chuàng)建一個(gè)受管轄的回調(diào)函數(shù),這個(gè)例子聲明為代表類型(delegate),也就是我們所說的回調(diào),它帶有兩個(gè)參數(shù)hwndlparam,第一個(gè)參數(shù)是一個(gè)窗口句柄,第二個(gè)參數(shù)由應(yīng)用程序定義,兩個(gè)參數(shù)均為整形。
 
  當(dāng)這個(gè)回調(diào)函數(shù)返回一個(gè)非零值時(shí),標(biāo)示執(zhí)行成功,零則暗示失敗,這個(gè)例子總是返回true值,以便持續(xù)枚舉。 
3. 最后創(chuàng)建以代表對(duì)象(delegate),并把它作為一個(gè)參數(shù)傳遞給enumwindows 函數(shù),平臺(tái)會(huì)自動(dòng)地 把代表轉(zhuǎn)化成函數(shù)能夠識(shí)別的回調(diào)格式。
 
[c#] 
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("窗口句柄為"); 
console.writeline(hwnd); 
return true; 
 
指針類型參數(shù)傳遞: 
 在windows api函數(shù)調(diào)用時(shí),大部分函數(shù)采用指針傳遞參數(shù),對(duì)一個(gè)結(jié)構(gòu)變量指針,我們除了使用上面的類和結(jié)構(gòu)方法傳遞參數(shù)之外,我們有時(shí)還可以采用數(shù)組傳遞參數(shù)。
 
 下面這個(gè)函數(shù)通過調(diào)用getusername獲得用戶名 
bool getusername( 
lptstr lpbuffer, // 用戶名緩沖區(qū) 
lpdword nsize // 存放緩沖區(qū)大小的地址指針 
); 
  
[dllimport("advapi32.dll", 
entrypoint="getcomputername", 
exactspelling=false, 
setlasterror=true)] 
static extern bool getcomputername ( 
[marshalas(unmanagedtype.lparray)] byte[] lpbuffer, 
  [marshalas(unmanagedtype.lparray)] int32[] nsize ); 
 這個(gè)函數(shù)接受兩個(gè)參數(shù),char * int *,因?yàn)槟惚仨毞峙湟粋€(gè)字符串緩沖區(qū)以接受字符串指針,你可以使用string類代替這個(gè)參數(shù)類型,當(dāng)然你還可以聲明一個(gè)字節(jié)數(shù)組傳遞ansi字符串,同樣你也可以聲明一個(gè)只有一個(gè)元素的長(zhǎng)整型數(shù)組,使用數(shù)組名作為第二個(gè)參數(shù)。上面的函數(shù)可以調(diào)用如下:
 
byte[] str=new byte[20]; 
int32[] len=new int32[1]; 
len[0]=20; 
getcomputername (str,len); 
messagebox.show(system.text.encoding.ascii.getstring(str)); 
 最后需要提醒的是,每一種方法使用前必須在文件頭加上: 
 using system.runtime.interopservices; 
 

商業(yè)源碼熱門下載www.html.org.cn

發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 古蔺县| 雅江县| 微山县| 常宁市| 鹿泉市| 神池县| 长沙县| 正安县| 庄浪县| 荣昌县| 汝南县| 泾源县| 平顶山市| 临夏市| 石家庄市| 简阳市| 呼玛县| 新兴县| 陇西县| 普格县| 紫金县| 黔东| 宿松县| 沂南县| 平原县| 佳木斯市| 喀喇沁旗| 南通市| 土默特右旗| 宜宾市| 泰和县| 凉山| 大冶市| 修文县| 贵德县| 密山市| 池州市| 上饶县| 神池县| 班戈县| 岳普湖县|