Delphi 擁有龐大的第三方 VCL 元件生態,而 C++Builder 雖共享 VCL 框架,但編譯後的 `.bpl`/`.dcu` 無法直接在 C++ 專案中連結(名稱修飾、例外處理、RTTI 差異)。重寫元件成本高、維護難。本文示範 「Delphi 封裝 DLL → C++Builder 動態載入」 模式,實現非視覺元件的跨語言復用。
+------------------+ C 介面匯出 +------------------+
| Delphi DLL | <-------------------> | C++Builder EXE |
| (VCL 元件實體) | 純扁平 API | (呼叫端) |
+------------------+ +------------------+
▲ ▲
│ 建立/銷毀元件實例 │ 持有 opaque handle
│ 封裝屬性/方法為函數 │ 透過函數指標操作
File → New → DLL Project,命名 SampleDLL。
unit SampleWrap;
interface
uses Classes, SysUtils;
type
TSampleComp = class(TComponent)
private
FActive: Boolean;
FPro1, FPro2: Integer;
published
property Active: Boolean read FActive write FActive;
property Pro1: Integer read FPro1 write FPro1;
property Pro2: Integer read FPro2 write FPro2;
end;
// 匯出函數宣告
function OpenSample: LongBool; stdcall;
procedure CloseSample; stdcall;
function GetActive: LongBool; stdcall;
procedure SetActive(Value: LongBool); stdcall;
function GetPro1: Integer; stdcall;
procedure SetPro1(Value: Integer); stdcall;
function GetPro2: Integer; stdcall;
procedure SetPro2(Value: Integer); stdcall;
exports
OpenSample, CloseSample,
GetActive, SetActive,
GetPro1, SetPro1,
GetPro2, SetPro2;
implementation
var
GInstance: TSampleComp = nil;
function OpenSample: LongBool;
begin
if GInstance = nil then GInstance := TSampleComp.Create(nil);
Result := True;
end;
procedure CloseSample;
begin
FreeAndNil(GInstance);
end;
function GetActive: LongBool; begin Result := GInstance.Active; end;
procedure SetActive(Value: LongBool); begin GInstance.Active := Value; end;
function GetPro1: Integer; begin Result := GInstance.Pro1; end;
procedure SetPro1(Value: Integer); begin GInstance.Pro1 := Value; end;
function GetPro2: Integer; begin Result := GInstance.Pro2; end;
procedure SetPro2(Value: Integer); begin GInstance.Pro2 := Value; end;
end.
SampleDLL.dll 並用 implib 產生 .libimplib SampleDLL.lib SampleDLL.dll
Project → Add to Project → 選擇 SampleDLL.lib。
// SampleDLL.h
extern "C" {
__declspec(dllimport) bool __stdcall OpenSample(void);
__declspec(dllimport) void __stdcall CloseSample(void);
__declspec(dllimport) bool __stdcall GetActive(void);
__declspec(dllimport) void __stdcall SetActive(bool);
__declspec(dllimport) int __stdcall GetPro1(void);
__declspec(dllimport) void __stdcall SetPro1(int);
__declspec(dllimport) int __stdcall GetPro2(void);
__declspec(dllimport) void __stdcall SetPro2(int);
}
#include "SampleDLL.h"
void __fastcall TForm1::Button1Click(TObject *Sender) {
if (!OpenSample()) { ShowMessage("DLL 初始化失敗"); return; }
SetActive(true);
SetPro1(123);
SetPro2(456);
ShowMessage(Format("Active=%d, Pro1=%d, Pro2=%d",
ARRAYOFCONST((GetActive(), GetPro1(), GetPro2()))));
CloseSample();
}
| 議題 | 解決方案 |
|---|---|
| 字串傳遞 | 匯出介面僅用 PAnsiChar/PWideChar + 長度,或 WideString (BStr);避免 AnsiString 跨界 |
| 例外處理 | DLL 內部 try/except 捕獲所有異常,轉為錯誤碼返回;絕不讓 Delphi 例外逃出 DLL |
| 記憶體管理 | Delphi 端 ShareMem 或統一用 GetMem/FreeMem;C++ 端不直接 delete Delphi 物件 |
| 執行緒安全 | 全域實例 GInstance 僅單執行緒適用;多執行緒需改為 TThreadLocal 或連線池模式 |
| 視覺元件限制 | 本模式不適用視覺控制項(需 Parent/Handle/訊息迴環);視覺元件請改用 Package (.bpl) 共享 VCL 或 ActiveX/COM 封裝 |
「Delphi DLL + C 介面 + C++Builder 動態載入」是歷史專案快速復用 Delphi 非視覺邏輯的經典捷徑。核心在於 扁平化 API 設計、嚴格例外隔離、明確所有權語義。若專案已遷移至現代 C++Builder 版本,優先評估原生 Delphi Personality 整合,減少維護負擔。