C++Builder 採用單元為單位編譯,每個 .cpp 編譯成獨立的 .obj,再由連結器合併。若在標頭檔直接定義變數,每個引用該標頭的單元都會得到一份獨立副本,導致「同名不同址」的災難。本文詳細說明三種正確做法,並解析 VCL 內部實作機制。
// Globals.h - 錯誤寫法
#ifndef GlobalsH
#define GlobalsH
#include <Classes.hpp>
AnsiString UserName; // 每個 include 此檔的單元都會產生一份副本
#endif
後果:編譯器警告「多重定義」,各單元操作各自的 UserName,賦值不互通。
// Project.cpp (或 .cbp 專案指定的主檔)
#include <vcl.h>
#pragma hdrstop
#include "MainForm.cpp"
// 全域變數實體定義(只能出現一次)
AnsiString UserName;
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
try {
Application->Initialize();
Application->CreateForm(__classid(TMainForm), &MainForm);
Application->Run();
} catch (...) { /* ... */ }
return 0;
}
// Globals.h
#ifndef GlobalsH
#define GlobalsH
#include <Classes.hpp>
// 僅宣告,不配置儲存空間
extern AnsiString UserName;
#endif
// Unit2.cpp
#include "Globals.h"
void SomeFunction() {
UserName = "Admin"; // 正確存取同一個實體
}
VCL 將 Application, Screen 等應用程式級全域物件以此方式導出,支援運行時套件 (BPL) 正確共享符號。
// Globals.cpp - 實作檔
#pragma hdrstop
#include "Globals.h"
#pragma package(smart_init) // 智慧初始化,支援 BPL
AnsiString UserName = "YinJiajun"; // 定義並初始化
// Globals.h - 標頭檔
#ifndef GlobalsH
#define GlobalsH
#include <Classes.hpp>
// PACKAGE 宏在建構套件時展開為 __declspec(dllexport),使用時為 dllimport
extern PACKAGE AnsiString UserName;
#endif
#pragma package(smart_init):確保套件載入/卸載時正確初始化/終結全域物件。PACKAGE 宏定義於 SysInit.hpp,視專案類型自動切換匯入/匯出屬性。避免全域命名空間污染,封裝存取邏輯,支援延遲初始化與執行緒安全。
// AppContext.h
#ifndef AppContextH
#define AppContextH
#include <System.hpp>
#include <mutex>
namespace AppContext {
class GlobalState {
private:
AnsiString FUserName;
static GlobalState* instance;
static std::mutex mtx;
GlobalState() : FUserName("Guest") {}
public:
static GlobalState& Instance() {
std::lock_guard lock(mtx);
if (!instance) instance = new GlobalState();
return *instance;
}
AnsiString GetUserName() const { return FUserName; }
void SetUserName(const AnsiString& name) { FUserName = name; }
};
// 便利函數
inline AnsiString UserName() { return GlobalState::Instance().GetUserName(); }
inline void SetUserName(const AnsiString& n) { GlobalState::Instance().SetUserName(n); }
}
#endif
使用方式:AppContext::SetUserName("Admin"); / AppContext::UserName()
| 變數 | 型別 | 用途 | 定義位置 |
|---|---|---|---|
| Application | TApplication* | 應用程式主物件,管理訊息迴圈、主視窗、提示資訊 | Forms.hpp |
| Screen | TScreen* | 螢幕裝置資訊、字型、游標、表單清單 | Forms.hpp |
| Clipboard | TClipboard* | 剪貼簿存取 | Clipbrd.hpp |
| Mouse | TMouse* | 滑鼠狀態、游標控制 | Controls.hpp |
| IsConsole | bool | 是否為主控台應用程式 | SysInit.hpp |
| IsLibrary | bool | 是否為 DLL/BPL | SysInit.hpp |