概述
类加载器的作用是动态的加载动态库,然后实例化对象。我们先来解释下
- 首先apollo中的各个module都会编译为一个动态库,拿planning模块来举例子,在"planning/dag/planning.dag"中,会加载动态库:
module_config {
module_library : "/apollo/bazel-bin/modules/planning/libplanning_component.so"
- 也就是说,apollo中的模块都会通过类加载器以动态库的方式加载,然后实例化,最后调用Initialize方法初始化。
因此,我们只需要搞清楚如下三个问题,就知道了类加载器的原理。
- cyber如何加载apollo模块?
- 如何实例化模块?
- 如何初始化模块?
poco 动态加载简介
类加载器的实现在"cyber/class_loader"目录中,通过"Poco/SharedLibrary.h"库来实现动态库的加载,
现代操作系统几乎都提供了共享库动态加载的功能。linux提供了dlopen函数来加载共享库。十分粗略地讲,共享库加载就是加载共享库文件,根据函数名符号找到函数的地址,然后调用函数。poco库是一个跨平台的通用C++库,封装了不同平台上的共享库加载功能。关于Poco动态库的加载可以参考
一个简单的类动态加载案例
// Base.h
#pragma once
#include <iostream>
// 基类接口
struct Base {
virtual ~Base() = default;
virtual void print_name() = 0;
};
// factory.h
#include "Base.h"
#include <string>
#include <map>
// 创造函数的类型
using object_creator_func = Base* (*)();
// 类名到创造函数的map
using CreatorList = std::map<std::string, object_creator_func>;
// 利用类名来创建其对象
Base* create_object(const std::string& derived_class_name);
// 获得一个静态变量
CreatorList& get_creator_list();
// 将类的创造函数加入一个静态变量
void add_creator_list(const std::string& derived_class_name, object_creator_func func);
// factory.cpp
#include "factory.h"
Base* create_object(const std::string& derived_class_name)
{
const auto& creator_list = get_creator_list();
return creator_list.at(derived_class_name)();
}
CreatorList& get_creator_list(){
static CreatorList creator_list;
return creator_list;
}
void add_creator_list(const std::string& derived_class_name, object_creator_func func){
auto& creator_lists = get_creator_list();
creator_lists.emplace(derived_class_name, func);
}
// Derived.h
#pragma once
#include <iostream>
#include "Base.h"
// 子类声明
struct Derived: public Base {
void print_name() override;
};
// Derived.cpp
#include "Derived.h"
#include "Base.h"
#include "factory.h"
void Derived::print_name(){
std::cout << "Derived" << std::endl;
}
// 帮助类,利用动态库加载时会初始化静态变量的特性来将基类的creator
// 注册入一个全局函数表。
struct ClassRegistry {
ClassRegistry() {
get_creator_list().emplace("Derived", []() -> Base* {
return new Derived;
});
}
};
// 帮族类的静态变量。
static ClassRegistry class_registry_derived{};
// main.cpp
#include "Poco/SharedLibrary.h"
#include "Base.h"
#include "factory.h"
using Poco::SharedLibrary;
typedef void (*HelloFunc)(); // function pointer type
int main(int argc, char **argv)
{
std::string path("/home/tt/workspace/my_test/build/libderived_plugin.so");
SharedLibrary library(path); // will also load the library
std::cout << "Finished loading shared library." << std::endl;
Base* object = create_object("Derived");
object->print_name();
library.unload();
return 0;
}
// CMakeLists.txt
cmake_minimum_required(VERSION 3.1)
project(test_poco)
add_library(factory SHARED factory.cpp)
add_library(derived_plugin SHARED Derived.cpp)
target_link_libraries(derived_plugin factory)
find_package(Poco REQUIRED COMPONENTS Foundation)
add_executable(main main.cpp)
target_link_libraries(main Poco::Foundation factory)
目录结构
cyber的动态加载库使用的技术和上面例子类似,不过它也提供更多功能,包括共享库生命周期管理,这个库的复杂度主要还是来自于共享库的生命周期管理。
其目录如下
├── BUILD // 编译文件
├── class_loader.cc // 类加载器
├── class_loader.h
├── class_loader_manager.cc // 类加载器管理
├── class_loader_manager.h
├── class_loader_register_macro.h // 类加载器注册宏定义
└── utility
├── class_factory.cc // 类工厂
├── class_factory.h
├── class_loader_utility.cc // 类加载器工具类
└── class_loader_utility.h
C++类的功能解释
- SharedLibrary poco对象,是共享库的实例。可以拥有多个ClassLoader。每个共享库最多只能有一个对象。SharedLibrary可以被unload,当它的所有ClassLoader都可以被unload。
- ClassFactory,用来创建子类的对象,和每一个子类一一对应。可以属于多个ClassLoader。它可以被unload,当所有ClassLoader被unload。
- ClassLoader,每个ClassLoader都属于一个SharedLibrary,记录了共享库中所有的类型。一个ClassLoader可以拥有多个ClassFactory。ClassLoaderManager通过该类来创建对象,它记录了所有由它创建的子类的数目,只有当子类数目为零时,说明该ClassLoader可以被unload。
- ClassLoaderManager,用来管理ClassLoader,通过它来加载ClassLoader和管理。这是该库的对外接口。线程安全。
类图
这个库的类图其实没有多大意义,因为很多类对象的关系由一些静态全局变量管理,不过为了完整性,还是将类图画出。
如何书写可加载的动态类
子类实现基类的各种接口,然后在子类cpp文件中使用宏CLASS_LOADER_REGISTER_CLASS(DerivedClass, BaseClass);来将子类注册入一个全局的静态变量中,之后我们可以使用类名字符串直接创建子类的实例。cyber/class_loader/class_loader_test.cc提供了一个样例。
类加载器(ClassLoader)
我们先从"class_loader.h"开始看起,
调用入口
方法
首先我们分析下"class_loader"实现的具体方法:
class ClassLoader {
public:
explicit ClassLoader(const std::string& library_path);
virtual ~ClassLoader();
// 库是否已经加载
bool IsLibraryLoaded();
// 加载库
bool LoadLibrary();
// 卸载库
int UnloadLibrary();
// 获取库的路径
const std::string GetLibraryPath() const;
// 获取类名称
template <typename Base>
std::vector<std::string> GetValidClassNames();
// 实例化类对象
template <typename Base>
std::shared_ptr<Base> CreateClassObj(const std::string& class_name);
// 类是否有效
template <typename Base>
bool IsClassValid(const std::string& class_name);
private:
// 当类删除
template <typename Base>
void OnClassObjDeleter(Base* obj);
private:
// 类的路径
std::string library_path_;
// 类加载引用次数
int loadlib_ref_count_;
// 类加载引用次数锁
std::mutex loadlib_ref_count_mutex_;
// 类引用次数
int classobj_ref_count_;
// 类引用次数锁
std::mutex classobj_ref_count_mutex_;
};
可以看到类加载器主要是提供了加载类,卸载类和实例化类的接口。实际上加载类和卸载类的实现都比较简单,都是调用"utility"类中的实现,我们暂时先放一边,先看下实例化对象的实现。
template <typename Base>
std::shared_ptr<Base> ClassLoader::CreateClassObj(
const std::string& class_name) {
// 加载库
if (!IsLibraryLoaded()) {
LoadLibrary();
}
// 根据类名称创建对象
Base* class_object = utility::CreateClassObj<Base>(class_name, this);
// 类引用计数加1
std::lock_guard<std::mutex> lck(classobj_ref_count_mutex_);
classobj_ref_count_ = classobj_ref_count_ + 1;
// 指定类的析构函数
std::shared_ptr<Base> classObjSharePtr(
class_object, std::bind(&ClassLoader::OnClassObjDeleter<Base>, this,
std::placeholders::_1));
return classObjSharePtr;
}
可以看到创建类的时候,类引用计数加1,并且绑定类的析构函数(OnClassObjDeleter),删除对象的时候让类引用计数减1。
template <typename Base>
void ClassLoader::OnClassObjDeleter(Base* obj) {
if (nullptr == obj) {
return;
}
std::lock_guard<std::mutex> lck(classobj_ref_count_mutex_);
delete obj;
--classobj_ref_count_;
}
我们先简单的分析下ClassLoaderManager,最后再分析utility。
ClassLoaderManager
类加载器管理实际上是管理不同的classloader,而不同的libpath对应不同的classloader。
主要数据成员
ClassLoaderManager主要的数据结构即libpath_loader_map_:
其中"libpath_loader_map_"为map结构,在"LoadLibrary"的时候赋值,key为library_path,而value为ClassLoader.
而ClassLoader可以根据so路径创建出一个对应的类
下面我们具体分析下ClassLoaderManager::LoadLibrary
ClassLoaderManager::LoadLibrary
调用入口
它是动态库的加载入口。其调用入口是ModuleController::LoadModule(const DagConfig& dag_config) ,其作用是根据配置文件将所有的组件初始化完毕。这里需要用到:
具体分析
这里我们详细分析一下LoadLibrary
参数:
- .so的路径
返回值:
- 这个路径是不是存在
干了啥?
- 如果路径存在,那么根据路径创建出一个类并存储到libpath_loader_map_中
而class_loader的构造函数除了会初始化一些成员变量外,最后它调用了工具类LoadLibrary(const std::string& library_path, ClassLoader* loader)
bool LoadLibrary(const std::string& library_path, ClassLoader* loader) {
...
SharedLibraryPtr shared_library = nullptr;
static std::recursive_mutex loader_mutex;
{
std::lock_guard<std::recursive_mutex> lck(loader_mutex);
// 设置动态库加载前的上下文信息,后面会用到
SetCurActiveClassLoader(loader);
SetCurLoadingLibraryName(library_path);
// 加载动态库
shared_library = SharedLibraryPtr(new SharedLibrary(library_path));
SetCurLoadingLibraryName("");
SetCurActiveClassLoader(nullptr);
}
...
}
SharedLibrary对象构造的时候通过SharedLibrary::Load(const std::string& path, int flags)调用dlopen加载了libcommon_component_example.so。
在CLASS_LOADER_REGISTER_CLASS_INTERNAL宏展开时定义了全局静态变量g_register_class_##UniqueID,当libcommon_component_example.so被加载的时候(dlopen返回前),它是要被初始化的,也就是它的构造函数会被调用。
我们看下这个构造函数主要做了些什么。
// 模板参数Derived的值为CommonComponentSample
// 模板参数Base的值为apollo::cyber::ComponentBase,是CommonComponentSample的间接基类
template <typename Derived, typename Base>
void RegisterClass(const std::string& class_name,
const std::string& base_class_name) {
//创建CommonComponentSample的类工厂
utility::AbstractClassFactory<Base>* new_class_factory_obj =
new utility::ClassFactory<Derived, Base>(class_name, base_class_name);
/*
设置类工厂的上下文信息:
在so加载前,LoadLibrary中已通过SetCurActiveClassLoader和SetCurLoadingLibraryName设置了上下文信息,
下面通过GetCurActiveClassLoader和GetCurLoadingLibraryName获取相关信息,
然后和对象new_class_factory_obj做绑定。
*/
new_class_factory_obj->AddOwnedClassLoader(GetCurActiveClassLoader());
new_class_factory_obj->SetRelativeLibraryPath(GetCurLoadingLibraryName());
GetClassFactoryMapMapMutex().lock();
ClassClassFactoryMap& factory_map =
GetClassFactoryMapByBaseClass(typeid(Base).name());
/*
下面的代码将类工厂new_class_factory_obj加到factory_map中,
map的key为类的名字CommonComponentSample, value为工厂对象new_class_factory_obj。
factory_map又维护在另一个map中,该map作为静态变量由函数GetClassFactoryMapMap维护,
它的key是Base的类名,即上面的typeid(Base).name()。
*/
factory_map[class_name] = new_class_factory_obj;
GetClassFactoryMapMapMutex().unlock();
}
从上面的代码可以看出,如果要获取CommonComponentSample对应的类工厂,
- 首先要以CommonComponentSample的基类apollo::cyber::ComponentBase的名字作为key从函数GetClassFactoryMapByBaseClass获取一个map
- 然后以类名CommonComponentSample作为key从这个map中获取对应的类工厂。
上面提到的两个map是嵌套的,定义如下:
//map的key为字符串CommonComponentSample,value为类工厂对象
using ClassClassFactoryMap =
std::map<std::string, utility::AbstractClassFactoryBase*>;
//map的key为CommonComponentSample的基类apollo::cyber::ComponentBase的名字,value为上面的map
using BaseToClassFactoryMapMap = std::map<std::string, ClassClassFactoryMap>;
当函数LoadLibrary返回时,CommonComponentSample对应的类工厂已经维护在上述的二维map结构中。至此,动态库已加载完毕,并形成了class_name、ClassLoader和ClassFactory的绑定关系。
ClassLoaderManager::CreateClassObj
调用入口
ModuleController::LoadModule中,在LoadLibrary之后,会初始化各个component
// 2. 根据配置信息初始化module中各个component实例
for (auto& component : module_config.components()) {
const std::string& class_name = component.class_name();
// class_name即CommonComponentSample,该component创建完成后,放在component_list_中统一维护
std::shared_ptr<ComponentBase> base =
class_loader_manager_.CreateClassObj<ComponentBase>(class_name);
if (base == nullptr || !base->Initialize(component.config())) {
return false;
}
component_list_.emplace_back(std::move(base));
}
函数ClassLoaderManager::CreateClassObj首先根据类名找到对应的ClassLoader对象,然后使用ClassLoader对象创建component实例
template <typename Base>
std::shared_ptr<Base> ClassLoaderManager::CreateClassObj(
const std::string& class_name) {
//获取所有的ClassLoader对象
std::vector<ClassLoader*> class_loaders = GetAllValidClassLoaders();
//根据类名查找对应的ClassLoader对象,然后用该对象创建component实例
for (auto class_loader : class_loaders) {
if (class_loader->IsClassValid<Base>(class_name)) {
return (class_loader->CreateClassObj<Base>(class_name));
}
}
AERROR << "Invalid class name: " << class_name;
return std::shared_ptr<Base>();
}
接下来我们看下ClassLoader是如何创建component实例的。
template <typename Base>
std::shared_ptr<Base> ClassLoader::CreateClassObj(
const std::string& class_name) {
...
Base* class_object = utility::CreateClassObj<Base>(class_name, this);
...
}
utility::CreateClassObj中首先根据类名查找对应的类工厂,然后由该工厂创建component实例对象。
template <typename Base>
Base* CreateClassObj(const std::string& class_name, ClassLoader* loader) {
GetClassFactoryMapMapMutex().lock();
//根据基类名称查找由<class_name, utility::AbstractClassFactoryBase*>组成的map
ClassClassFactoryMap& factoryMap =
GetClassFactoryMapByBaseClass(typeid(Base).name());
//查找class_name对应的类工厂,即CommonComponentSample对应的类工厂
AbstractClassFactory<Base>* factory = nullptr;
if (factoryMap.find(class_name) != factoryMap.end()) {
factory = dynamic_cast<utility::AbstractClassFactory<Base>*>(
factoryMap[class_name]);
}
GetClassFactoryMapMapMutex().unlock();
//由类工厂创建CommonComponentSample实例对象
Base* classobj = nullptr;
if (factory && factory->IsOwnedBy(loader)) {
classobj = factory->CreateObj();
}
return classobj;
}
补充说明:类工厂的定义如下,在前面提到的RegisterClass函数中实例化,模板参数ClassObject的值为CommonComponentSample。所以,下面的CreateObj函数中直接new了一个CommonComponentSample对象。
template <typename ClassObject, typename Base>
class ClassFactory : public AbstractClassFactory<Base> {
public:
ClassFactory(const std::string& class_name,
const std::string& base_class_name)
: AbstractClassFactory<Base>(class_name, base_class_name) {}
Base* CreateObj() const { return new ClassObject; }
};
至此,CommonComponentSample已完成实例化。