前文我們講到了怎么不依賴第三庫,搭建自己的測試框架
沒有看的讀者可以通過這個鏈接自行閱讀:
👉👉👉 自力更生:0依賴三方庫,手把手教你打造專屬C++測試框架
作為項目開發來說,我們通常會將不同的功能、數據進行分層或分塊,這其實就涉及到不同模塊之間的相互調用。選擇什么樣的策略來實現模塊間能力的交互,其實是非常重要的。
本文中,我們就來抽象業務邏輯,看下模塊間的加載是如何實現的。
文章目錄
- 模塊間的加載方式
- 動態加載(Dynamic Loading)
- 靜態加載(Static Loading)
- 什么時候用什么方式
- 怎么用
- 動態加載示例介紹
- 靜態加載示例介紹
- 總結
模塊間的加載方式
通常而言,模塊間的加載方式可以分為兩類:
- 動態加載
- 靜態加載
什么是動態加載呢?簡單而言就是模塊間遵循動態加載協議,平臺層模塊或業務層模塊主動的調用 LoadLibrary()
和 GetProcAddress
接口來加載其他模塊。
什么是靜態加載呢?簡單而言就是模塊間遵循靜態加載協議,平臺層模塊或業務層模塊在編譯時就將其他模塊的依賴關系鏈接進來(通常建議在編寫平臺層 CMakeLists.txt 時,將依賴的庫或模塊列入鏈接庫的列表,并設置鏈接類型為靜態庫
)。
筆者在撰寫此文時特地查了相關資料,對它們的特點做下介紹(PS: 文中有些地方可能有誤,如有疑問,歡迎指正):
動態加載(Dynamic Loading)
動態加載指的是在程序運行時,根據需要加載和卸載庫或模塊。這種方式有以下幾個特點:
- 運行時鏈接:庫的代碼在程序運行時才被加載,通常是通過動態鏈接庫(Dynamic Link Library,DLL)在Windows上或共享庫(Shared Object,SO)在Unix-like系統上實現。
- 靈活性:可以在不重啟程序的情況下加載或卸載模塊,提供更高的靈活性。
- 內存使用:只需要加載正在使用的庫,節省內存。
- 更新和維護:可以獨立更新庫而不需要重新編譯整個程序。
- 依賴管理:需要更復雜的依賴管理,因為庫在運行時才被加載。
靜態加載(Static Loading)
靜態加載指的是在程序編譯時,庫或模塊已經被鏈接到程序中。這種方式有以下幾個特點:
- 編譯時鏈接:庫的代碼在編譯期間被鏈接到最終的可執行文件中。
- 加載時間:因為庫的代碼已經是程序的一部分,所以不需要在程序運行時加載。
- 內存使用:靜態加載的庫會占用更多的內存,因為所有庫的代碼都會被包含在最終的可執行文件中。
- 更新和維護:更新靜態加載的庫可能需要重新編譯整個程序。
- 依賴管理:靜態加載簡化了依賴管理,因為所有依賴都包含在程序中。
什么時候用什么方式
也許讀者在其他地方看到過它的使用建議,比如說根據加載的時間
、內存的使用
、模塊間交互的頻繁性
,模塊間的依賴關系復雜度
、模塊的更新頻率
等方面進行選擇。
筆者就不在從這些維度進行闡述了。
筆者從工程實踐的角度出發,講講兩種使用的場景:
- 當主模塊沒有代碼邏輯依賴該模塊暴露的API接口時,該模塊的能力僅僅是主模塊能力的增強,就可以采用動態加載方式。
- 當主模塊有代碼邏輯依賴該模塊暴露的API接口時,該模塊的能力是主模塊的核心功能,就可以采用靜態加載方式。
怎么用
動態加載示例介紹
場景: 假設有主模塊A,在其上開發了模塊B,以進行能力增強。
那么對于模塊 A
來說,得做如下事情:
- 定義模塊間交互的協議類
TestModuleBase
,用于模塊B
來派生,自定義自己的加載、卸載業務邏輯。
// xx.h
class TestModuleBase
{
public:TestModuleBase();virtual ~TestModuleBase();virtual SystemStatus initialize();virtual SystemStatus uninitialize();private:TestModuleBase(const TestModuleBase&) = delete;TestModuleBase& operator=(const TestModuleBase&) = delete;
};typedef TestModuleBase* (__cdecl* RxProlModule)();// xx.cpp
TestModuleBase::TestModuleBase()
{}TestModuleBase::~TestModuleBase()
{}SystemStatus TestModuleBase::initialize()
{return SystemStatus::e_Ok;
}SystemStatus TestModuleBase::uninitialize()
{return SystemStatus::e_Ok;
}
- 定義一個模塊加載器
TestModuleLoader
類,用來管理模塊的加載和卸載。
// xx.h
class DEMO_TEST_STATIC_EXPORT TestModuleLoader
{
public:TestModuleLoader() = delete;static SystemStatus loadModule(const wchar_t* pPath);static SystemStatus unloadModule(const wchar_t* pPath);private:TestModuleLoader(const TestModuleLoader&) = delete;TestModuleLoader& operator=(const TestModuleLoader&) = delete;
};// xx.cpp
SystemStatus TestModuleLoader::loadModule(const wchar_t* pPath)
{HMODULE pHandleModule = LoadLibrary(pPath);if (!pHandleModule == NULL) {return SystemStatus::e_Fail;}RxProlModule rxProlModule = (RxProlModule)(GetProcAddress(pHandleModule, "rxProtocalModule"));if (rxProlModule == NULL) {FreeLibrary(pHandleModule);return SystemStatus::e_Fail;}TestModuleBase* pModule = rxProlModule();if (pModule == NULL) {FreeLibrary(pHandleModule);return SystemStatus::e_Fail;}return pModule->initialize();
}SystemStatus TestModuleLoader::unloadModule(const wchar_t* pPath)
{if (pPath == NULL) {return SystemStatus::e_Fail;}HMODULE pHandleModule = GetModuleHandle(pPath);if (pHandleModule == NULL) {return SystemStatus::e_Fail;}RxProlModule rxProlModule = (RxProlModule)(GetProcAddress(pHandleModule, "rxProtocalModule"));if (rxProlModule == NULL) {FreeLibrary(pHandleModule);return SystemStatus::e_Fail;}TestModuleBase* pModule = rxProlModule();if (pModule == NULL) {FreeLibrary(pHandleModule);return SystemStatus::e_Fail;}SystemStatus uninitResult = pModule->uninitialize();if (uninitResult != SystemStatus::e_Ok) {return uninitResult;}FreeLibrary(pHandleModule);return SystemStatus::e_Ok;
}
- 在適當的時機,比如說模塊
A
初始化和反初始化的時候,對模塊B
進行主動的加載和卸載。
SystemStatus PlatformModule::initialize()
{...auto ss = TestModuleLoader::loadModule("DemoModule1.dll")if (ss != SystenStatus::e_Ok) {return ss;}...
}SystemStatus PlatformModule::uninitialize()
{...auto ss = TestModuleLoader::unloadModule("DemoModule1.dll")if (ss != SystenStatus::e_Ok) {return ss;}...
}
那么對于模塊 B
來說,得做如下事情:
- 派生協議類
TestModuleBase
,實現自己的加載、卸載業務邏輯。
// xx.h
class DemoTestModule :public TestModuleBase
{
public:DemoTestModule();~DemoTestModule();SystemStatus initialize() override;SystemStatus uninitialize() override;private:DemoTestModule(const DemoTestModule&) = delete;DemoTestModule& operator=(const DemoTestModule&) = delete;bool m_initialized = false;
};DEMO_TEST_C_EXPORT TestModuleBase* rxProtocalModule();// xx.cpp
DEMO_TEST_C_EXPORT TestModuleBase* rxProtocalModule()
{static DemoTestModule module;return &module;
}DemoTestModule::DemoTestModule()
{}DemoTestModule::~DemoTestModule()
{}SystemStatus DemoTestModule::initialize()
{if (m_initialized) {return SystemStatus::e_NotSpecified;}m_initialized = true;// do something for initreturn SystemStatus::e_Ok;
}SystemStatus DemoTestModule::uninitialize()
{if (m_initialized) {// do uninit thingm_initialized = false;}return SystemStatus::e_Ok;
}
靜態加載示例介紹
場景: 假設有主模塊A,在其上開發了模塊B,以實現核心功能。
- 對于模塊
B
來說,需定義一個模塊初始化的類,實現該模塊中對象的初始化和反初始化。同時給該類設置一個靜態對象。
class DemoModuleInit
{
public:DemoModuleInit(){// do init thing}~DemoModuleInit(){// do uninit thing}
};static DemoModuleInit g_demoModuleInit;
- 在模塊
A
的CMakeLists.txt中,將模塊B
的靜態庫或模塊鏈接進來。
link_directories("${PROJECT_SOURCE_DIR}/lib")
...
link_libraries(DemoModule1)
總結
以上就是模塊間加載的兩種方式,動態加載和靜態加載,以及它們的使用場景和示例。希望能對讀者有所幫助。
如果讀者有不理解的地方,歡迎私信交流。