Динамическое связывание разделяемой библиотеки из кода, заключенного в pybind11 - PullRequest
6 голосов
/ 22 марта 2020

Я пытаюсь добавить python привязок к научному коду C ++ среднего размера c (несколько десятков тысяч LOC). Мне удалось заставить его работать без особых проблем, но теперь я столкнулся с проблемой, которую не могу решить сам. Код организован следующим образом:

  • Все классы и структуры данных скомпилированы в библиотеке libcommon.a
  • Исполняемые файлы создаются путем связывания этой библиотеки
  • pybind11 используется для создания core.so python модуля

Привязки для "основных" частей работают нормально. Действительно, симуляции, запускаемые из автономного кода или из python, дают точно такие же результаты.

Однако этот код также поддерживает подключаемую систему, которая может загружать совместно используемые библиотеки во время выполнения. Эти общие библиотеки содержат классы, которые наследуются от интерфейсов, определенных в основном коде. Оказывается, что если я пытаюсь связать эти общие библиотеки из python, я получаю печально известные ошибки «неопределенный символ». Я проверил, что эти символы находятся в модуле core.so (используя nm -D). Фактически, симуляции, которые выполняют динамическое связывание c с автономным кодом, работают отлично (в одной папке и с одним и тем же вводом). Каким-то образом разделяемая библиотека не может найти правильные символы при вызове через python, но у нее нет проблем при загрузке автономным кодом. Я использую CMake для построения системы.

Далее следует MCE. Скопируйте каждый файл в папку, скопируйте (или свяжите) папку pybind11 в том же месте и используйте следующие команды:

mkdir build
cd build
cmake ..
make

, которые сгенерируют двоичный файл standalone и модуль python , Исполняемый файл standalone выдаст правильный вывод. Напротив, использование следующих команд в python3 (что, по крайней мере, в моей голове должно быть эквивалентно) приводит к ошибке:

import core
b = core.load_plugin()

main. cpp

#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;
}

Base.h

#ifndef BASE
#define BASE

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

    virtual void foo();
};

#endif

Base. cpp

#include "Base.h"

#include <iostream>

Base::Base() {}

Base::~Base() {}

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

plugin_loader.h

#ifndef LOADER
#define LOADER

#include "Base.h"

Base *load_plugin();

#endif

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();
}

Derived.h

#include "Base.h"

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

extern "C" Base *make() {
    return new Derived();
}

Производные. cpp

#include "Derived.h"

#include <iostream>

Derived::Derived() {}

Derived::~Derived() {}

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

привязок. 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);
}

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)

1 Ответ

2 голосов
/ 25 марта 2020

Вы правы, символы из импортированной библиотеки не видны, поскольку core загружен без установленного флага RTLD_GLOBAL. Это можно исправить с помощью пары дополнительных строк на стороне python:

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

import core
b = core.load_plugin()

С sys.setdlopenflags() do c:

К обмениваться символами между модулями расширения, вызывать как sys.setdlopenflags(os.RTLD_GLOBAL). Имена Symboli c для значений флагов можно найти в модуле os (константы RTLD_xxx, например, os.RTLD_LAZY).

...