在CBuilder里創(chuàng)建可以被VisualC使用的DLL_第1頁(yè)
在CBuilder里創(chuàng)建可以被VisualC使用的DLL_第2頁(yè)
在CBuilder里創(chuàng)建可以被VisualC使用的DLL_第3頁(yè)
在CBuilder里創(chuàng)建可以被VisualC使用的DLL_第4頁(yè)
在CBuilder里創(chuàng)建可以被VisualC使用的DLL_第5頁(yè)
已閱讀5頁(yè),還剩29頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(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ì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論