我们在编写C++框架时,经常会涉及到一项基础技术,就是根据“一个动态库 + 一个类名称字符串“,动态的创建类对象。
这样做的好处是可以实现框架与业务代码的彻底解耦。框架不用关心业务侧的具体实现细节,只需要提供一个基类由业务方继承实现,然后业务方在配置文件中配置对应的动态库+类名称即可实现自动加载并运行。我们通常把这类功能称为classloader,今天就带大家一起来实现下。
一、实现原理
classloader一般包含shared_library动态库加载/卸载、object_factory动态对象创建工厂、class_register动态类注册,以及class_loader统一管理部分。整体关系如下图:
动态对象创建的关键是,框架不知道业务类的任何信息,所以需要业务侧在实现Derived类(即图中DemoComponent)之后,使用REGISTER_CLASS宏注册类以让框架知晓(注册时,宏需要自动为类生成一个Creater方法并注册给ObjectFactory,因此保存注册信息的ObjectFactory需要使用单例)。
框架持有每个业务组件类的Creater方法之后,在需要创建CreateObject时使用单例保存的业务类注册creater即可创建对应对象。
二、具体实现
这部分是最简单的,我们使用dlopen、dlclose来实现即可,简单的封装下,这里不赘述,我们重点讲解后续部分。
class SharedLibrary {
...
bool Load(const std::string &libraryFile){
...
// Load so
_libraryHandle = dlopen(libraryFile.c_str(), real_flag);
if (!_libraryHandle) {
const char *err = dlerror();
LOG(ERROR) << "Library [" << libraryFile << "] load fail: " << std::string(err);
return false;
}
...
}
...
bool UnLoad(){
...
// UnLoad so
if (_libraryHandle) {
LOG(INFO) << "UnLoad";
dlclose(_libraryHandle);
_libraryHandle = nullptr;
}
...
}
...
}
2、object_factory对象创建工厂与class_register动态类注册
object_factory对象工厂是classloader的核心,决定了如何完全松耦合的实现动态类创建。class_register作为辅助,为object_factory的预先注册提供宏支持。
2.1 新手入门
提到动态创建对象,相信新手想到的最简单的方法通常如下:
void CreateObject(std::string ObjName)
{
if(ObjName == “A”)
{
new A;
}
else if(ObjName == “B”)
{
new B;
}
...
}
但是这里有个问题,框架编写时并不知道业务要起什么类名,框架编译时也没有这些类的so库,又如何在代码中提前编写new 类名的代码呢?况且业务代码一直在变,框架怎么可能跟着业务变化一直修改这些地方呢?显然这种方法是不可行的。
2.2 宏注册
为了解决这个问题,接下来很多同学会想到可以用宏来实现传入字符串,new出对应的类,这个思路很好,所以我们接下来实现了一个宏定义:
// 动态注册类creator
#define REGISTER_CLASS(ClassName) \
ClassName *Create##ClassName() \
{ \
return new ClassName; \
} \
static bool g_reg_##ClassName = ObjectFactory::GetInstance()->Register(#Derived, Create##UniqueID);
上面这个宏很简单,首先是定义了一个new对象的函数,new一个传入类名的对象。接着定义了一个static g_reg_xx变量,这个变量被初始化的时候,会调用到ObjectFactory::Register来保存类名和类creater方法的关系。这里我们使用static GetInstance单例函数,确保在所有static g_reg_xx对象构造之前,ObjectFactory单例对象已经完成了初始化。这里的static g_reg_xx变量也可以使用struct构造函数来代替,目的都是为了在加载REGISTER_CLASS代码之后就能自动创建一个static对象,从而自动完成类注册动作。
接下来,我们要实现下Register逻辑,这里比较简单,实现一个ObjectFactory单例,把Register传入的类名和类creater方法保存下来备用即可:
class ObjectFactory{
// 动态注册类
using ObjCreator = std::function<Object *(void)>;
bool Register(const std::string &className, ObjCreator objCreator)
{
LOG(INFO) << "Register " << className;
_creatorMap.insert(std::make_pair(className, objCreator));
LOG(INFO) << "_creatorMap size: " << _creatorMap.size();
return true;
}
...
}
写到这里,我们会发现objCreator作为Register的通用Creator参数,定义的返回值要想保持一致,需要一个类似Object的基础类。我们可以据此实现这个Object基类,并要求所有的动态类都继承它。但是问题来了,有些类为了方便在类函数中把类对象作为参数传给外部等原因,已经继承了enable_shared_from_this<xxx>等其他基类,而使用多继承则容易让代码逻辑变得比较复杂。
2.3 模板
为了解决上边的问题,我们又想到了一个解决方法,那就是使用模板来解决,这样我们就不需要预先知道业务类的基类了。我们对ObjectFactory进行下模板改造,并实现CreateObject方法:
template <typename Base>
class ObjectFactory {
...
// 动态注册类
using ObjCreator = std::function<Base *(void)>;
bool Register(const std::string &className, ObjCreator objCreator)
{
LOG(INFO) << "Register " << className;
_creatorMap.insert(std::make_pair(className, objCreator));
LOG(INFO) << "_creatorMap size: " << _creatorMap.size();
return true;
}
...
// 动态创建类对象
std::shared_ptr<Base> CreateObject(const std::string &className)
{
LOG(INFO) << "CreateObject " << className << " _creatorMap size: " << _creatorMap.size();
auto it = _creatorMap.find(className);
if (it == _creatorMap.end())
{
LOG(ERROR) << className << " class not registed!";
return nullptr;
}
return std::shared_ptr<Base>(it->second());
}
...
}
REGISTER_CLASS宏也需要同步调整下,增加基类和模板:
// 动态注册类creator(子类、基类)
#define REGISTER_CLASS(Derived, Base) \
Derived *Create##Derived() \
{ \
return new Derived; \
} \
static bool g_reg_##Derived = ObjectFactory<Base>::GetInstance()->Register(#Derived, Create##Derived);
在特定类型组件场景下,基类是固定的,为了方便业务方REGISTER,我们可以额外为这些组件单独定义宏,以减少REGISTER参数,例如:
#define REGISTER_COMPONENT(Derived) \
REGISTER_CLASS(Derived, ComponentBase)
通过模板化改造,我们的封装已经顺利实现了与业务侧的解耦,唯一需要业务侧仅需要在实现业务类的时候调用REGISTER_CLASS进行注册即可。由于是全局static,当业务的so动态库被加载时,即可自动完成注册动作。
3、class_loader统一管理
为了方便对动态加载的so库和创建的obj对象进行统一管理,我们需要有一个类似manager的类,作为classloader入口的同时,持有和管理相关资源。
class ClassLoader
{
...
// load one library
bool ClassLoader::LoadLibrary(const std::string &libraryFile)
{
if (!IsLoaded(libraryFile))
{
LOG(INFO) << "LoadLibrary " << libraryFile;
_libloaderMap[libraryFile] = std::make_shared<SharedLibrary>(libraryFile);
}
return IsLoaded(libraryFile);
}
...
// create Base Object
template <typename Base>
std::shared_ptr<Base> CreateObject(const std::string &className)
{
return ObjectFactory<Base>::GetInstance()->CreateObject(className);
}
...
void ClassLoader::Release()
{
// unload all library
LOG(INFO) << "Release All Library";
for (auto loader : _libloaderMap)
{
UnloadLibrary(loader.first);
}
}
};
4、测试
我们实际测试下动态加载效果:
int main()
{
// create obj
std::string libraryFile("libdemo_component.so");
std::string className("DemoComponent");
ClassLoader classloader;
// load so lib
auto ret = classloader.LoadLibrary(libraryFile);
std::cout << "LoadLibrary: " << ret << std::endl;
// create object
std::shared_ptr<ComponentBase> obj = classloader.CreateObject<ComponentBase>(className);
obj->Init();
return 0;
}
I0406 23:16:19.192689 31550 class_loader.cc:30 ClassLoader()
I0406 23:16:19.192725 31550 class_loader.cc:44 LoadLibrary libdemo_component.so
I0406 23:16:19.192752 31550 shared_library.cc:12 SharedLibrary() libdemo_component.so
I0406 23:16:19.192759 31550 shared_library.cc:24 Load libdemo_component.so
I0406 23:16:19.193063 31550 object_factory.h:36 ObjectFactory Instance is initializing
ObjectFactory() ObjectFactory_1680774079193100
I0406 23:16:19.193133 31550 object_factory.h:47 Register DemoComponent
I0406 23:16:19.193186 31550 object_factory.h:50 _creatorMap size: 1
I0406 23:16:19.193351 31550 object_factory.h:60 CreateObject DemoComponent _creatorMap size: 1
I0406 23:16:19.193377 31550 component_base.cc:25 ComponentBase()
I0406 23:16:19.193392 31550 demo_component.h:29 DemoComponent()
I0406 23:16:19.193405 31550 demo_component.cc:25 DemoComponent Init()
I0406 23:16:19.193413 31550 demo_component.h:33 ~DemoComponent()
I0406 23:16:19.193423 31550 component_base.cc:46 ~ComponentBase()
I0406 23:16:19.193445 31550 class_loader.cc:35 ~ClassLoader()
I0406 23:16:19.193454 31550 class_loader.cc:85 Release All Library
I0406 23:16:19.193462 31550 class_loader.cc:75 UnLoading Library libdemo_component.so
I0406 23:16:19.193472 31550 shared_library.cc:62 UnLoad
I0406 23:16:19.193552 31550 shared_library.cc:18 ~SharedLibrary()
三、总结
好了,截至到现在,我们已经基本实现了一个动态创建对象的classloader封装雏形。但也可以看到他还很不完善,比如不支持构造函数传参、不支持并行加载/对象创建等。如果你对此感兴趣请给我留言,我们一起来继续完善它。
yan 23.4.5
参考: