版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
在C++Builder里創(chuàng)建可以被VisualC++使用的DLL
這篇文章舉例說明如何用C++Builder創(chuàng)建一個(gè)DLL,使它可以在VisualC++工程里調(diào)用。
?簡(jiǎn)介:為什么這個(gè)這么難
?指導(dǎo)方針摘要
?例1:隱式連接
?例2:顯式連接
?例3:用#define組裝隱式連接
?例4:用stdcall函數(shù)隱式連接
?結(jié)論
簡(jiǎn)介:為什么這個(gè)這么難
如果你用BCB創(chuàng)建了一個(gè)DLL,它可以被BCB的可執(zhí)行文件調(diào)用,你知道這種使用
DLL的方式?jīng)]什么難度。當(dāng)你構(gòu)造一個(gè)DLL,BCB生成一個(gè)帶".LIB”擴(kuò)展名的引入庫(kù)。
把這個(gè)L1B文件添加到你的工程里。連接器按引入庫(kù)決定DLL內(nèi)部調(diào)用。當(dāng)你運(yùn)行你的程
序時(shí),DLL隱式的被載入,你不必去考慮DLL內(nèi)部調(diào)用工作是。
當(dāng)EXE文件是由MicrosoftVisualC++編譯的時(shí)候,情況會(huì)變得比較復(fù)雜。有3個(gè)主要
的問題。首先,BCB和MSVC對(duì)DLL中的函數(shù)命名方式是不一致的。BCB使用一種習(xí)慣,
MSVC使用另一種不同的習(xí)慣。當(dāng)然,兩種習(xí)慣是不兼容的。命名問題在如何在C++Builder
工程里使用VC++編譯的DLL那篇文章里已經(jīng)討論過了。表1總結(jié)了各個(gè)編譯器在各自的
調(diào)用習(xí)慣下,導(dǎo)出的MyFunction函數(shù)。注意Borland給_cdecl函數(shù)前加了一一個(gè)下劃線,而
MSVC沒有。另一方面,MSVC認(rèn)為導(dǎo)出的_stdcall函數(shù)前面帶有下劃線,后面還有一些
垃圾。
表I:VisualC++andC++Builder命名習(xí)慣
調(diào)用習(xí)慣VC++命名VC++(使用了DEF)C++Builder命名
—stdcall_MyFunction@4MyFunctionMyFunction
—cdeclMyFunctionMyFunction_MyFunction
第2個(gè)問題是Borland引入庫(kù)與MSVC不是:進(jìn)制兼容的。當(dāng)你編譯DLL時(shí)、由BCB
創(chuàng)建的引入庫(kù)不能被MSVC用來(lái)連接。如果你想使用隱式連接,那么你需要?jiǎng)?chuàng)建一個(gè)MSVC
格式的引入庫(kù)。另一種可選擇的辦法就是采用顯式連接(LoadLibrary和GetProcAddress)。
第3個(gè)問題是不能從DLL里導(dǎo)出C++類和成員函數(shù),如果你想讓MSVC的用戶也可以調(diào)用
它。好吧,那不完全屬實(shí)。你的DLL能導(dǎo)出C++類,但是MSVC不能使用它們。原因就
是C++成員函數(shù)名被編譯器改編(mangled)。這個(gè)改編的名字結(jié)果了DLL。為了調(diào)用在DLL
里被改編的函數(shù),你必需知道被改編的是哪個(gè)函數(shù)。Borland和Microsoft使用了不同的名
字改編方案。結(jié)果是,MSVC不能恰好看到Borland編譯的DLL里的C++類和成員函數(shù)。
注意:
Borland和Microsoft沒有采用相同的方式改編函數(shù),因?yàn)橐勒誂NSIC++標(biāo)準(zhǔn),C++編譯器不被假定
追隨相同的指導(dǎo)方針。名字改編只是實(shí)現(xiàn)的細(xì)節(jié)。
這三個(gè)問題使得Borland創(chuàng)建的DLL可以在MSVC里被調(diào)用變得非常困難,但并非不
可能的。這篇文章描述了一套指導(dǎo)方針,你可以跟著制作與Microsoft兼容的BCBDLL。
我們討論四種不同的技術(shù)。三種采用引入庫(kù)隱式連接調(diào)用,一種在運(yùn)行時(shí)利用顯式連接。
指導(dǎo)方針摘要
你可以跟著下面的指導(dǎo)方針摘要列表建造你的DLL。第1個(gè)列表討論隱式連接;第2
個(gè)列表描述顯式連接;第3種技術(shù)采用#define組裝隱式連接;最后一個(gè)例子利用假的MSVC
DLL工程為_stdcall函數(shù)創(chuàng)建引入庫(kù)。
技術(shù)I:隱式連接
1-使用_cdecl調(diào)用習(xí)慣代替_stdcalL。
2-導(dǎo)出簡(jiǎn)單的"C"風(fēng)格函數(shù),沒有C++類或成員函數(shù)。
3-確定你有一個(gè)extern"C"{)包圍你的函數(shù)原型。
4-創(chuàng)建DEF文件,包含與Microsoft兼容的導(dǎo)出函數(shù)別名。別名也就是不包含前面的卜劃線。
DEF文件內(nèi)容如下:
EXPORTS
;MSVCnameBorlandname
Foo=_Foo
Bar=_Bar
5-把DEF文件加入到你的工程里重新編譯它。
6-把DLL和DLL頭文件拷貝到你的MSVC工程目錄里。
7-運(yùn)行impdef為DLL創(chuàng)建第2個(gè)DEF文件。這個(gè)DEF文件用來(lái)創(chuàng)建引入庫(kù)。
>impdefmydll.defmydll.dll
8-運(yùn)行Microsoft的L工B工具,用k一步創(chuàng)建的DEF文件創(chuàng)建COFF引入庫(kù)。調(diào)用格式為:
>lib/DEFmydll.def
9-把用LIB.EXE創(chuàng)建的LIB文件添加到你的MSVC工程里。
技術(shù)2;顯式連接
1-使用__cdecl或—stdcall,如果你使用_stdcall可以跳過第4,5步。
2-導(dǎo)出簡(jiǎn)單的"C"風(fēng)格函數(shù),沒有C++類或成員函數(shù)。
3-確定你有一個(gè)extern"C"{}包圍你的函數(shù)原型。
4-如果你使用_cdecl,那么你可能想去掉導(dǎo)出函數(shù)前面的下劃線,但你不必這么做。你可
以用例1的第4,5步去掉下劃線。如果你沒有去掉下載線,在調(diào)用GetProcAddress函數(shù)時(shí)
函數(shù)名必須前面的卜劃線。
5-把DLL拷貝到MSVC工程目錄里。
6-在MSVC應(yīng)用程序中,使用LoadLibraryAPI函數(shù)載入DLL。
7-調(diào)用GetProcAddressAPI在DLL里查找你想要的調(diào)用函數(shù),保存GelProcAddress函數(shù)返
回的函數(shù)指針。當(dāng)你想調(diào)用函數(shù)的時(shí)候,提取函數(shù)指針。
8-當(dāng)你用完DLL時(shí)調(diào)用FreeLibrary。
技術(shù)3:用#define組裝隱式連接
1-用_cdecl調(diào)用習(xí)慣代替_stdcallo
2-導(dǎo)出簡(jiǎn)單的“C”風(fēng)格函數(shù),沒有C++類或成員函數(shù)。
3-確定你有一個(gè)extern"C"{}包圍你的函數(shù)原型。
4-在你的DLL頭文件里,為每?個(gè)導(dǎo)出函數(shù)名創(chuàng)建?個(gè)#define。
#define會(huì)調(diào)用預(yù)編譯器在每一個(gè)函數(shù)名前加上下劃線。因?yàn)槲覀冎幌霝镸SVC創(chuàng)建別名,
所以代碼檢查_MSC_VER。
#ifdef_MSC_VER
#defineFoo_Foo
#defineBar_Bar
#endif
5-把DLL和DLL頭文件拷貝到MSVC工程目錄里。
6-運(yùn)行impdef為DLL函數(shù)DEF文件。
>impdefmydll.defmydll.dll
7-使用Microsoft的LIB工具為DEF文件創(chuàng)建COFF格式的引入庫(kù)。
>lib/defmydll.def
8-把LIB.EXE創(chuàng)建的LIB文件添加到MSVC工程里。
技術(shù)4:用_stdcall函數(shù)隱式連接
1-當(dāng)建造你的DLL時(shí)使用_stdcall調(diào)用習(xí)慣。
2-導(dǎo)出簡(jiǎn)單的“C”風(fēng)格函數(shù),沒有C++類或成員函數(shù)。
3-確定你有個(gè)extern"C”{}包圍你的函數(shù)原型。
4-為MSVC創(chuàng)建?個(gè)弓|入庫(kù)。這一部分比較困難。你不能用LIB.EXE為_stdcall函數(shù)創(chuàng)建
引入庫(kù)。你必須創(chuàng)建一個(gè)由MSVC編譯的的假的DLL。這樣做,按這些步驟:
4a-用MSVC創(chuàng)建一個(gè)不使用MFC的DLL
4b-從BCB里拷貝覆蓋DLL頭文件和DLL源代碼
4c-編輯你的DLL源代碼,拋開每一個(gè)例程的函數(shù)體部分,使用一個(gè)假的返回值返回
4d-配置MSVC工程生成的DLL,采用和BCBDLL同的的名字
4e-把DEF文件添加到MSVC工程,禁止它對(duì)_stdcall命名進(jìn)行修飾(_Foo@4)
5-編譯第4步得到的虛假DLL工程。這將會(huì)生成?個(gè)DLL(你可以把它丟到垃圾筒里)和一
個(gè)LIB文件(這是你需要的)。
6-把從第5步得到的LIB文件添加到你需要調(diào)用這個(gè)BCBDLL的MSVC工程里。LIB文
件會(huì)確保連接。為MSVC可執(zhí)行文件配置BCBDLL(不是虛假DLL)。
注意:
一般情況下,隱式連接比顯式連接要優(yōu)先考慮,因?yàn)閷?duì)程序員來(lái)說隱式連接更簡(jiǎn)單,而且它是
類型安全的(錯(cuò)誤發(fā)生在連接時(shí)而不是運(yùn)行時(shí))。不管用哪種方法,當(dāng)你在編譯器間共享DLL時(shí),如
果你選擇堅(jiān)持使用隱式連接,就必須為每一個(gè)編譯器創(chuàng)建兼容的引入庫(kù)。創(chuàng)建兼容的引入庫(kù)比用顯
式連增加的負(fù)擔(dān)就是要注意更多的要求。
注意:
如果你想使你的DLL可以被VisualBasic的開發(fā)者使用,顯式連接的指導(dǎo)方針同樣適用。如果
你想把你的DLL給VC開發(fā)者,按顯式連接的指導(dǎo)方針,采用_stdcall調(diào)用習(xí)慣。
下面4個(gè)部分詳細(xì)描述每一種技術(shù)。
例1:顯式連接
這個(gè)例子詳細(xì)描述了上一部分技術(shù)1的指導(dǎo)方針。技術(shù)1的指針方針可以分為兩組。1-5
項(xiàng)處理在BCB這邊編譯DLL;6-9項(xiàng)處理在MSVC這邊使用DLL。我們將沿這條主線分
別進(jìn)行討論。
在這個(gè)例子里,我們將用BCB建造一個(gè)DLL,它導(dǎo)出兩個(gè)函數(shù):Foo和Bar。兩個(gè)函數(shù)
都返回一個(gè)整型值。函數(shù)原型為:
intFoo(intValue);
intBar(void);
然后我們?cè)贛SVC里建造一個(gè)測(cè)試EXE,用來(lái)調(diào)用BorlandDLL。
用BCB編譯DLL
下面兩個(gè)程序清單包含我們的DLL源代碼。清單1要在BCB和MSVC之間共享的頭
文件;清單2包含我們的DLL函數(shù)實(shí)現(xiàn)部分。創(chuàng)建一個(gè)BCBDLL工程,從清單1和2中
拷貝代碼粘貼到工程里?;蛘吣憧梢韵螺d這篇文章的源代碼以節(jié)省時(shí)間。BCBDLL工程已
經(jīng)為你設(shè)置好了。(參見最下面的卜載部分)
//---------------------------------------------
//Listing1-DLLheaderfile
#ifndefBCBDLL_H
#defineBCBDLL_H
#ifdef_cplusplus
extern"Cn{
#endif
#ifdefBUILD_DLL
#defineIMPORT_EXPORT__declspec(dllexport)
#else
#defineIMPORT_EXPORTdeclspec(dllimport)
#endif
IMPORT_EXPORTint_cdeclFoo(intValue);
IMPORT_EXPORTint_cdeclBar(void);
#ifdef__cplusplus
#endif
#endif
//-----------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------
//Listing2-DLLsourcecode
#include<windows.h>
#pragmahdrstop
#defineBUILD_DLL
^include"bcbdll.h"
int_cdeclFoo(intValue)
returnValue+1;
int__cdeclBar(void)
staticintret=0;
returnret++;
關(guān)于頭文件有兩個(gè)要注意的地方。首先,觀察我們用extern"C"的方法確保函數(shù)名不
會(huì)被C++編譯器改編;其次,注意到在我們建造DLL時(shí),導(dǎo)出函數(shù)有一個(gè)特殊指示的前綴
_declspec(dllexport)(>當(dāng)我們從MSVC里使用DLL時(shí),函數(shù)前綴變?yōu)開declspec(dHimport)。
這個(gè)指示的改變是通過IMPORT_EXPORT宏定義實(shí)現(xiàn)的。
最后,注意我們顯式聲明了_cdecl為調(diào)用習(xí)慣。技術(shù)上,我們可以省略_cdecl關(guān)鍵字,因
為_cdecl已經(jīng)是默認(rèn)的。但是,我想不管怎樣把它列出來(lái)是?個(gè)好習(xí)慣。通過列出調(diào)用習(xí)
慣,你顯式的告訴人們你選擇了_cdecl作為?個(gè)前提。同樣,默認(rèn)的調(diào)用習(xí)慣在兩個(gè)編譯
器里可以通過編譯開關(guān)改變。你肯定不想這些編譯器開關(guān)影響到你DLL的可用性。
頭文件本身滿足了指導(dǎo)方針中的1-3項(xiàng)。我們需要做的下?件事情是處理第4項(xiàng):給導(dǎo)出
函數(shù)建立別名。
首先,按現(xiàn)在的情況建造DLL代碼。其次,運(yùn)行TDUMP工具檢查函數(shù)的函數(shù)名確實(shí)包含
前面的下劃線。
c:>tdump-m-eebcbdll.dll
TurboDumpVersion5.0.16.12Copyright(c)1988,2000InpriseCorporation
DisplayofFileBCBDLL.DLL
EXPORTord:0001=*_Bar'
EXPORTord:0002=,_Foo'
EXPORTord:0003=,___CPPdebugHook,
注意:
使用TDUMP時(shí)別忘了用-m開關(guān)。TDUMP嘗試反改編(unmangle)被修飾的名字,使他們更容易閱
讀。但是,當(dāng)你查看一個(gè)DLL的時(shí)候,明智的選擇是查看函數(shù)的原始格式。-m開關(guān)告訴TDUMP
顯示原始函數(shù)名。
像你看到的那樣,F(xiàn)oo和Bar都包含前端下劃線。至于_CPPdebugHook,你可以不理它,
它是幕后操縱的,當(dāng)它不存在好了。它對(duì)你沒什么意義,你也不能讓它走開,因此就不要
把它放在心上了。
為了用別名去掉下劃線,我們需要做三件事:首先創(chuàng)建DLL的DEF文件;然后調(diào)整DEF
文件,為Borland名字創(chuàng)建MSVC的別名;最后,把DEF文件添加到你的BCB工程里,
重建DLL。
要?jiǎng)?chuàng)建DEF文件,對(duì)DLL運(yùn)行Borland的IMPDEF工具。
C:>impdefbcbdllx.defbcbdll.dll
我選擇bcbdllx.def為文件名,因?yàn)樯院?在我們創(chuàng)建MSVC引入庫(kù)之前)我們將使用其它DEF
文件。我想避免兩者混淆。bcbdllx.def內(nèi)容如下:
LIBRARYBCBDLL.DLL
EXPORTS
Bar@1Bar
Foo@2FOO
___CPPdebugHook@3;___CPPdebugHook
注意到在Foo和Boo前端的下劃線。如果DLL把Foo和Bar導(dǎo)出為一Foo和一Bar,當(dāng)MSVC
用戶設(shè)法建造他們的工程的時(shí)候,將看到連接錯(cuò)誤。我們需要?jiǎng)內(nèi)ハ聞澗€。我們用在DEF
文件里給函數(shù)別名的方法實(shí)現(xiàn)。
DEF文件別名允許我們?yōu)檎鎸?shí)的函數(shù)導(dǎo)出擔(dān)當(dāng)代理或占位符的函數(shù)名。在DLL里的真實(shí)的
函數(shù)仍然是一Foo和一Bar。代理名將是Foo和Bar(注意沒有了下劃線)。當(dāng)我們給兩個(gè)函數(shù)
別名的時(shí)候,DLL將導(dǎo)出兩個(gè)將的符號(hào),它們歸諸于原來(lái)的函數(shù)。
完成別名,編輯DEF文件,改變成下面的樣子:
LIBRARYBCBDLL.DLL
EXPORTS
Bar=_Bar
Foo=_Foo
這個(gè)DEF文件創(chuàng)建兩個(gè)新的出口,F(xiàn)oo和Bar,它們分別擔(dān)當(dāng)_Foo和一Bar的點(diǎn)位符。把這
個(gè)DEF文件保存到你的硬盤上。一旦你完成了這些工作,便可以把DEF文件添加到你的
BCB工程里,使用Project-Add菜單項(xiàng)。添加后,BCB會(huì)在工程管理器(ProjectManager)的
樹狀結(jié)構(gòu)里顯示出DEF文件。
一旦你把DEF文件加入到工程里,做一次完全的重建。工程連接好之后,再次對(duì)DLL運(yùn)行
TDUMP,檢查從DLL里導(dǎo)出的帶下劃線函數(shù)。
>tdump-m-eebcbdll.dll
TurboDumpVersion5.0.16.12Copyright(c)1988,2000InpriseCorporation
DisplayofFileBCBDLL.DLL
EXPORTord:0004=,Bar,
EXPORTord:0005=,Foo,
EXPORTord:0002=-
EXPORTord:0001=,_Foo,
EXPORTord:0003=*___CPPdebugHook1
對(duì)TDUMP的輸出有兩點(diǎn)要注意的事情要注意。首先,觀察Foo和Bar到場(chǎng)了(沒有前端下
劃線)?,F(xiàn)在DLL導(dǎo)出函數(shù)名與MSVC的一致了。還注意到原來(lái)的函數(shù),一Foo和一Bar,還
在那兒。被修飾過的函數(shù)仍就從DLL里導(dǎo)出。使用DEF文件別名并不隱藏原來(lái)的函數(shù)。
你可能會(huì)想把這原來(lái)的兩個(gè)函數(shù)用什么辦法隱藏起來(lái)。但是,這么做將會(huì)危害到從BCB工
程里使用DLL的人們。記得BCB的連接器期望在那兒有個(gè)前端下劃線。如果你真的用
了什么方法從DLL里把一Fo。和一Bar隱藏了(以我的知識(shí)是不可能實(shí)現(xiàn)的),那么你的DLL
從BCB里調(diào)用將變得非常困難。
如果TDUMP的輸出沒有列出代理函數(shù)(不帶下劃線的函數(shù)),那么返回上一步,檢查你的
DEF文件。在你可以繼續(xù)之前,你需要得到別名的出現(xiàn)。如果DLL看起來(lái)0K了,那么該
是轉(zhuǎn)到MSVC這邊的時(shí)間了。
從MSVC里調(diào)用DLL
一旦你擁有了一個(gè)被反改編_cdecl函數(shù)出口的DLL模型,下一步就是要為MSVC用戶生
成?個(gè)引入庫(kù)。為這,你將需要?jiǎng)倓倓?chuàng)建的DLL,使用Borland的IMPDEF實(shí)用工具(再一
次),和來(lái)自MSVC的LIB.EXE工具。第步是創(chuàng)建DLL的DEF文件。為這,我建議你拷
貝DLL和DLL頭文件到你的MSVC工程目錄里,在那兒工作。
C:>impdefbcbdll.defbcbdll.dll
IMPDEF將創(chuàng)建一個(gè)DEF文件,內(nèi)容如下:
C:>impdefbcbdll.defbcbdll.dll
LIBRARYBCBDLL.DLL
EXPORTS
Bar@4;Bar
Foo05;Foo
_Bar@2;_Bar
_Foo@1;_Foo
___CPPdebugHook@3;___CPPdebugHook
打開DEF文件,改變它的內(nèi)容為:
LIBRARYBCBDLL.DLL
IMPORTS
Bar@4;Bar
Foo@5;Foo
注意到我們移除了包含下劃線的函數(shù),和調(diào)試鉤掛(debughook)函數(shù)。我們還把EXPORT改
成了IMPORTS,因?yàn)槲覀儸F(xiàn)在是在引入函數(shù),而不是導(dǎo)出它們(我懷疑它對(duì)MSVCLIB.EXE
來(lái)說會(huì)產(chǎn)生不同)。
下一步,我們用MicrosoftLIB.EXE,從DEF文件那兒創(chuàng)建一個(gè)COFF格式的庫(kù)。語(yǔ)法為:
lib/DEF:bcbdll.def/out:bcbdll_msvc.lib
注意:
MSVC命令行實(shí)用工具在默認(rèn)情況下不在你的配置的路徑里。你可能需要運(yùn)行一個(gè)MSVC帶的批處
理文件,使得LIB.EXE可以被直接調(diào)用。批處理文件叫做VCVARS32.BAT,它位于DevStudio安
裝路徑的\VC\BIN廣目錄下。
這里,所有艱苦的工作都做完了。現(xiàn)在你需要做就是把你的DLL,MSVCLIB文件,和DLL
文件件加入到你的MSVC客戶端。要使用DLL,需要添加LIB文件至MSVC工程里,并且
在源代碼內(nèi)#includeDLL頭文件。
我準(zhǔn)備了一個(gè)MSVC的簡(jiǎn)單工程來(lái)證明上面的概念。清單3給出客戶端DLL的源代碼。沒
什么特別的地方,就是一個(gè)main函數(shù),-個(gè)DLL頭文件的#include,和對(duì)DLL的幾個(gè)函
數(shù)調(diào)用。主要是你正確的添加了引入庫(kù),由LIB.EXE生成的那個(gè),添加到MSVC工程里。
//-------------------------------------------------
//Listing3-MSVCDLLclientcode
#include<iostream>
#include<windows.h>
usingnamespacestd;
#include"bcbdll.hn
intmain()
cout<<*'Foo(10)="<<Foo(10)?endl;
cout<<”Bar()="<<Bar()<<endl;
cout<<”Bar()="<<Bar()<<endl;
cout<<”Bar()=H<<Bar()<<endl;
return0;
)
//-------------------------------------------------
例2:顯式連接
這個(gè)例子向你展示了如何從MSVC里使用顯式連接調(diào)用BCB編譯的DLL。用顯式連接,你
不必?cái)[弄?jiǎng)?chuàng)建一個(gè)MSVC兼容的引入庫(kù)。顯示連接不利的是它需要在用戶端做更多的工
作,它不及隱式連接類型安全,錯(cuò)誤被延期到運(yùn)行時(shí)而不是連接時(shí)。雖然顯式連接有許多不
利因素,但在某些情況下它還是卜分有用的。
在這個(gè)例子里,我們將創(chuàng)建個(gè)DLL,它導(dǎo)出兩個(gè)函數(shù):Foo和Bar。函數(shù)的原型同上一個(gè)
例子一樣。
intFoo(intValue);
intBar(void);
這一顯式連接的指導(dǎo)方針與隱式連接的相仿。我們需要導(dǎo)出簡(jiǎn)單的C函數(shù),需要防止C++
名字改編。如果我們用_cdecl調(diào)用習(xí)慣,那么我們可能想要為BCB導(dǎo)出的函數(shù)建立別名,
以去掉它們前端的卜一劃線。如果我們選擇不用別名去掉卜劃線的方法,那么當(dāng)按名字載入函
數(shù)時(shí),我們必須包含下劃線。換句話說,當(dāng)你對(duì)_cdecl函數(shù)起作用時(shí),你必須在某幾點(diǎn)上
處理卜劃線。你也可以在BCB建造DLL的時(shí)候處理下劃線,或者在運(yùn)行時(shí)調(diào)用DLL時(shí)處
理它。我們利用_stdcall代替_cdecl以回避整個(gè)討論的下劃線問題。這是我們?cè)谶@個(gè)例子
里要做的。清單4和5給出的我們DLL的源代碼。
注意:
如果你導(dǎo)出_stdcall函數(shù),至關(guān)緊要的是要讓客戶端應(yīng)用程序知道。一些人容易犯一個(gè)錯(cuò)誤,認(rèn)為
使用_stdcall只不過是去掉了_cdecl函數(shù)前面的卜劃線。別掉進(jìn)這個(gè)陷井。_stdcall函數(shù)處理堆棧
方式也_cdecl是不同的。如果客戶端應(yīng)用程序把_stdcall當(dāng)作_cdecl函數(shù)調(diào)用(也就是,堆棧將被
破壞,客戶端程序會(huì)死得很難看),將要發(fā)生一些錯(cuò)誤。
//---------------------------
//Listing4-DLLheaderfile
#ifndefBCBDLL_H
#defineBCBDLL_H
#ifdef_cplusplus
extern"C"{
#endif
#ifdefBUILD_DLL
#defineIMPORT__EXPORT__declspec(dllexport)
#else
#defineIMPORT_EXPORTdeclspec(dllimport)
#endif
IMPORT_EXPORTint_stdcallFoo(intValue);
IMPORT_EXPORTint_stdcallBar(void);
#ifdef__cplusplus
#endif
#endif
//
//
//Listing5-DLLsourcecode
#include<windows.h>
#defineBU工LD_DLL
#includenbcbdll.h"
int__stdcallFoo(intValue)
returnValue+1;
int__stdcallBar(void)
staticintret=0;
returnret++;
//
注意這段代碼幾乎與隱式連接的一模?樣。唯?不同的地方就是把Foo和Bar的調(diào)用習(xí)慣改
成_stdcall代替_cdecL
現(xiàn)在讓我們看一下調(diào)用DLL的MSVC程序代碼。代碼如清單6所示。
//-------------------------------------------------
//Listing6-MSVCclientcode
#include<iostream>
#include<windows.h>
usingnamespacestd;
HINSTANCEhDll=0;
typedefint(stdcall*foo_type)(intValue);
typedefint(stdcall*bar_type)();
foo_typeFoo=0;
bar_typeBar=0;
voidDLLInit()
hDll=LoadLibrary(nbcbdll.dll,1);
Foo=(foo__type)GetProcAddress(hDll,nFoo");
Bar=(bar__type)GetProcAddress(hDllz"Bar”);
voidDLLFree()
FreeLibrary(hDll);
intmain()
DLLInit();
cout<<"Foo()="?Foo(10)?endl;
cout<<”Bar()="?Bar()<<endl;
cout<<11Bar()="?Bar()<<endl;
cout<<nBar()="<<Bar()<<endl;
DLLFree();
return0;
}
//-------------------------------------------------
這段代碼片段里有許多需要消化的地方。首先也是最重要的,觀察代碼本身是編譯器中立的。
你可以在BCB或MSVC里編譯它。我首先在BCB里編譯它,確信它可以按我所想的工作。
第二,注意到代碼沒有為#includebcbdll.h操心。有一個(gè)重要的原因。bcbdll.h為Foo和Bar
函數(shù)定義的原型。但是,我們不把我們的代碼同任何預(yù)先定義的那些原型連接。通常,這
些原型的存根來(lái)自引入庫(kù)。但是這個(gè)例子示范的是顯式連接,當(dāng)你顯示地連接時(shí),是不使用
引入庫(kù)的,在頭文件里的Foo和Bar原型對(duì)我們來(lái)說沒多大意義。
第三件要注意的事情是關(guān)于這段代碼里出現(xiàn)的typedef和函數(shù)指針,位于源文件的頂部附近。
晃式連接需要你在運(yùn)行時(shí)用APIGetProcAddrress得到DLL函數(shù)的地址。你必須把
GetProcAddress返回的結(jié)果存儲(chǔ)到某個(gè)地方。最好的地點(diǎn)是把結(jié)果存儲(chǔ)到函數(shù)指針里。通
過把函數(shù)地址存儲(chǔ)到函數(shù)指針里,你可以使用正常的函數(shù)調(diào)用語(yǔ)法調(diào)用函數(shù)(如Foo(lO)),
typedef聲明創(chuàng)建了兩個(gè)新的類型:foo_type和bar_type。它們都是函數(shù)指針類型。foo_type
聲明了一個(gè)指向_stdcall函數(shù)的類型,這個(gè)函數(shù)打官腔一個(gè)整型參數(shù),返回一個(gè)整型值。
bar_type定義了一個(gè)指向_stdcall類型的、沒有參數(shù)、有一個(gè)整型返回值的函數(shù)。這些typedef
產(chǎn)生了兩個(gè)效果。第一,它們提供了清晰的方式來(lái)聲明函數(shù)指針變量Foo和Bar。第二,
它們使我們可以很方便的轉(zhuǎn)換GetProcAddress返回的結(jié)果。從GetProcAddress返回的結(jié)果
是一個(gè)指向_stdcall類型的、沒有參數(shù)、有一個(gè)整型返回值的函數(shù)。除非你的函數(shù)與這個(gè)格
式相同,否則你需要轉(zhuǎn)換GetProcAddress的結(jié)果(這個(gè)轉(zhuǎn)換是顯式連接比隱式連接缺管類型
安全的原因)。
在typedef的下面有兩個(gè)變量Foo和Bar。這兩個(gè)是函數(shù)指針變量。它們會(huì)保存我們想要調(diào)
用的兩個(gè)函數(shù)的地址。注意這些變量的名字是任意的。我選擇Foo和Bar是為了使代碼像
隱式連接。不要犯這樣的錯(cuò)誤,F(xiàn)oo和Bar變量名沒有與DLL里的真實(shí)函數(shù)建立連接。我
們可以把變量命名為Guido和Bjarne,如果你想的話。
在函數(shù)指針聲明下面,你會(huì)看到兩個(gè)叫Dlllnit和DHFree的函數(shù)實(shí)體。這兩個(gè)實(shí)體處理載入
DLL,查找導(dǎo)出函數(shù),和在我們使用賽后釋放程序庫(kù)。用這種方法,其余的代碼不知道DLL
是顯式連接的。它可以像往常一樣調(diào)用Foo和Bar(或者Guido和Bjarne,如果你改變了名字)。
唯一要協(xié)調(diào)的是你必須在調(diào)用任何DLL程序之前調(diào)用Dlllnito我們也應(yīng)當(dāng)細(xì)致的,調(diào)用
DHFree釋放程序庫(kù)。
注意:
當(dāng)在命名總題上Borland編譯器和Microsoft編譯器之間大戰(zhàn)之時(shí),GetProcAddress是你的最后一道
防線。這包括Borland_cdecl命名帶一個(gè)前端下劃線(如_Foo)?也包括改編C++名字。如果有人支
持你用改編函數(shù)名字的DLL,你可以永遠(yuǎn)傳遞這些難看的參數(shù),把改編名字給GetProcAddress。不
管你實(shí)際上你能調(diào)用函數(shù)而沒碰到其它的什么問題,但是至少你將會(huì)有一個(gè)機(jī)會(huì)。
這就是全部。在MSVC里編譯代碼,你就完成了。你不必?cái)[弄DEF文件或是引入庫(kù)。但是
在你這邊的代碼里有些瑣碎的工作要處理。
例3:用#define組裝隱式連接
這個(gè)例子展示了可能是從MSVC工程里調(diào)用BCBDLL最簡(jiǎn)單的一種方法,但它也可能是最
沒有吸引力的一種方法。代碼使用一個(gè)狡詐的#define,當(dāng)檢查到是Microsoft編譯器時(shí)給
_cdecl函數(shù)前加上下劃線。也就是說,我們簡(jiǎn)單的#define了Foo為_氏0。
這種技術(shù)的優(yōu)勢(shì)在于我們不必實(shí)行任何別名。我們能直接導(dǎo)出包含下劃線的_cdecl函數(shù)。
但是,我們?nèi)跃捅仨氂肕icrosoft的L1B.EXE創(chuàng)建一個(gè)MSVC兼容的引入庫(kù)。
這種技術(shù)是關(guān)鍵是MSVC不期望_cdecl函數(shù)有任何的修飾(見表1)。它們應(yīng)當(dāng)和看起來(lái)」
樣。如果MSVC應(yīng)用程序試圖執(zhí)行個(gè)_cdecl函數(shù)Foo,它期望在DLL里查找??個(gè)沒有
下劃線的函數(shù)Foo。如果我們改變MSVC的代碼,讓它調(diào)用_Fo。,那么它將試圖在DLL
里查找一個(gè)叫做一Foo的函數(shù)。
Borland給_cdecl函數(shù)前加上了下劃線。我們可以哄騙MSVC,讓它在調(diào)用函數(shù)的時(shí)候在函
數(shù)名的前面加一個(gè)下劃線。緊記我們只想在MSVC這邊添加一個(gè)下劃線,而不是Borland
這邊。
#define組裝的DLL代碼與例1里清單2的代碼完全一樣。唯一不同的是DLL頭文件。當(dāng)
檢測(cè)到是MSVC時(shí),DLL頭文件為每一個(gè)函數(shù)原型加一個(gè)卜劃線。清單7展示了修改后的
頭文件。
//-----------------------------------------------------------------------------------
//Listing7-DLLheaderfile
#ifndefBCBDLL_H
#defineBCBDLL_H
#ifdef_cplusplus
externnCn{
#endif
#ifdefBUILD_DLL
#defineIMPORT__EXPORTdeclspec(dllexport)
#else
#define工MPORT_EXPORT__declspec(dllimport)
#endif
//#definekludge.IfwearebeingcompiledwithMSVC,thenjusttackona
//leadingunderscorebecauseBorlandC++willexportFooandBaras_Foo
//and_Barrespectively
_MSC_VER
#defineFoo_Foo
#defineBar_Bar
#endif
IMPORT_EXPORTint_cdeclFoo(intValue);
IMPORT_EXPORTint_cdeclBar(void);
#ifdef__cplusplus
#endif
#endif
〃-------------------------------------------------
在頭文件里,除#define組裝之外,你還必須創(chuàng)建一個(gè)MSVC兼容的引入庫(kù)。你可以按前面
的步驟完成。對(duì)編譯好的DLL運(yùn)行IMPDEF,得到一個(gè)DEF文件。然后運(yùn)行Microsoft
LIB.EXE工具創(chuàng)建一個(gè)COFF格式的引入庫(kù)。這時(shí),你不必考慮去編輯DEF文件。最后,
拷貝DLL,COFF引入庫(kù),和DLL文件件到你的MSVC工程里。把LIB文件添加到你的
MSVC工程里,重建。
這是創(chuàng)建MSVC引入庫(kù)的命令行例子。注意我們不必編輯DEF文件。我們剛好可以把它傳
遞給LIB.EXE。
//Createdeffile
>impdefbcbdll.defbcbdll.dll
//createCOFFimportlibraryusingMSlib.exe
>lib/defbcbdll.def
例4:用_stdcall函數(shù)隱式連接
在我們進(jìn)行之前,讓我們調(diào)查一下為什么我們需要單獨(dú)論述_stdcall函數(shù)。MSVC沒有提供
與Borland的IMPLIB相當(dāng)?shù)墓ぞ?。你不能攫取DLL,生成一個(gè)MSVC可用的引入庫(kù)。最
接近的工具是LIB.EXE,它可以通過一個(gè)DEF文件創(chuàng)建一個(gè)引入庫(kù)。DEF文件必須是手動(dòng)
創(chuàng)建,或利用Borland的IMPDEF工具生成的。
沒什么大不了的???你仍能創(chuàng)建MSVC引入庫(kù),只是必須通過中間步驟創(chuàng)建一個(gè)DEF文件,
然后把它傳遞給LIB.EXE工具。正確的,在你采用_cdecl函數(shù)的時(shí)候。當(dāng)你轉(zhuǎn)到_stdcall
的時(shí)候,問題就發(fā)生了。問題是Microsoft的LIB.EXE工具為導(dǎo)出_stdcall函數(shù)的DLL生
成引入庫(kù)顯得無(wú)能為力。
因?yàn)檫@個(gè)原因,我把用_stdcall隱式連接分離出來(lái)作為它自己的一部分。我們需要跟著一個(gè)
不同步驟的次序來(lái)創(chuàng)建Microsoft兼容的引入庫(kù)。(同樣注意到我把這部分放到最后的好理
由,至少這些步驟是冗長(zhǎng)乏味的)。
既然我們不能用LIB.EXE為用_stdcall的BCBDLL生成引入庫(kù),那我們需要提?種不同
的策%有?種生成引入庫(kù)的方法(可能是唯一的方法),依靠只要你建造?個(gè)DLL的,MSVC
就可以生成?個(gè)引入庫(kù)這一事實(shí)。如果你建造?個(gè)包含_stdcall函數(shù)的MSVCDLL,編譯
器和連接器會(huì)正確的分解導(dǎo)出的_stdcall函數(shù),生成引入庫(kù)。
那么你會(huì)問它會(huì)怎么幫助我們呢?畢竟,我們正在用BorlandC++編譯DLL。在MSVC里創(chuàng)
建一個(gè)DLL工程有什么好處?我們想讓EXE用MSVC編譯,但是DLL應(yīng)當(dāng)保持在BCB
這邊。這個(gè)問題的答案是我們?cè)贛SVC里編譯虛假DLL工程,唯一的目的是生成一個(gè)
_stdcall的引入庫(kù)。由MSVC創(chuàng)建的DLL可以被丟到垃圾筒里。我們不需要它。
這種技術(shù)是建立在虛假DLL工程的基礎(chǔ)之上的。我們?cè)贛SVC里創(chuàng)建一個(gè)虛假DLL工程,
就是得到生成Microsoft兼容的引入庫(kù)的好處。于是我們可以把這個(gè)引入庫(kù)和BCB生成的
DLL相結(jié)合,再提供給MSVC用戶,使得他們可以調(diào)用我們的帶有_stdcall函數(shù)的Borland
DLLo
這是這種技術(shù)所必須的幾步。首先,用BCB編譯你的DLL。用_stdcall調(diào)用習(xí)慣,導(dǎo)出簡(jiǎn)
單的C函數(shù),用extern"C”包裝所有的聲明。DLL的代碼與例2中清單4和5的代碼相同,
因此我不把它們?cè)倭谐鰜?lái)了。第二步是在MSVC里創(chuàng)建虛假DLL工程。編譯虛假DLL工
程,盜取生成的引入庫(kù)。最后一步是把這個(gè)引入庫(kù)添加到任一想要調(diào)用BorlandDLL的
MSVC工程里。
這?技術(shù)最有挑戰(zhàn)興趣的是圍繞虛假DLL工程和引入庫(kù)的基因。建造一個(gè)虛假DLL工程,
用MSVC創(chuàng)建一個(gè)non-MFCDLL工作區(qū)。編輯MSVC工程設(shè)置,以便使生成DLL的函數(shù)
與
溫馨提示
- 1. 本站所有資源如無(wú)特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 2024銷售外包的簡(jiǎn)單合同
- 2024版長(zhǎng)春房屋買賣合同文本
- 2025場(chǎng)地租賃合同標(biāo)準(zhǔn)范本(含環(huán)保條款)2篇
- 2025年度中央廚房承包合同范本(二零二五年度)4篇
- 2025年度磚廠生產(chǎn)線升級(jí)改造承包合同4篇
- 2025年度磚廠智能化生產(chǎn)系統(tǒng)承包合同4篇
- 2025年度智能溫室大棚使用權(quán)轉(zhuǎn)讓購(gòu)買合同范本
- 2025年度物業(yè)管理與社區(qū)養(yǎng)老服務(wù)平臺(tái)合同4篇
- 2024年項(xiàng)目委托建設(shè)協(xié)議3篇
- 2025年度醫(yī)療器械注冊(cè)代理與風(fēng)險(xiǎn)控制合同3篇
- 城市軌道交通的網(wǎng)絡(luò)安全與數(shù)據(jù)保護(hù)
- 英國(guó)足球文化課件
- 《行政職業(yè)能力測(cè)驗(yàn)》2023年公務(wù)員考試新疆維吾爾新疆生產(chǎn)建設(shè)兵團(tuán)可克達(dá)拉市預(yù)測(cè)試題含解析
- 醫(yī)院投訴案例分析及處理要點(diǎn)
- 燙傷的安全知識(shí)講座
- 工程變更、工程量簽證、結(jié)算以及零星項(xiàng)目預(yù)算程序?qū)嵤┘?xì)則(試行)
- 練習(xí)20連加連減
- 五四制青島版數(shù)學(xué)五年級(jí)上冊(cè)期末測(cè)試題及答案(共3套)
- 員工內(nèi)部崗位調(diào)換申請(qǐng)表
- 商法題庫(kù)(含答案)
- 鋼結(jié)構(gòu)用高強(qiáng)度大六角頭螺栓連接副 編制說明
評(píng)論
0/150
提交評(píng)論