在 DLL 專案中定義類,需導出供外部調用。直接在類前加 __declspec(dllexport) 編譯 DLL 時正確,但引用該頭文件的客戶端編譯會報錯 (因客戶端需 dllimport)。
在頭文件中定義宏,根據編譯環境自動切換:
// MyClass.h
#ifndef MYCLASS_H
#define MYCLASS_H
#ifdef MYCLASS_EXPORTS // DLL 專案定義此宏
#define MYCLASS_API __declspec(dllexport)
#else // 客戶端專案不定義
#define MYCLASS_API __declspec(dllimport)
#endif
class MYCLASS_API MyClass {
public:
MyClass();
void doSomething();
private:
int data_;
};
#endif // MYCLASS_H
在 DLL 專案的屬性 → C/C++ → Preprocessor → Preprocessor Definitions 中添加 MYCLASS_EXPORTS。客戶端只需包含頭文件、鏈接 .lib,無需修改代碼。
使用模塊定義文件 (MyClass.def) 列出導出符號,避免修改頭文件:
LIBRARY MyClass
EXPORTS
?doSomething@MyClass@@QEAAXXZ // 裝飾名,需用 dumpbin 獲取
??0MyClass@@QEAA@XZ // 構造函數
??1MyClass@@QEAA@XZ // 析構函數
但需手動維護裝飾名,不推薦用於類導出。
Linux 下無 dllexport,默認所有符號可見。但為規範接口、隱藏內部符號,建議:
extern "C" 防止名字修飾,便于 dlsym 查找。// SMSGamePlugin.h (公共基類)
class CSMSGamePlugin {
public:
virtual int Initialize() = 0;
virtual int Load() = 0;
virtual int Handle() = 0;
virtual ~CSMSGamePlugin() {}
};
// Flower.h (插件實現)
#include "SMSGamePlugin.h"
extern "C" CSMSGamePlugin* GetGameObject(); // 工廠函數
class CFlower : public CSMSGamePlugin {
public:
int Initialize() override;
int Load() override;
int Handle() override;
};
// Flower.cpp
#include "Flower.h"
extern "C" CSMSGamePlugin* GetGameObject() { return new CFlower(); }
int CFlower::Initialize() { puts("Init"); return 0; }
int CFlower::Load() { puts("Load"); return 0; }
int CFlower::Handle() { puts("Handle"); return 0; }
#include <dlfcn.h>
#include "SMSGamePlugin.h"
int main() {
void* handle = dlopen("./Flower.so", RTLD_LAZY);
auto create = (CSMSGamePlugin*(*)())dlsym(handle, "GetGameObject");
CSMSGamePlugin* plugin = create();
plugin->Initialize();
plugin->Load();
plugin->Handle();
delete plugin;
dlclose(handle);
return 0;
}
編譯:g++ -fPIC -shared -o Flower.so Flower.cpp,主程序 g++ -rdynamic -ldl -o main main.cpp。
dlopen 獲取的對象指針,在 dlclose 前必須 delete,否則因代碼段卸載導致段錯誤。