Apollo:cyber/class_loader类加载器

文章介绍了Apollo框架中如何通过类加载器动态加载模块,以动态库的形式实例化和初始化对象。类加载器基于Poco库实现,通过`Poco::SharedLibrary`加载动态库,并使用工厂模式注册和创建对象。文章详细阐述了加载库、实例化和初始化的流程,并给出了一个简单的类加载示例。
摘要由CSDN通过智能技术生成

概述

类加载器的作用是动态的加载动态库,然后实例化对象。我们先来解释下

  • 首先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已完成实例化。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
root@in_dev_docker:/apollo# bash scripts/msf_create_lossless_map.sh /apollo/hdmap/pcd_apollo/ 50 /apollo/hdmap/ /apollo/bazel-bin WARNING: Logging before InitGoogleLogging() is written to STDERR E0715 22:08:35.399576 6436 lossless_map_creator.cc:162] num_trials = 1 Pcd folders are as follows: /apollo/hdmap/pcd_apollo/ Resolution: 0.125 Dataset: /apollo/hdmap/pcd_apollo Dataset: /apollo/hdmap/pcd_apollo/ Loaded the map configuration from: /apollo/hdmap//lossless_map/config.xml. Saved the map configuration to: /apollo/hdmap//lossless_map/config.xml. Saved the map configuration to: /apollo/hdmap//lossless_map/config.xml. E0715 22:08:35.767315 6436 lossless_map_creator.cc:264] ieout_poses = 1706 Failed to find match for field 'intensity'. Failed to find match for field 'timestamp'. E0715 22:08:35.769896 6436 velodyne_utility.cc:46] Un-organized-point-cloud E0715 22:08:35.781770 6436 lossless_map_creator.cc:275] Loaded 245443D Points at Trial: 0 Frame: 0. F0715 22:08:35.781791 6436 base_map_node_index.cc:101] Check failed: false *** Check failure stack trace: *** scripts/msf_create_lossless_map.sh: line 11: 6436 Aborted (core dumped) $APOLLO_BIN_PREFIX/modules/localization/msf/local_tool/map_creation/lossless_map_creator --use_plane_inliers_only true --pcd_folders $1 --pose_files $2 --map_folder $IN_FOLDER --zone_id $ZONE_ID --coordinate_type UTM --map_resolution_type single root@in_dev_docker:/apollo# bash scripts/msf_create_lossless_map.sh /apollo/hdmap/pcd_apollo/ 50 /apollo/hdmap/
07-16
根据提供的信息,执行脚本 `scripts/msf_create_lossless_map.sh` 时出现了错误。具体的错误信息如: ``` E0715 22:08:35.399576 6436 lossless_map_creator.cc:162] num_trials = 1 Pcd folders are as follows: /apollo/hdmap/pcd_apollo/ Resolution: 0.125 Dataset: /apollo/hdmap/pcd_apollo Dataset: /apollo/hdmap/pcd_apollo/ Loaded the map configuration from: /apollo/hdmap//lossless_map/config.xml. Saved the map configuration to: /apollo/hdmap//lossless_map/config.xml. Saved the map configuration to: /apollo/hdmap//lossless_map/config.xml. E0715 22:08:35.767315 6436 lossless_map_creator.cc:264] ieout_poses = 1706 Failed to find match for field 'intensity'. Failed to find match for field 'timestamp'. E0715 22:08:35.769896 6436 velodyne_utility.cc:46] Un-organized-point-cloud E0715 22:08:35.781770 6436 lossless_map_creator.cc:275] Loaded 245443D Points at Trial: 0 Frame: 0. F0715 22:08:35.781791 6436 base_map_node_index.cc:101] Check failed: false *** Check failure stack trace: *** scripts/msf_create_lossless_map.sh: line 11: 6436 Aborted (core dumped) $APOLLO_BIN_PREFIX/modules/localization/msf/local_tool/map_creation/lossless_map_creator --use_plane_inliers_only true --pcd_folders $1 --pose_files $2 --map_folder $IN_FOLDER --zone_id $ZONE_ID --coordinate_type UTM --map_resolution_type single ``` 这段错误信息表明在执行脚本时发生了一个检查失败的情况。错误的具体位置在 `base_map_node_index.cc:101`。可能的原因包括: 1. 数据不匹配:脚本中使用的数据可能存在不匹配的情况,例如字段名或者数据格式不正确。 2. 数据文件缺失:脚本所需的某些数据文件可能不存在或者路径不正确。 3. 依赖问题:脚本所依赖的某些组件或库可能缺失或者版本不兼容。 请检查脚本 `scripts/msf_create_lossless_map.sh` 中的相关代码,确保数据文件和依赖项的正确性。如果问题仍然存在,您可以提供更多的上下文信息,以便我们能够更好地帮助您解决问题。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值