天天看點

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 {

        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;

    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");

    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;

    if ( (reason==DLL_PROCESS_DETACH) && SaveApp )

        Application = SaveApp ; // 恢複 Application

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格式

      這時函數名全部變成大寫,第一個參數先壓棧,然後清棧。

// 本文轉自 C++Builder研究 - http://www.ccrun.com/article.asp?i=498&d=f36445

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

繼續閱讀