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

下載本文檔

版權(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)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論