BCB編寫DLL

 
一.注意:
創建動態鏈接庫時,如果想你創建的動態鏈接庫並非只用於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;
}
//---------------------------------------------------------------------------
保存工程爲DLLs,編譯,就會在工程目錄下生成DLLs.dll(大小應該是8K),同時還會自動在同一目錄下生成DLLs.lib靜態庫,如果沒有生成,請自己手動使用implib.exe工具生成(使用方法見本文說明)。

(2).導出函數(使用VCL)
使用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;
     }
}
//---------------------------------------------------------------------------
保存工程爲DLLs,編譯,就會在工程目錄下生成DLLs.dll,同時還會自動在同一目錄下生成DLLs.lib靜態庫,如果沒有生成,請自己手動使用implib.exe工具生成。

(3).導出類
使用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;
}
//---------------------------------------------------------------------------
保存工程爲DLLs,編譯,就會在工程目錄下生成DLLs.dll,同時還會自動在同一目錄下生成DLLs.lib靜態庫,如果沒有生成,請自己手動使用implib.exe工具生成。
注意,並非所有的代碼都需要在工程DLL主文件中寫,導出函數和類等都可以在其它單元中設計,然後在主文件中#include即可,看自己的習慣,當然了,一個巨大的DLL工程,不可能寫在一個文件中的。

(4).使用(1)創建的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!");
     }

(5).使用(2)創建的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!");
     }

(6).使用(3)創建的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(不加參數)。

====

BCB編寫DLL終極手冊

  一. 編寫 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();
}

二. 靜態調用 DLL

  使用 $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;
}

三. 動態調用 DLL

// 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 編譯成功

---------------------------[版權聲明]--------------------------- 本博客的技術文章以及程序源碼,包括原創,轉載和翻譯三種類型。部分資料來源於網上,作品的版權屬於原創作者所有。凡是在標題處帶有“【原創】”標識的隨筆、文章等,都爲本人根據自己的實際能力所創,其他即爲轉載。如果您需要轉載本博客發佈的原創及翻譯作品,請註明來源,作者及翻譯人等信息,如果本博客發佈的內容觸犯了您的版權,請來信告訴我,我將馬上刪除,謝謝合作。
發佈了53 篇原創文章 · 獲贊 15 · 訪問量 25萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章