創建動態鏈接庫時,如果想你創建的動態鏈接庫並非只用於Borland開發工具,那麼就需要遵循發下規則:
(1).在導出函數的返回值和參數中不要使用Borland特有的數據類型和結構體,如AnsiString之類,請使用C/C++標準的數據類型或使用 C/C++標準數據類型定義的結構體(特別不要使用String數據類型,BCB DLL嚮導生成的DLL工程文件中大篇幅的說明就是對此的說明,請自己查閱);
(2).請使用extern "C"命名約定,這樣,生成的DLL中的導出函數,就不會使用C++的命名約定,而是使用的C命名約定,即導出函數不會名字分解,而是和你定義的函數相同;
(3).導出函數請使用WIN32 API的調用方式__stdcall(即WINAPI)或VC與BorlandC++的調用約定__cdecl,不要使用Borland特有的__fastcall調用約定,否則只有Borland開發工具纔可以使用這些動態鏈接庫;
(1).導出函數(不使用VCL)
使用BCB建立DLL嚮導來建立一個工程,選擇不使用VCL,代碼如下:
//---------------------------------------------------------------------------
#include
//---------------------------------------------------------------------------
// Important note about DLL memory management when your DLL uses the
// static version of the RunTime Library:
//
// If your DLL exports any functions that pass String objects (or structs/
// classes containing nested Strings) as parameter or function results,
// you will need to add the library MEMMGR.LIB to both the DLL project and
// any other projects that use the DLL. You will also need to use MEMMGR.LIB
// if any other projects which use the DLL will be performing new or delete
// operations on any non-TObject-derived classes which are exported from the
// DLL. Adding MEMMGR.LIB to your project will change the DLL and its calling
// EXE's to use the BORLNDMM.DLL as their memory manager. In these cases,
// the file BORLNDMM.DLL should be deployed along with your DLL.
//
// To avoid using BORLNDMM.DLL, pass string information using "char *" or
// ShortString parameters.
//
// If your DLL uses the dynamic version of the RTL, you do not need to
// explicitly add MEMMGR.LIB as this will be done implicitly for you
//---------------------------------------------------------------------------
#pragma argsused
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
return 1;
}
//---------------------------------------------------------------------------
extern "C" __declspec(dllexport) int __stdcall Calc(int a, int b) //導出函數
{
return a+b;
}
//---------------------------------------------------------------------------
使用BCB DLL嚮導建立一個工程,選擇使用VCL支持,新建立一個窗體(採用默認名稱TForm1)存爲Unit1.cpp,設置窗體大小爲一般OKCANCEL 對框的大小(這裏設爲277*119),在窗體上添加兩個按鈕:Button1標籤爲"確定",ModalResult爲mrOk,Button2標籤爲 "取消",ModalResult爲mrCancel;
然後在DLL主程序中引用剛纔建立的窗體,並添加導出函數,代碼如下:
#include
#include
#pragma hdrstop
//---------------------------------------------------------------------------
// Important note about DLL memory management when your DLL uses the
// static version of the RunTime Library:
//
// If your DLL exports any functions that pass String objects (or structs/
// classes containing nested Strings) as parameter or function results,
// you will need to add the library MEMMGR.LIB to both the DLL project and
// any other projects that use the DLL. You will also need to use MEMMGR.LIB
// if any other projects which use the DLL will be performing new or delete
// operations on any non-TObject-derived classes which are exported from the
// DLL. Adding MEMMGR.LIB to your project will change the DLL and its calling
// EXE's to use the BORLNDMM.DLL as their memory manager. In these cases,
// the file BORLNDMM.DLL should be deployed along with your DLL.
//
// To avoid using BORLNDMM.DLL, pass string information using "char *" or
// ShortString parameters.
//
// If your DLL uses the dynamic version of the RTL, you do not need to
// explicitly add MEMMGR.LIB as this will be done implicitly for you
//---------------------------------------------------------------------------
#include "Unit.h" //引用設計的窗體
#pragma argsused
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
return 1;
}
//---------------------------------------------------------------------------
extern "C" __declspec(dllexport) int __stdcall UserClick(void) //導出函數
{
TForm1 *Form1 = new TForm1(NULL);
if(Form1->ShowModal() == mrOk) {
delete Form1;
return 1;
} else {
delete Form1;
return 0;
}
}
//---------------------------------------------------------------------------
使用BCB建立DLL嚮導來建立一個工程,選擇不使用VCL(本例不使用VCL,是否支持VCL是視你自己的應用而定),代碼如下:
#include
//---------------------------------------------------------------------------
// Important note about DLL memory management when your DLL uses the
// static version of the RunTime Library:
//
// If your DLL exports any functions that pass String objects (or structs/
// classes containing nested Strings) as parameter or function results,
// you will need to add the library MEMMGR.LIB to both the DLL project and
// any other projects that use the DLL. You will also need to use MEMMGR.LIB
// if any other projects which use the DLL will be performing new or delete
// operations on any non-TObject-derived classes which are exported from the
// DLL. Adding MEMMGR.LIB to your project will change the DLL and its calling
// EXE's to use the BORLNDMM.DLL as their memory manager. In these cases,
// the file BORLNDMM.DLL should be deployed along with your DLL.
//
// To avoid using BORLNDMM.DLL, pass string information using "char *" or
// ShortString parameters.
//
// If your DLL uses the dynamic version of the RTL, you do not need to
// explicitly add MEMMGR.LIB as this will be done implicitly for you
//---------------------------------------------------------------------------
#pragma argsused
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
return 1;
}
//---------------------------------------------------------------------------
__declspec(dllexport) __stdcall class TTest{ //聲明導出類
int a,b;
public:
void __stdcall SetValue(int x, int y);
int __stdcall Calc(void);
};
//---------------------------------------------------------------------------
void __stdcall TTest::SetValue(int x, int y) //類的實現
{
this->a = x;
this->b = y;
}
//---------------------------------------------------------------------------
int __stdcall TTest::Calc(void) //類的實現
{
return this->a + this->b;
}
//---------------------------------------------------------------------------
注意,並非所有的代碼都需要在工程DLL主文件中寫,導出函數和類等都可以在其它單元中設計,然後在主文件中#include即可,看自己的習慣,當然了,一個巨大的DLL工程,不可能寫在一個文件中的。
新建一個工程。
[1].靜態調用:
向工程中添加(1)中生成的靜態庫DLLs.lib,然後在.h中添加導出函數的定義,如下:
extern "C" __declspec(dllimport) int __stdcall Calc(int,int); //導出DLL中函數
然後就可以在這個單元中使用int __stdcall Calc(int,int)這個函數了,如:
ShowMessage(IntToStr(Calc(5,10));
[2].動態調用
不需要添加靜態庫.lib文件,只要在需要調用時才載入DLL,如下:
HINSTANCE Hdl;
int __stdcall (*Calc)(int,int); //定義函數原型
Hdl = ::LoadLibrary("DLLs.dll"); //載入DLL
if(Hdl != NULL) {
Calc = (int __stdcall (*)(int,int))::GetProcAddress(Hdl,"Calc"); //獲取函數入口地址
if(Calc != NULL) {
ShowMessage(IntToStr(Calc(5,10))); //調用DLL中函數
} else {
ShowMessage("不能找到函數入口!");
}
::FreeLibrary(Hdl); //一定不要忘記調用完畢後釋放DLL
} else {
ShowMessage("不能載入DLL!");
}
[1].靜態調用:
向工程中添加(2)中生成的靜態庫DLLs.lib,然後在.h中添加導出函數的定義,如下:
extern "C" __declspec(dllimport) int __stdcall UserClick(void); //導出DLL中函數
然後就可以在這個單元中使用int __stdcall UserClick(void)這個函數了,如:
if(UserClick()) {
ShowMessage("用戶點擊“確定”");
} else {
ShowMessage("用戶點擊“取消”或直接關閉");
}
[2].動態調用
不需要添加靜態庫.lib文件,只要在需要調用時才載入DLL,如下:
HINSTANCE Hdl;
int __stdcall (*UserClick)(void);
Hdl = ::LoadLibrary("DLLs.dll");
if(Hdl != NULL) {
UserClick = (int __stdcall (*)(void))::GetProcAddress(Hdl,"UserClick");
if(UserClick != NULL) {
if(UserClick()) {
ShowMessage("用戶點擊“確定”");
} else {
ShowMessage("用戶點擊“取消”或直接關閉");
}
} else {
ShowMessage("不能找到函數入口!");
}
::FreeLibrary(Hdl);
} else {
ShowMessage("不能載入DLL!");
}
DLL中導出的類,只能使用表態的方式來調用。本例中的DLL調用方法如下:
向工程中添加(3)中生成的靜態庫DLLs.lib,然後在.h中添加導出類的聲明,如下:
__declspec(dllimport) __stdcall class TTest{
int a,b;
public:
void __stdcall SetValue(int x, int y);
int __stdcall Calc(void);
};
然後就可以在這個單元中使用TTest這個類了,如:
TTest *Inst = new TTest;
Inst->SetValue(5,10);
ShowMessage(IntToStr(Inst->Calc()));
delete Inst;
(1).impdef.exe
用法:impdef.exe deffile.def yourdll.dll
生成指定DLL文件的def文件,可以用它來查看DLL中的函數聲明。例如,BCB使用VC的DLL時,可能需要查看一下VC中導出函數的函數名;或者未使用extern "C"調用約定時,可以用它來查看DLL中導出函數的C++命名方式,從而可以正確調用。
(2).implib.exe
用法:implib.exe libfile.lib yourdll.dll
用此工具來生成DLL調用的靜態庫,用於DLL的靜態調用方式。
(3).vctobpru.exe
用此工具可以把VC的工程導入到BCB工程中,它自動轉換VC的工程文件爲BCB工程文件,生成相應的.bpr等文件。
(4).tdump.exe(VC中爲dumpbin.exe)
用法:tdump.exe [options] [inputfile] [listfile] [options]
用此工具可以查看DLL中的導出函數聲明等等,具體用法請運行tdump.exe(不加參數)。
====
一. 編寫 DLL
File/New/Dll 生成 Dll 的嚮導,然後可以添加導出函數和導出類
導出函數:extern "C" __declspec(dllexport) ExportType FunctionName(Parameter)
導出類:class __declspec(dllexport) ExportType ClassName{...}
例子:(說明:只是生成了一個 DLL.dll )
#include "DllForm.h" // TDllFrm 定義
USERES("Dll.res");
USEFORM("DllForm.cpp", DllFrm);
class __declspec(dllexport) __stdcall MyDllClass { //導出類
public:
MyDllClass();
void CreateAForm();
TDllFrm* DllMyForm;
};
TDllFrm* DllMyForm2;
extern "C" __declspec(dllexport) __stdcall void CreateFromFunct();//導出函數
//---------------------------------------------------------------------------
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*)
{
return 1;
}
//---------------------------------------------------------------------------
MyDllClass::MyDllClass()
{
}
void MyDllClass::CreateAForm()
{
DllMyForm = new TDllFrm(Application);
DllMyForm->Show();
}
//---------------------------------------------------------------------------
void __stdcall CreateFromFunct()
{
DllMyForm2 = new TDllFrm(Application);
DllMyForm2->Show();
}
使用 $BCB path\Bin\implib.exe 生成 Lib 文件,加入到工程文件中,將該文件拷貝到當前目錄,使用 implib MyDll.lib MyDll.dll 生成。
// Unit1.h // TForm1 定義
#include "DllForm.h" // TDllFrm 定義
//---------------------------------------------------------------------------
__declspec(dllimport) class __stdcall MyDllClass {
public:
MyDllClass();
void CreateAForm();
TDllFrm* DllMyForm;
};
extern "C" __declspec(dllimport) __stdcall void CreateFromFunct();
class TForm1 : public TForm{}
// Unit1.cpp // TForm1 實現
void __fastcall TForm1::Button1Click(TObject *Sender)
{ // 導出類實現,導出類只能使用靜態方式調用
DllClass = new MyDllClass();
DllClass->CreateAForm();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{ // 導出函數實現
CreateFromFunct();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
delete DllClass;
}
// Unit1.h
class TForm1 : public TForm
{
private: // User declarations
void (__stdcall *CreateFromFunct)();
}
// Unit1.cpp // TForm1
HINSTANCE DLLInst = NULL;
void __fastcall TForm1::Button2Click(TObject *Sender)
{
if( NULL == DLLInst ) DLLInst = LoadLibrary("DLL.dll"); //上面的 Dll
if (DLLInst) {
CreateFromFunct = (void (__stdcall*)()) GetProcAddress(DLLInst,
"CreateFromFunct");
if (CreateFromFunct) CreateFromFunct();
else ShowMessage("Could not obtain function pointer");
}
else ShowMessage("Could not load DLL.dll");
}
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
if ( DLLInst ) FreeLibrary (DLLInst);
}
四. DLL 作爲 MDIChild (子窗體) 【只編寫動態調用的例子】
實際上,調用子窗體的 DLL 時,系統只是檢查應用程序的 MainForm 是否爲 fsMDIForm 的窗體,這樣只要把調用程序的 Application 的 Handle 傳遞給 DLL 的 Application 即可;同時退出 DLL 時也要恢復。
Application
// MDIChildPro.cpp // Dll 實現 CPP
#include "unit1.h" // TForm1 定義 TApplication *SaveApp = NULL; int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*) { if ( (reason==DLL_PROCESS_DETACH) && SaveApp ) Application = SaveApp ; // 恢復 Application return 1; }
extern "C" __declspec(dllexport) __stdcall void TestMDIChild( //1024X768 TApplication* mainApp, LPSTR lpCaption) { if ( NULL == SaveApp ) // 保存 Application,傳遞 Application { SaveApp = Application; Application = mainApp; } // lpCaption 爲子窗體的 Caption TForm1 *Form1 = new TForm1 ( Application, lpCaption ); Form1->Show(); }
注:上面的程序使用 BCB 3.0 編譯成功
五. BCB 調用 VC 編寫的 DLL 1. 名字分解: 沒有名字分解的函數 TestFunction1 // __cdecl calling convention @TestFunction2 // __fastcall calling convention TESTFUNCTION3 // __pascal calling convention TestFunction4 // __stdcall calling convention 有名字分解的函數 @TestFunction1$QV // __cdecl calling convention @TestFunction2$qv // __fastcall calling convention TESTFUNCTION3$qqrv // __apscal calling convention @TestFunction4$qqrv // __stdcall calling convention 使用 extern "C" 不會分解函數名 使用 Impdef MyLib.def MyLib.DLL 生成 def 文件查看是否使用了名字分解
2. 調用約定: __cdecl 缺省 是 Borland C++ 的缺省的 C 格式命名約定,它在標識符前加一下劃線,以保留它原來所有的全程標識符。參數按最右邊參數優先的原則傳遞給棧,然後清棧。 extaern "C" bool __cdecl TestFunction(); 在 def 文件中顯示爲 TestFunction @1 註釋: @1 表示函數的順序數,將在“使用別名”時使用。
__pascal Pascal格式 這時函數名全部變成大寫,第一個參數先壓棧,然後清棧。 TESTFUNCTION @1 //def file __stdcall 標準調用 最後一個參數先壓棧,然後清棧。 TestFunction @1 //def file
__fastcall 把參數傳遞給寄存器 第一個參數先壓棧,然後清棧。 @TestFunction @1 //def file
3. 解決調用約定: Microsoft 與 Borland 的 __stdcall 之間的區別是命名方式。 Borland 採用__stdcall 的方式去掉了名字起前的下劃線。 Microsoft 則是在前加上下劃線,在後加上 @ ,再後跟爲棧保留的字節數。字節數取決於參數在棧所佔的空間。每一個參數都舍入爲 4 的倍數加起來。這種 Miocrosoft 的 DLL 與系統的 DLL 不一樣。 4. 使用別名: 使用別名的目的是使調用文件 .OBJ 與 DLL 的 .DEF 文件相匹配。如果還沒有.DEF 文件,就應該先建一個。然後把 DEF 文件加入 Project。使用別名應不斷修改外部錯誤,如果沒有,還需要將 IMPORTS 部分加入 DEF 文件。 IMPORTS TESTFUNCTIOM4 = DLLprj.TestFunction4 TESTFUNCTIOM5 = DLLprj.WEP @500 TESTFUNCTIOM6 = DLLprj.GETHOSTBYADDR @51 這裏需要說明的是,調用應用程序的 .OBJ 名與 DLL 的 .DEF 文件名是等價的,而且總是這樣。甚至不用考慮調用約定,它會自動匹配。在前面的例子中,函數被說明爲 __pascal,因此產生了大寫函數名。這樣鏈接程序不會出錯。 5. 動態調用例子 VC DLL 的代碼如下:
extern "C" __declspec(dllexport) LPSTR __stdcall BCBLoadVCWin32Stdcall() { static char strRetStdcall[256] = "BCB Load VC_Win32 Dll by __stdcall mode is OK!";
return strRetStdcall; }
extern "C" __declspec(dllexport) LPSTR __cdecl BCBLoadVCWin32Cdecl() { static char strRetCdecl[256] = "BCB Load VC_Win32 Dll by __cdecl mode is OK!";
return strRetCdecl; }
extern "C" __declspec(dllexport) LPSTR __fastcall BCBLoadVCWin32Fastcall() { static char strRetFastcall[256] = "BCB Load VC_Win32 Dll by __fastcall mode is OK!";
return strRetFastcall; }
其實動態調用與調用 BCB 編寫的 DLL 沒有區別,關鍵是查看 DLL 的導出函數名字。可以使用 tdump.exe(BCB工具) 或者 dumpbin.exe(VC工具) 查看 tdump -ee MyDll.dll >1.txt (查看 1.txt 文件即可) 由於 VC6 不支持 __pascall 方式,下面給出一個三種方式的例子:
void __fastcall TForm1::btnBLVCWin32DynClick(TObject *Sender) { /*cmd: tdbump VCWin32.dll >1.txt Turbo Dump Version 5.0.16.4 Copyright (c) 1988, 1998 Borland International Display of File VCWIN32.DLL
EXPORT ord:0000='BCBLoadVCWin32Fastcall::' EXPORT ord:0001='BCBLoadVCWin32Cdecl' EXPORT ord:0002='_BCBLoadVCWin32Stdcall@0' */ if ( !DllInst ) DllInst = LoadLibrary ( "VCWin32.dll" ); if ( DllInst ) { BCBLoadVCWin32Stdcall = (LPSTR (__stdcall *) () ) GetProcAddress ( DllInst, "_BCBLoadVCWin32Stdcall@0" ); //VC Dll // GetProcAddress ( DllInst, "BCBLoadVCWin32Stdcall" ); //BCB Dll if ( BCBLoadVCWin32Stdcall ) { ShowMessage( BCBLoadVCWin32Stdcall() ); } else ShowMessage ( "Can't find the __stdcall Function!" );
BCBLoadVCWin32Cdecl = (LPSTR (__cdecl *) () ) GetProcAddress ( DllInst, "BCBLoadVCWin32Cdecl" ); if ( BCBLoadVCWin32Cdecl ) { ShowMessage( BCBLoadVCWin32Cdecl() ); } else ShowMessage ( "Can't find the __cdecl Function!" );
//Why?不是 'BCBLoadVCWin32Fastcall::',而是 '@BCBLoadVCWin32Fastcall@0'? BCBLoadVCWin32Fastcall = (LPSTR (__fastcall *) () ) //GetProcAddress ( DllInst, "BCBLoadVCWin32Fastcall::" ); GetProcAddress ( DllInst, "@BCBLoadVCWin32Fastcall@0" ); if ( BCBLoadVCWin32Fastcall ) { ShowMessage( BCBLoadVCWin32Fastcall() ); } else ShowMessage ( "Can't find the __fastcall Function!" ); } else ShowMessage ( "Can't find the Dll!" ); }
6. 靜態調用例子 靜態調用有點麻煩,從動態調用中可以知道導出函數的名字,但是直接時(加入 lib 文件到工程文件) Linker 提示不能找到函數的實現 從 4 看出,可以加入 def 文件連接(可以通過 impdef MyDll.def MyDll.dll 獲得導出表)。 建立與 DLL 文件名一樣的 def 文件與 lib 文件一起加入到工程文件。 上面的 DLL(VCWIN32.dll) 的 def 文件爲(VCWIN32.def): LIBRARY VCWIN32.DLL
IMPORTS @BCBLoadVCWin32Fastcall = VCWIN32.@BCBLoadVCWin32Fastcall@0 _BCBLoadVCWin32Cdecl = VCWIN32.BCBLoadVCWin32Cdecl BCBLoadVCWin32Stdcall = VCWIN32._BCBLoadVCWin32Stdcall@0
對應的函數聲明和實現如下: extern "C" __declspec(dllimport) LPSTR __fastcall BCBLoadVCWin32Fastcall(); extern "C" __declspec(dllimport) LPSTR __cdecl BCBLoadVCWin32Cdecl(); extern "C" __declspec(dllimport) LPSTR __stdcall BCBLoadVCWin32Stdcall();
void __fastcall TfrmStatic::btnLoadDllClick(TObject *Sender) { ShowMessage ( BCBLoadVCWin32Fastcall() ); ShowMessage ( BCBLoadVCWin32Cdecl() ); ShowMessage ( BCBLoadVCWin32Stdcall() ); } 注意:在 BCB 5.0 中,可能直接按下 F9 是不能通過 Linker 的,請先 Build 一次。上面的程序使用 BCB 5.0 與 VC6.0 編譯成功