




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)
文檔簡介
在C++Builder里創(chuàng)建可以被VisualC++使用的DLL
這篇文章舉例說明如何用C++Builder創(chuàng)建一個DLL,使它可以在VisualC++工程里調(diào)用。
?簡介:為什么這個這么難
?指導(dǎo)方針摘要
?例1:隱式連接
?例2:顯式連接
?例3:用#define組裝隱式連接
?例4:用stdcall函數(shù)隱式連接
?結(jié)論
簡介:為什么這個這么難
如果你用BCB創(chuàng)建了一個DLL,它可以被BCB的可執(zhí)行文件調(diào)用,你知道這種使用
DLL的方式?jīng)]什么難度。當(dāng)你構(gòu)造一個DLL,BCB生成一個帶".LIB”擴展名的引入庫。
把這個L1B文件添加到你的工程里。連接器按引入庫決定DLL內(nèi)部調(diào)用。當(dāng)你運行你的程
序時,DLL隱式的被載入,你不必去考慮DLL內(nèi)部調(diào)用工作是。
當(dāng)EXE文件是由MicrosoftVisualC++編譯的時候,情況會變得比較復(fù)雜。有3個主要
的問題。首先,BCB和MSVC對DLL中的函數(shù)命名方式是不一致的。BCB使用一種習(xí)慣,
MSVC使用另一種不同的習(xí)慣。當(dāng)然,兩種習(xí)慣是不兼容的。命名問題在如何在C++Builder
工程里使用VC++編譯的DLL那篇文章里已經(jīng)討論過了。表1總結(jié)了各個編譯器在各自的
調(diào)用習(xí)慣下,導(dǎo)出的MyFunction函數(shù)。注意Borland給_cdecl函數(shù)前加了一一個下劃線,而
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個問題是Borland引入庫與MSVC不是:進制兼容的。當(dāng)你編譯DLL時、由BCB
創(chuàng)建的引入庫不能被MSVC用來連接。如果你想使用隱式連接,那么你需要創(chuàng)建一個MSVC
格式的引入庫。另一種可選擇的辦法就是采用顯式連接(LoadLibrary和GetProcAddress)。
第3個問題是不能從DLL里導(dǎo)出C++類和成員函數(shù),如果你想讓MSVC的用戶也可以調(diào)用
它。好吧,那不完全屬實。你的DLL能導(dǎo)出C++類,但是MSVC不能使用它們。原因就
是C++成員函數(shù)名被編譯器改編(mangled)。這個改編的名字結(jié)果了DLL。為了調(diào)用在DLL
里被改編的函數(shù),你必需知道被改編的是哪個函數(shù)。Borland和Microsoft使用了不同的名
字改編方案。結(jié)果是,MSVC不能恰好看到Borland編譯的DLL里的C++類和成員函數(shù)。
注意:
Borland和Microsoft沒有采用相同的方式改編函數(shù),因為依照ANSIC++標(biāo)準(zhǔn),C++編譯器不被假定
追隨相同的指導(dǎo)方針。名字改編只是實現(xiàn)的細(xì)節(jié)。
這三個問題使得Borland創(chuàng)建的DLL可以在MSVC里被調(diào)用變得非常困難,但并非不
可能的。這篇文章描述了一套指導(dǎo)方針,你可以跟著制作與Microsoft兼容的BCBDLL。
我們討論四種不同的技術(shù)。三種采用引入庫隱式連接調(diào)用,一種在運行時利用顯式連接。
指導(dǎo)方針摘要
你可以跟著下面的指導(dǎo)方針摘要列表建造你的DLL。第1個列表討論隱式連接;第2
個列表描述顯式連接;第3種技術(shù)采用#define組裝隱式連接;最后一個例子利用假的MSVC
DLL工程為_stdcall函數(shù)創(chuàng)建引入庫。
技術(shù)I:隱式連接
1-使用_cdecl調(diào)用習(xí)慣代替_stdcalL。
2-導(dǎo)出簡單的"C"風(fēng)格函數(shù),沒有C++類或成員函數(shù)。
3-確定你有一個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-運行impdef為DLL創(chuàng)建第2個DEF文件。這個DEF文件用來創(chuàng)建引入庫。
>impdefmydll.defmydll.dll
8-運行Microsoft的L工B工具,用k一步創(chuàng)建的DEF文件創(chuàng)建COFF引入庫。調(diào)用格式為:
>lib/DEFmydll.def
9-把用LIB.EXE創(chuàng)建的LIB文件添加到你的MSVC工程里。
技術(shù)2;顯式連接
1-使用__cdecl或—stdcall,如果你使用_stdcall可以跳過第4,5步。
2-導(dǎo)出簡單的"C"風(fēng)格函數(shù),沒有C++類或成員函數(shù)。
3-確定你有一個extern"C"{}包圍你的函數(shù)原型。
4-如果你使用_cdecl,那么你可能想去掉導(dǎo)出函數(shù)前面的下劃線,但你不必這么做。你可
以用例1的第4,5步去掉下劃線。如果你沒有去掉下載線,在調(diào)用GetProcAddress函數(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ù)指針。
8-當(dāng)你用完DLL時調(diào)用FreeLibrary。
技術(shù)3:用#define組裝隱式連接
1-用_cdecl調(diào)用習(xí)慣代替_stdcallo
2-導(dǎo)出簡單的“C”風(fēng)格函數(shù),沒有C++類或成員函數(shù)。
3-確定你有一個extern"C"{}包圍你的函數(shù)原型。
4-在你的DLL頭文件里,為每?個導(dǎo)出函數(shù)名創(chuàng)建?個#define。
#define會調(diào)用預(yù)編譯器在每一個函數(shù)名前加上下劃線。因為我們只想為MSVC創(chuàng)建別名,
所以代碼檢查_MSC_VER。
#ifdef_MSC_VER
#defineFoo_Foo
#defineBar_Bar
#endif
5-把DLL和DLL頭文件拷貝到MSVC工程目錄里。
6-運行impdef為DLL函數(shù)DEF文件。
>impdefmydll.defmydll.dll
7-使用Microsoft的LIB工具為DEF文件創(chuàng)建COFF格式的引入庫。
>lib/defmydll.def
8-把LIB.EXE創(chuàng)建的LIB文件添加到MSVC工程里。
技術(shù)4:用_stdcall函數(shù)隱式連接
1-當(dāng)建造你的DLL時使用_stdcall調(diào)用習(xí)慣。
2-導(dǎo)出簡單的“C”風(fēng)格函數(shù),沒有C++類或成員函數(shù)。
3-確定你有個extern"C”{}包圍你的函數(shù)原型。
4-為MSVC創(chuàng)建?個弓|入庫。這一部分比較困難。你不能用LIB.EXE為_stdcall函數(shù)創(chuàng)建
引入庫。你必須創(chuàng)建一個由MSVC編譯的的假的DLL。這樣做,按這些步驟:
4a-用MSVC創(chuàng)建一個不使用MFC的DLL
4b-從BCB里拷貝覆蓋DLL頭文件和DLL源代碼
4c-編輯你的DLL源代碼,拋開每一個例程的函數(shù)體部分,使用一個假的返回值返回
4d-配置MSVC工程生成的DLL,采用和BCBDLL同的的名字
4e-把DEF文件添加到MSVC工程,禁止它對_stdcall命名進行修飾(_Foo@4)
5-編譯第4步得到的虛假DLL工程。這將會生成?個DLL(你可以把它丟到垃圾筒里)和一
個LIB文件(這是你需要的)。
6-把從第5步得到的LIB文件添加到你需要調(diào)用這個BCBDLL的MSVC工程里。LIB文
件會確保連接。為MSVC可執(zhí)行文件配置BCBDLL(不是虛假DLL)。
注意:
一般情況下,隱式連接比顯式連接要優(yōu)先考慮,因為對程序員來說隱式連接更簡單,而且它是
類型安全的(錯誤發(fā)生在連接時而不是運行時)。不管用哪種方法,當(dāng)你在編譯器間共享DLL時,如
果你選擇堅持使用隱式連接,就必須為每一個編譯器創(chuàng)建兼容的引入庫。創(chuàng)建兼容的引入庫比用顯
式連增加的負(fù)擔(dān)就是要注意更多的要求。
注意:
如果你想使你的DLL可以被VisualBasic的開發(fā)者使用,顯式連接的指導(dǎo)方針同樣適用。如果
你想把你的DLL給VC開發(fā)者,按顯式連接的指導(dǎo)方針,采用_stdcall調(diào)用習(xí)慣。
下面4個部分詳細(xì)描述每一種技術(shù)。
例1:顯式連接
這個例子詳細(xì)描述了上一部分技術(shù)1的指導(dǎo)方針。技術(shù)1的指針方針可以分為兩組。1-5
項處理在BCB這邊編譯DLL;6-9項處理在MSVC這邊使用DLL。我們將沿這條主線分
別進行討論。
在這個例子里,我們將用BCB建造一個DLL,它導(dǎo)出兩個函數(shù):Foo和Bar。兩個函數(shù)
都返回一個整型值。函數(shù)原型為:
intFoo(intValue);
intBar(void);
然后我們在MSVC里建造一個測試EXE,用來調(diào)用BorlandDLL。
用BCB編譯DLL
下面兩個程序清單包含我們的DLL源代碼。清單1要在BCB和MSVC之間共享的頭
文件;清單2包含我們的DLL函數(shù)實現(xiàn)部分。創(chuàng)建一個BCBDLL工程,從清單1和2中
拷貝代碼粘貼到工程里。或者你可以下載這篇文章的源代碼以節(jié)省時間。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)于頭文件有兩個要注意的地方。首先,觀察我們用extern"C"的方法確保函數(shù)名不
會被C++編譯器改編;其次,注意到在我們建造DLL時,導(dǎo)出函數(shù)有一個特殊指示的前綴
_declspec(dllexport)(>當(dāng)我們從MSVC里使用DLL時,函數(shù)前綴變?yōu)開declspec(dHimport)。
這個指示的改變是通過IMPORT_EXPORT宏定義實現(xiàn)的。
最后,注意我們顯式聲明了_cdecl為調(diào)用習(xí)慣。技術(shù)上,我們可以省略_cdecl關(guān)鍵字,因
為_cdecl已經(jīng)是默認(rèn)的。但是,我想不管怎樣把它列出來是?個好習(xí)慣。通過列出調(diào)用習(xí)
慣,你顯式的告訴人們你選擇了_cdecl作為?個前提。同樣,默認(rèn)的調(diào)用習(xí)慣在兩個編譯
器里可以通過編譯開關(guān)改變。你肯定不想這些編譯器開關(guān)影響到你DLL的可用性。
頭文件本身滿足了指導(dǎo)方針中的1-3項。我們需要做的下?件事情是處理第4項:給導(dǎo)出
函數(shù)建立別名。
首先,按現(xiàn)在的情況建造DLL代碼。其次,運行TDUMP工具檢查函數(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時別忘了用-m開關(guān)。TDUMP嘗試反改編(unmangle)被修飾的名字,使他們更容易閱
讀。但是,當(dāng)你查看一個DLL的時候,明智的選擇是查看函數(shù)的原始格式。-m開關(guān)告訴TDUMP
顯示原始函數(shù)名。
像你看到的那樣,F(xiàn)oo和Bar都包含前端下劃線。至于_CPPdebugHook,你可以不理它,
它是幕后操縱的,當(dāng)它不存在好了。它對你沒什么意義,你也不能讓它走開,因此就不要
把它放在心上了。
為了用別名去掉下劃線,我們需要做三件事:首先創(chuàng)建DLL的DEF文件;然后調(diào)整DEF
文件,為Borland名字創(chuàng)建MSVC的別名;最后,把DEF文件添加到你的BCB工程里,
重建DLL。
要創(chuàng)建DEF文件,對DLL運行Borland的IMPDEF工具。
C:>impdefbcbdllx.defbcbdll.dll
我選擇bcbdllx.def為文件名,因為稍后(在我們創(chuàng)建MSVC引入庫之前)我們將使用其它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è)法建造他們的工程的時候,將看到連接錯誤。我們需要剝?nèi)ハ聞澗€。我們用在DEF
文件里給函數(shù)別名的方法實現(xiàn)。
DEF文件別名允許我們?yōu)檎鎸嵉暮瘮?shù)導(dǎo)出擔(dān)當(dāng)代理或占位符的函數(shù)名。在DLL里的真實的
函數(shù)仍然是一Foo和一Bar。代理名將是Foo和Bar(注意沒有了下劃線)。當(dāng)我們給兩個函數(shù)
別名的時候,DLL將導(dǎo)出兩個將的符號,它們歸諸于原來的函數(shù)。
完成別名,編輯DEF文件,改變成下面的樣子:
LIBRARYBCBDLL.DLL
EXPORTS
Bar=_Bar
Foo=_Foo
這個DEF文件創(chuàng)建兩個新的出口,F(xiàn)oo和Bar,它們分別擔(dān)當(dāng)_Foo和一Bar的點位符。把這
個DEF文件保存到你的硬盤上。一旦你完成了這些工作,便可以把DEF文件添加到你的
BCB工程里,使用Project-Add菜單項。添加后,BCB會在工程管理器(ProjectManager)的
樹狀結(jié)構(gòu)里顯示出DEF文件。
一旦你把DEF文件加入到工程里,做一次完全的重建。工程連接好之后,再次對DLL運行
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
對TDUMP的輸出有兩點要注意的事情要注意。首先,觀察Foo和Bar到場了(沒有前端下
劃線)。現(xiàn)在DLL導(dǎo)出函數(shù)名與MSVC的一致了。還注意到原來的函數(shù),一Foo和一Bar,還
在那兒。被修飾過的函數(shù)仍就從DLL里導(dǎo)出。使用DEF文件別名并不隱藏原來的函數(shù)。
你可能會想把這原來的兩個函數(shù)用什么辦法隱藏起來。但是,這么做將會危害到從BCB工
程里使用DLL的人們。記得BCB的連接器期望在那兒有個前端下劃線。如果你真的用
了什么方法從DLL里把一Fo。和一Bar隱藏了(以我的知識是不可能實現(xiàn)的),那么你的DLL
從BCB里調(diào)用將變得非常困難。
如果TDUMP的輸出沒有列出代理函數(shù)(不帶下劃線的函數(shù)),那么返回上一步,檢查你的
DEF文件。在你可以繼續(xù)之前,你需要得到別名的出現(xiàn)。如果DLL看起來0K了,那么該
是轉(zhuǎn)到MSVC這邊的時間了。
從MSVC里調(diào)用DLL
一旦你擁有了一個被反改編_cdecl函數(shù)出口的DLL模型,下一步就是要為MSVC用戶生
成?個引入庫。為這,你將需要剛剛創(chuàng)建的DLL,使用Borland的IMPDEF實用工具(再一
次),和來自MSVC的LIB.EXE工具。第步是創(chuàng)建DLL的DEF文件。為這,我建議你拷
貝DLL和DLL頭文件到你的MSVC工程目錄里,在那兒工作。
C:>impdefbcbdll.defbcbdll.dll
IMPDEF將創(chuàng)建一個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,因為我們現(xiàn)在是在引入函數(shù),而不是導(dǎo)出它們(我懷疑它對MSVCLIB.EXE
來說會產(chǎn)生不同)。
下一步,我們用MicrosoftLIB.EXE,從DEF文件那兒創(chuàng)建一個COFF格式的庫。語法為:
lib/DEF:bcbdll.def/out:bcbdll_msvc.lib
注意:
MSVC命令行實用工具在默認(rèn)情況下不在你的配置的路徑里。你可能需要運行一個MSVC帶的批處
理文件,使得LIB.EXE可以被直接調(diào)用。批處理文件叫做VCVARS32.BAT,它位于DevStudio安
裝路徑的\VC\BIN廣目錄下。
這里,所有艱苦的工作都做完了。現(xiàn)在你需要做就是把你的DLL,MSVCLIB文件,和DLL
文件件加入到你的MSVC客戶端。要使用DLL,需要添加LIB文件至MSVC工程里,并且
在源代碼內(nèi)#includeDLL頭文件。
我準(zhǔn)備了一個MSVC的簡單工程來證明上面的概念。清單3給出客戶端DLL的源代碼。沒
什么特別的地方,就是一個main函數(shù),-個DLL頭文件的#include,和對DLL的幾個函
數(shù)調(diào)用。主要是你正確的添加了引入庫,由LIB.EXE生成的那個,添加到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:顯式連接
這個例子向你展示了如何從MSVC里使用顯式連接調(diào)用BCB編譯的DLL。用顯式連接,你
不必擺弄創(chuàng)建一個MSVC兼容的引入庫。顯示連接不利的是它需要在用戶端做更多的工
作,它不及隱式連接類型安全,錯誤被延期到運行時而不是連接時。雖然顯式連接有許多不
利因素,但在某些情況下它還是卜分有用的。
在這個例子里,我們將創(chuàng)建個DLL,它導(dǎo)出兩個函數(shù):Foo和Bar。函數(shù)的原型同上一個
例子一樣。
intFoo(intValue);
intBar(void);
這一顯式連接的指導(dǎo)方針與隱式連接的相仿。我們需要導(dǎo)出簡單的C函數(shù),需要防止C++
名字改編。如果我們用_cdecl調(diào)用習(xí)慣,那么我們可能想要為BCB導(dǎo)出的函數(shù)建立別名,
以去掉它們前端的卜一劃線。如果我們選擇不用別名去掉卜劃線的方法,那么當(dāng)按名字載入函
數(shù)時,我們必須包含下劃線。換句話說,當(dāng)你對_cdecl函數(shù)起作用時,你必須在某幾點上
處理卜劃線。你也可以在BCB建造DLL的時候處理下劃線,或者在運行時調(diào)用DLL時處
理它。我們利用_stdcall代替_cdecl以回避整個討論的下劃線問題。這是我們在這個例子
里要做的。清單4和5給出的我們DLL的源代碼。
注意:
如果你導(dǎo)出_stdcall函數(shù),至關(guān)緊要的是要讓客戶端應(yīng)用程序知道。一些人容易犯一個錯誤,認(rèn)為
使用_stdcall只不過是去掉了_cdecl函數(shù)前面的卜劃線。別掉進這個陷井。_stdcall函數(shù)處理堆棧
方式也_cdecl是不同的。如果客戶端應(yīng)用程序把_stdcall當(dāng)作_cdecl函數(shù)調(diào)用(也就是,堆棧將被
破壞,客戶端程序會死得很難看),將要發(fā)生一些錯誤。
//---------------------------
//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操心。有一個重要的原因。bcbdll.h為Foo和Bar
函數(shù)定義的原型。但是,我們不把我們的代碼同任何預(yù)先定義的那些原型連接。通常,這
些原型的存根來自引入庫。但是這個例子示范的是顯式連接,當(dāng)你顯示地連接時,是不使用
引入庫的,在頭文件里的Foo和Bar原型對我們來說沒多大意義。
第三件要注意的事情是關(guān)于這段代碼里出現(xiàn)的typedef和函數(shù)指針,位于源文件的頂部附近。
晃式連接需要你在運行時用APIGetProcAddrress得到DLL函數(shù)的地址。你必須把
GetProcAddress返回的結(jié)果存儲到某個地方。最好的地點是把結(jié)果存儲到函數(shù)指針里。通
過把函數(shù)地址存儲到函數(shù)指針里,你可以使用正常的函數(shù)調(diào)用語法調(diào)用函數(shù)(如Foo(lO)),
typedef聲明創(chuàng)建了兩個新的類型:foo_type和bar_type。它們都是函數(shù)指針類型。foo_type
聲明了一個指向_stdcall函數(shù)的類型,這個函數(shù)打官腔一個整型參數(shù),返回一個整型值。
bar_type定義了一個指向_stdcall類型的、沒有參數(shù)、有一個整型返回值的函數(shù)。這些typedef
產(chǎn)生了兩個效果。第一,它們提供了清晰的方式來聲明函數(shù)指針變量Foo和Bar。第二,
它們使我們可以很方便的轉(zhuǎn)換GetProcAddress返回的結(jié)果。從GetProcAddress返回的結(jié)果
是一個指向_stdcall類型的、沒有參數(shù)、有一個整型返回值的函數(shù)。除非你的函數(shù)與這個格
式相同,否則你需要轉(zhuǎn)換GetProcAddress的結(jié)果(這個轉(zhuǎn)換是顯式連接比隱式連接缺管類型
安全的原因)。
在typedef的下面有兩個變量Foo和Bar。這兩個是函數(shù)指針變量。它們會保存我們想要調(diào)
用的兩個函數(shù)的地址。注意這些變量的名字是任意的。我選擇Foo和Bar是為了使代碼像
隱式連接。不要犯這樣的錯誤,F(xiàn)oo和Bar變量名沒有與DLL里的真實函數(shù)建立連接。我
們可以把變量命名為Guido和Bjarne,如果你想的話。
在函數(shù)指針聲明下面,你會看到兩個叫Dlllnit和DHFree的函數(shù)實體。這兩個實體處理載入
DLL,查找導(dǎo)出函數(shù),和在我們使用賽后釋放程序庫。用這種方法,其余的代碼不知道DLL
是顯式連接的。它可以像往常一樣調(diào)用Foo和Bar(或者Guido和Bjarne,如果你改變了名字)。
唯一要協(xié)調(diào)的是你必須在調(diào)用任何DLL程序之前調(diào)用Dlllnito我們也應(yīng)當(dāng)細(xì)致的,調(diào)用
DHFree釋放程序庫。
注意:
當(dāng)在命名總題上Borland編譯器和Microsoft編譯器之間大戰(zhàn)之時,GetProcAddress是你的最后一道
防線。這包括Borland_cdecl命名帶一個前端下劃線(如_Foo)?也包括改編C++名字。如果有人支
持你用改編函數(shù)名字的DLL,你可以永遠(yuǎn)傳遞這些難看的參數(shù),把改編名字給GetProcAddress。不
管你實際上你能調(diào)用函數(shù)而沒碰到其它的什么問題,但是至少你將會有一個機會。
這就是全部。在MSVC里編譯代碼,你就完成了。你不必擺弄DEF文件或是引入庫。但是
在你這邊的代碼里有些瑣碎的工作要處理。
例3:用#define組裝隱式連接
這個例子展示了可能是從MSVC工程里調(diào)用BCBDLL最簡單的一種方法,但它也可能是最
沒有吸引力的一種方法。代碼使用一個狡詐的#define,當(dāng)檢查到是Microsoft編譯器時給
_cdecl函數(shù)前加上下劃線。也就是說,我們簡單的#define了Foo為_氏0。
這種技術(shù)的優(yōu)勢在于我們不必實行任何別名。我們能直接導(dǎo)出包含下劃線的_cdecl函數(shù)。
但是,我們?nèi)跃捅仨氂肕icrosoft的L1B.EXE創(chuàng)建一個MSVC兼容的引入庫。
這種技術(shù)是關(guān)鍵是MSVC不期望_cdecl函數(shù)有任何的修飾(見表1)。它們應(yīng)當(dāng)和看起來」
樣。如果MSVC應(yīng)用程序試圖執(zhí)行個_cdecl函數(shù)Foo,它期望在DLL里查找??個沒有
下劃線的函數(shù)Foo。如果我們改變MSVC的代碼,讓它調(diào)用_Fo。,那么它將試圖在DLL
里查找一個叫做一Foo的函數(shù)。
Borland給_cdecl函數(shù)前加上了下劃線。我們可以哄騙MSVC,讓它在調(diào)用函數(shù)的時候在函
數(shù)名的前面加一個下劃線。緊記我們只想在MSVC這邊添加一個下劃線,而不是Borland
這邊。
#define組裝的DLL代碼與例1里清單2的代碼完全一樣。唯一不同的是DLL頭文件。當(dāng)
檢測到是MSVC時,DLL頭文件為每一個函數(shù)原型加一個卜劃線。清單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)建一個MSVC兼容的引入庫。你可以按前面
的步驟完成。對編譯好的DLL運行IMPDEF,得到一個DEF文件。然后運行Microsoft
LIB.EXE工具創(chuàng)建一個COFF格式的引入庫。這時,你不必考慮去編輯DEF文件。最后,
拷貝DLL,COFF引入庫,和DLL文件件到你的MSVC工程里。把LIB文件添加到你的
MSVC工程里,重建。
這是創(chuàng)建MSVC引入庫的命令行例子。注意我們不必編輯DEF文件。我們剛好可以把它傳
遞給LIB.EXE。
//Createdeffile
>impdefbcbdll.defbcbdll.dll
//createCOFFimportlibraryusingMSlib.exe
>lib/defbcbdll.def
例4:用_stdcall函數(shù)隱式連接
在我們進行之前,讓我們調(diào)查一下為什么我們需要單獨論述_stdcall函數(shù)。MSVC沒有提供
與Borland的IMPLIB相當(dāng)?shù)墓ぞ摺D悴荒芫鹑LL,生成一個MSVC可用的引入庫。最
接近的工具是LIB.EXE,它可以通過一個DEF文件創(chuàng)建一個引入庫。DEF文件必須是手動
創(chuàng)建,或利用Borland的IMPDEF工具生成的。
沒什么大不了的啊?你仍能創(chuàng)建MSVC引入庫,只是必須通過中間步驟創(chuàng)建一個DEF文件,
然后把它傳遞給LIB.EXE工具。正確的,在你采用_cdecl函數(shù)的時候。當(dāng)你轉(zhuǎn)到_stdcall
的時候,問題就發(fā)生了。問題是Microsoft的LIB.EXE工具為導(dǎo)出_stdcall函數(shù)的DLL生
成引入庫顯得無能為力。
因為這個原因,我把用_stdcall隱式連接分離出來作為它自己的一部分。我們需要跟著一個
不同步驟的次序來創(chuàng)建Microsoft兼容的引入庫。(同樣注意到我把這部分放到最后的好理
由,至少這些步驟是冗長乏味的)。
既然我們不能用LIB.EXE為用_stdcall的BCBDLL生成引入庫,那我們需要提?種不同
的策%有?種生成引入庫的方法(可能是唯一的方法),依靠只要你建造?個DLL的,MSVC
就可以生成?個引入庫這一事實。如果你建造?個包含_stdcall函數(shù)的MSVCDLL,編譯
器和連接器會正確的分解導(dǎo)出的_stdcall函數(shù),生成引入庫。
那么你會問它會怎么幫助我們呢?畢竟,我們正在用BorlandC++編譯DLL。在MSVC里創(chuàng)
建一個DLL工程有什么好處?我們想讓EXE用MSVC編譯,但是DLL應(yīng)當(dāng)保持在BCB
這邊。這個問題的答案是我們在MSVC里編譯虛假DLL工程,唯一的目的是生成一個
_stdcall的引入庫。由MSVC創(chuàng)建的DLL可以被丟到垃圾筒里。我們不需要它。
這種技術(shù)是建立在虛假DLL工程的基礎(chǔ)之上的。我們在MSVC里創(chuàng)建一個虛假DLL工程,
就是得到生成Microsoft兼容的引入庫的好處。于是我們可以把這個引入庫和BCB生成的
DLL相結(jié)合,再提供給MSVC用戶,使得他們可以調(diào)用我們的帶有_stdcall函數(shù)的Borland
DLLo
這是這種技術(shù)所必須的幾步。首先,用BCB編譯你的DLL。用_stdcall調(diào)用習(xí)慣,導(dǎo)出簡
單的C函數(shù),用extern"C”包裝所有的聲明。DLL的代碼與例2中清單4和5的代碼相同,
因此我不把它們再列出來了。第二步是在MSVC里創(chuàng)建虛假DLL工程。編譯虛假DLL工
程,盜取生成的引入庫。最后一步是把這個引入庫添加到任一想要調(diào)用BorlandDLL的
MSVC工程里。
這?技術(shù)最有挑戰(zhàn)興趣的是圍繞虛假DLL工程和引入庫的基因。建造一個虛假DLL工程,
用MSVC創(chuàng)建一個non-MFCDLL工作區(qū)。編輯MSVC工程設(shè)置,以便使生成DLL的函數(shù)
與
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 福建事業(yè)單位考試職場心理素質(zhì)試題及答案
- 財務(wù)成本管理考察重點試題及答案
- 2024年項目管理考試復(fù)習(xí)思路試題及答案
- 項目管理變更控制探討試題及答案
- 項目概念確認(rèn)的試題及答案
- 微生物抗生素替代物研究試題及答案
- 項目干系人分析中的定性與定量方法試題及答案
- 項目管理信息系統(tǒng)試題及答案
- 迪慶防風(fēng)卷簾施工方案
- 金屬非金屬地下礦山安全生產(chǎn)標(biāo)準(zhǔn)化定級評分標(biāo)準(zhǔn)(2023版)
- 鼻腸管的護理查房
- 化工技術(shù)經(jīng)濟(第五版)課件-第7章-項目可行性研究與決策-
- 安全掃描漏洞管理方案
- 員工工資表范本
- 小學(xué)二年級下口算題1000道(50道每天)
- 空調(diào)安裝免責(zé)協(xié)議書模板
- 浙江省石材面板保溫裝飾板外墻外保溫系統(tǒng)應(yīng)用技術(shù)規(guī)程
- 換電站(充電樁)安全風(fēng)險告知模板
- 寧夏傳統(tǒng)文化調(diào)研報告范文
- 景區(qū)食堂經(jīng)營外包合同
評論
0/150
提交評論