首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

BCB DLL 開發完全指南:靜態/動態載入、MDI 子視窗、VC++ 互操作

     編輯:關於C++

一、建立 DLL 專案

  1. File → New → DLL Wizard 產生專案骨架。
  2. 專案選項 → Build with runtime packages 取消勾選 (單一檔案部署) 或勾選 (體積小、需部署 BPL)。
  3. 定義匯出巨集 (專案選項 → Conditionals → Defines):
    
    #ifdef BUILDING_MYDLL
    #define MYDLL_API __declspec(dllexport)
    #else
    #define MYDLL_API __declspec(dllimport)
    #endif
    

二、匯出函式與類別

匯出函式 (C 連結,避免名稱修飾)


// MyDLL.h
#ifdef __cplusplus
extern "C" {
#endif
MYDLL_API int  __stdcall Add(int a, int b);
MYDLL_API void __stdcall ShowMessage(const char* msg);
#ifdef __cplusplus
}
#endif

// MyDLL.cpp
#include "MyDLL.h"
#include 
#pragma hdrstop

int __stdcall Add(int a, int b) { return a + b; }
void __stdcall ShowMessage(const char* msg) { ::MessageBoxA(NULL, msg, "DLL", MB_OK); }

匯出 C++ 類別 (需相同編譯器/版本/RTL 設定)


// MyClass.h
class MYDLL_API __stdcall MyClass {
public:
    MyClass();
    ~MyClass();
    void DoSomething();
private:
    int m_data;
};

// MyClass.cpp
#include "MyClass.h"
MyClass::MyClass() : m_data(0) {}
MyClass::~MyClass() {}
void MyClass::DoSomething() { m_data++; }

三、靜態載入 (Load-time Dynamic Linking)

  1. 使用 implib.exe 由 DLL 產生 .lib 匯入庫:
    
    implib MyDLL.lib MyDLL.dll
    
  2. 將 .lib 加入應用程式專案 (Project → Add to Project)。
  3. 應用程式標頭檔引入相同宣告 (改用 dllimport),直接呼叫函式/建立類別實例。
  4. 優點:程式碼簡潔、編譯期檢查、IDE 程式碼導航支援。缺點:DLL 必須存在於啟動路徑、版本綁定緊密。

四、動態載入 (Run-time Dynamic Linking)


// 應用程式
typedef int (__stdcall *PFN_ADD)(int, int);
typedef void (__stdcall *PFN_SHOW)(const char*);

HINSTANCE hDll = LoadLibrary(_T("MyDLL.dll"));
if (hDll) {
    PFN_ADD pAdd = (PFN_ADD)GetProcAddress(hDll, "Add");
    PFN_SHOW pShow = (PFN_SHOW)GetProcAddress(hDll, "ShowMessage");
    if (pAdd && pShow) {
        int sum = pAdd(3, 5); // 8
        pShow("Hello from DLL!");
    }
    FreeLibrary(hDll);
}
  • 優點:可延遲載入、條件載入、多版本相容、DLL 缺失不影響主程式啟動。
  • 缺點:無編譯期型別檢查、函式指標易出錯、需手動管理生命週期。

五、DLL 實作 MDI 子視窗 (傳遞 Application 指標)

關鍵:DLL 與 EXE 必須共用同一個 VCL Application 物件,否則視窗無法正確嵌入 MDI 框架、工具列合併失敗、快捷鍵無效。


// DLL 端
TApplication* SavedApp = NULL;

int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*)
{
    if (reason == DLL_PROCESS_DETACH && SavedApp)
        Application = SavedApp; // 還原
    return 1;
}

extern "C" __declspec(dllexport) __stdcall void CreateMDIChild(TApplication* MainApp, const char* Caption)
{
    if (!SavedApp) { SavedApp = Application; Application = MainApp; }
    TForm1* frm = new TForm1(Application); // 傳遞 Application 給建構函式
    frm->Caption = Caption;
    frm->Show();
}

// EXE 端呼叫
typedef void (__stdcall *PFN_CREATE)(TApplication*, const char*);
HINSTANCE hDll = LoadLibrary("ChildDLL.dll");
if (hDll) {
    PFN_CREATE pCreate = (PFN_CREATE)GetProcAddress(hDll, "CreateMDIChild");
    if (pCreate) pCreate(Application, "子視窗標題");
}

六、BCB 呼叫 VC++ 建立的 DLL (呼叫慣例與名稱修飾)

