从 pybind11 包装的代码动态链接共享库

lr1*_*985 6 c++ cmake shared-libraries dynamic-linking pybind11

我正在尝试将 python 绑定添加到中等大小的 C++ 科学代码(数万个 LOC)。我设法让它在没有太多问题的情况下工作,但我现在遇到了一个我无法解决的问题。代码组织如下:

  • 所有的类和数据结构都编译在一个库中 libcommon.a
  • 可执行文件是通过链接这个库创建的
  • pybind11 用于创建core.sopython模块

“主要”部分的绑定工作正常。事实上,从独立代码或从 python 启动的模拟给出了完全相同的结果。

但是,该代码还支持类似插件的系统,该系统可以在运行时加载共享库。这些共享库包含从主代码中定义的接口继承的类。事实证明,如果我尝试从 python 链接这些共享库,我会收到臭名昭著的“未定义符号”错误。我已经检查过这些符号是否在core.so模块中(使用nm -D)。事实上,使用独立代码执行动态链接的模拟工作得很好(在同一文件夹中并使用相同的输入)。不知何故,共享库在通过 python 调用时找不到正确的符号,但在由独立代码加载时没有问题。我正在使用 CMake 来构建系统。

接下来是 MCE。复制文件夹中的每个文件,pybind11在同一位置复制(或链接)文件夹并使用以下命令:

mkdir build
cd build
cmake ..
make
Run Code Online (Sandbox Code Playgroud)

这将生成一个standalone二进制文件和一个 python 模块。该standalone可执行文件将产生正确的输出。相比之下,在 python3 中使用以下命令(至少在我看来,应该是等效的)会产生错误:

mkdir build
cd build
cmake ..
make
Run Code Online (Sandbox Code Playgroud)

主程序

#include "Base.h"
#include "plugin_loader.h"

#include <iostream>

int main() {
    Base *d = load_plugin();
    if(d == NULL) {
        std::cerr << "No lib found" << std::endl;
        return 1;
    }
    d->foo();

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

基数

#ifndef BASE
#define BASE

struct Base {
    Base();
    virtual ~Base();

    virtual void foo();
};

#endif
Run Code Online (Sandbox Code Playgroud)

基础文件

#include "Base.h"

#include <iostream>

Base::Base() {}

Base::~Base() {}

void Base::foo() {
    std::cout << "Hey, it's Base!" << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

plugin_loader.h

#ifndef LOADER
#define LOADER

#include "Base.h"

Base *load_plugin();

#endif
Run Code Online (Sandbox Code Playgroud)

plugin_loader.cpp

#include "plugin_loader.h"

#include <dlfcn.h>
#include <iostream>

typedef Base* make_base();

Base *load_plugin() {
    void *handle = dlopen("./Derived.so", RTLD_LAZY | RTLD_GLOBAL);
    const char *dl_error = dlerror();
    if(dl_error != nullptr) {
        std::cerr << "Caught an error while opening shared library: " << dl_error << std::endl;
        return NULL;
    }
    make_base *entry = (make_base *) dlsym(handle, "make");

    return (Base *) entry();
}
Run Code Online (Sandbox Code Playgroud)

派生.h

#include "Base.h"

struct Derived : public Base {
    Derived();
    virtual ~Derived();
    void foo() override;
};

extern "C" Base *make() {
    return new Derived();
}
Run Code Online (Sandbox Code Playgroud)

派生文件

#include "Derived.h"

#include <iostream>

Derived::Derived() {}

Derived::~Derived() {}

void Derived::foo() {
    std::cout << "Hey, it's Derived!" << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

绑定.cpp

#include <pybind11/pybind11.h>

#include "Base.h"
#include "plugin_loader.h"

PYBIND11_MODULE(core, m) {
        pybind11::class_<Base, std::shared_ptr<Base>> base(m, "Base");

        base.def(pybind11::init<>());
        base.def("foo", &Base::foo);

        m.def("load_plugin", &load_plugin);
}
Run Code Online (Sandbox Code Playgroud)

CMakeLists.txt


PROJECT(foobar)

# compile the library
ADD_LIBRARY(common SHARED Base.cpp plugin_loader.cpp)
TARGET_LINK_LIBRARIES(common ${CMAKE_DL_LIBS})
SET_TARGET_PROPERTIES(common PROPERTIES POSITION_INDEPENDENT_CODE ON)

# compile the standalone code
ADD_EXECUTABLE(standalone main.cpp)
TARGET_LINK_LIBRARIES(standalone common)

# compile the "plugin"
SET(CMAKE_SHARED_LIBRARY_PREFIX "")
ADD_LIBRARY(Derived SHARED Derived.cpp)

# compile the bindings
ADD_SUBDIRECTORY(pybind11)
INCLUDE_DIRECTORIES( ${PROJECT_SOURCE_DIR}/pybind11/include )

FIND_PACKAGE( PythonLibs 3 REQUIRED )
INCLUDE_DIRECTORIES( ${PYTHON_INCLUDE_DIRS} )

ADD_LIBRARY(_oxpy_lib STATIC bindings.cpp)
TARGET_LINK_LIBRARIES(_oxpy_lib ${PYTHON_LIBRARIES} common)
SET_TARGET_PROPERTIES(_oxpy_lib PROPERTIES POSITION_INDEPENDENT_CODE ON)

pybind11_add_module(core SHARED bindings.cpp)
TARGET_LINK_LIBRARIES(core PRIVATE _oxpy_lib)

Run Code Online (Sandbox Code Playgroud)

Ser*_*gei 5

你是对的,导入库中的符号不​​可见,因为core加载时没有RTLD_GLOBAL设置标志。你可以在 python 端用几行额外的行来解决这个问题:

import sys, os
sys.setdlopenflags(os.RTLD_GLOBAL | os.RTLD_LAZY)

import core
b = core.load_plugin()
Run Code Online (Sandbox Code Playgroud)

sys.setdlopenflags() 文档

要在扩展模块之间共享符号,请调用 as sys.setdlopenflags(os.RTLD_GLOBAL)。标志值的符号名称可以在os模块中找到(RTLD_xxx常量,例如os.RTLD_LAZY)。