核心差異

關鍵:extern "C" 可抑制 C++ 名稱修飾,但 無法抑制 __stdcall/@N 後綴 (VC++ 特有)。

動態載入 VC++ DLL 實戰


// VC++ DLL 端 (VC_DLL.cpp)
extern "C" __declspec(dllexport) const char* __stdcall GetStringStd() { return "VC __stdcall OK"; }
extern "C" __declspec(dllexport) const char* __cdecl GetStringCdecl() { return "VC __cdecl OK"; }
extern "C" __declspec(dllexport) const char* __fastcall GetStringFast() { return "VC __fastcall OK"; }

// BCB 端動態載入
HINSTANCE hDll = LoadLibrary("VC_DLL.dll");
if (hDll) {
    // 使用 tdump -ee VC_DLL.dll 查看實際匯出名稱
    // 假設 tdump 輸出:
    //   _GetStringStd@0
    //   _GetStringCdecl
    //   @GetStringFast@0

    typedef const char* (__stdcall *PFN_STD)();
    typedef const char* (__cdecl *PFN_CDECL)();
    typedef const char* (__fastcall *PFN_FAST)();

    PFN_STD pStd = (PFN_STD)GetProcAddress(hDll, "_GetStringStd@0");
    PFN_CDECL pCdecl = (PFN_CDECL)GetProcAddress(hDll, "_GetStringCdecl");
    PFN_FAST pFast = (PFN_FAST)GetProcAddress(hDll, "@GetStringFast@0");

    if (pStd) ShowMessage(pStd());
    if (pCdecl) ShowMessage(pCdecl());
    if (pFast) ShowMessage(pFast());
    FreeLibrary(hDll);
}

靜態載入 VC++ DLL (需 .def 別名)

  1. 用 impdef VC_DLL.def VC_DLL.dll 產生模組定義檔。
  2. 編輯 .def 加入 IMPORTS 區段對應別名:
    
    LIBRARY VC_DLL.DLL
    IMPORTS
        GetStringStd = VC_DLL._GetStringStd@0
        GetStringCdecl = VC_DLL._GetStringCdecl
        GetStringFast = VC_DLL.@GetStringFast@0
    
  3. 將 .def 加入 BCB 專案 (Project → Add to Project)。
  4. BCB 程式碼正常宣告 extern "C" __declspec(dllimport) ... 並呼叫。

七、常用工具

  • tdump -ee MyDLL.dll (BCB) / dumpbin /exports MyDLL.dll (VC++):檢視實際匯出名稱與序數。
  • implib MyDLL.lib MyDLL.dll:由 DLL 產生匯入庫。
  • impdef MyDLL.def MyDLL.dll:由 DLL 產生 .def 檔。
  • Dependency Walker (depends.exe) / ldd:分析相依 DLL。

八、最佳實務總結

呼叫慣例BCB 匯出名稱VC++ 匯出名稱參數傳遞/清理堆疊
__cdecl_FuncName_FuncName呼叫者清理,參數右至左入堆疊
__stdcallFuncName_FuncName@N (N=堆疊位元組數)被呼叫者清理,參數右至左
__fastcall@FuncName@N@FuncName@N前三參數用 EAX/EDX/ECX,其餘堆疊,被呼叫者清理
__pascalFUNCNAME不支援呼叫者清理,參數左至右
場景建議
純 C 介面、跨編譯器/語言extern "C" __stdcall + 動態載入 + 版本化函式名 (FuncName_V1)
BCB 專用、C++ 類別、緊密整合靜態載入 + 匯出類別 + 共用 RTL (勾選 Build with runtime packages)
插件架構、熱插拔動態載入 + 介面類別 (純虛擬) + 工廠函式 CreatePlugin()
呼叫 VC++/Delphi/其他編譯器 DLL動態載入 + tdump/dumpbin 確認名稱 + __stdcall 統一慣例
DLL 內使用 VCL 視窗/元件傳遞 EXE 的 Application 指標、DLL_PROCESS_DETACH 還原、共用 RTL

結語

DLL 開發核心在於呼叫慣例一致、名稱修飾對應、記憶體管理邊界清晰 (誰配置誰釋放、跨模組 new/delete 需共用 RTL)。掌握 tdump/dumpbin、.def 別名、implib、動態載入技巧,即可應對 BCB/VC++/Delphi 混合開發場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved