Автоматическое создание оболочек для классов, загружаемых с помощью dlopen () - PullRequest
2 голосов
/ 08 июля 2019

Я пишу прокси-класс, который загружает совместно используемую библиотеку с dlopen() и перенаправляет ее функции-члены соответствующим членам экземпляра прокси-класса внутри загруженного общего объекта за кулисами.

Например, общий объект имеет класс Person:

class Person
{
    ...
    void setName(std::string name);
};

Я добавил файл-обертку, который содержит заголовок person.h и определяет следующий символ:

extern "C" {
    void Person_setName(void* instancePointer, std::string name);
}

Этот вызов просто перенаправляет объект person, являющийся первым аргументом, extern "C", чтобы избежать проблемы искажения всего имени. На стороне клиента я написал класс Person с теми же членами, которые содержат указатель на упакованный класс и перенаправляют все вызовы.

Теперь возникает вопрос:

  1. Есть ли лучший способ создания экземпляра и использования класса из загруженного общего объекта? Я нашел какое-то другое решение, которое может обойтись без оболочек, но оно не рекомендуется для Usenet и сильно зависит от GCC и его версии, а также от неопределенного поведения, поэтому я решил отказаться от этого пути.
  2. Есть ли способ автоматизировать создание этих оболочек? Все они просто добавляют первый аргумент, который является указателем на экземпляр каждого метода, и пересылают его в реальный объект. Для этого должна быть какая-то магия шаблонов, нет? В настоящее время я использую некоторые макросы для этой работы, но меня может заинтересовать шаблонное решение.
  3. Может быть, есть какой-нибудь инструмент, который может сделать это автоматически, например, SWIG ? Насколько я видел, SWIG просто для экспорта интерфейса C ++ в языки высокого уровня.

Ответы [ 2 ]

2 голосов
/ 10 июля 2019

На исходный вопрос был дан ответ, но в комментариях содержался этот вопрос, который, я думаю, тоже нуждается в ответе.

как клиентская программа, использующая dlopen(), откроет общий объектнайти правильный символ, если он вызывает setName в таком возвращаемом экземпляре?

Вы создаете методы virtual.Вот пример, в котором создается libfoobar.so.Он предоставляет одну фабричную функцию (make_Foo) для создания Foo.Bar может быть создан из экземпляра Foo.Все методы могут быть использованы через указатели экземпляра.Я смешал, используя необработанные указатели и unique_ptr s, чтобы показать некоторые опции.

Сначала foobar.hpp и foobar.cpp, которые будут помещены в libfoobar.so:

foobar.hpp

#pragma once

#include <string>
#include <memory>

class Bar;

class Foo {
public:
    Foo();
    virtual ~Foo();

    virtual void set_name(const std::string& name);
    virtual std::string const& get_name() const;

    virtual Bar* get_Bar() const;

private:
    std::string m_name;
};

class Bar {
public:
    Bar(const Foo&);
    virtual ~Bar();

    virtual std::string const& get_value() const;
private:
    std::string m_value;
};

// a Foo factory
extern "C" {
std::unique_ptr<Foo> make_Foo();
}

foobar.cpp

#include "foobar.hpp"

// You can also use the library constructor and destructor
// void __attribute__((constructor)) init(void) {}
// void __attribute__((destructor)) finalize(void) {}

// Foo - impl

Foo::Foo() : m_name{} {}

Foo::~Foo() {}

void Foo::set_name(const std::string& name) {
    m_name = name;
}

std::string const& Foo::get_name() const {
    return m_name;
}

Bar* Foo::get_Bar() const {
    return new Bar(*this);
}

// Bar - impl

Bar::Bar(const Foo& f) :  m_value(f.get_name()) {}
Bar::~Bar() {}

std::string const& Bar::get_value() const { return m_value; }

// a factory function that can be loaded with dlsym()
extern "C" {
std::unique_ptr<Foo> make_Foo() {
    return std::make_unique<Foo>();
}
}

Затем универсальный помощник динамической библиотеки:

dynlib.hpp

#pragma once

#include <dlfcn.h> // dlload, dlsym, dlclose
#include <stdexcept>

using dynlib_error = std::runtime_error;

class dynlib {
public:
    dynlib(const char* filename);
    dynlib(const dynlib&) = delete;
    dynlib(dynlib&&);
    dynlib& operator=(const dynlib&) = delete;
    dynlib& operator=(dynlib&&);
    virtual ~dynlib();

protected:
    template<typename T>
    T load(const char* symbol) const {
        static_cast<void>(dlerror()); // clear errors
        return reinterpret_cast<T>(dlsym(handle, symbol));
    }

private:
    void* handle;
};

dynlib.cpp

#include "dynlib.hpp"
#include <utility>

dynlib::dynlib(const char* filename) : handle(dlopen(filename, RTLD_NOW | RTLD_LOCAL)) {
    if(handle == nullptr) throw dynlib_error(std::string(dlerror()));
}

dynlib::dynlib(dynlib&& o) : handle(std::exchange(o.handle, nullptr)) {}

dynlib& dynlib::operator=(dynlib&& o) {
    if(handle) dlclose(handle);
    handle = std::exchange(o.handle, nullptr);
    return *this;
}

dynlib::~dynlib() {
    if(handle) dlclose(handle);
}

и потомок dynlib для загрузки libfoobar.so:

foobarloader.hpp

#pragma once

#include "foobar.hpp"
#include "dynlib.hpp"

#include <memory>

class foobarloader : public dynlib {
public:
    using foo_t = std::unique_ptr<Foo> (*)();
    const foo_t make_Foo; // a factory function to load
                          // add more if needed

    foobarloader();
};

foobarloader.cpp

#include "foobarloader.hpp"

foobarloader::foobarloader() :
    dynlib("./libfoobar.so"),
    make_Foo(load<foo_t>("make_Foo")) // load function
{
    if(make_Foo == NULL) throw dynlib_error(std::string(dlerror()));
}

И, наконец, приложение, которое никак не связано с libfoobar.so:

test_app.cpp

#include "foobarloader.hpp"

#include <iostream>
#include <stdexcept>
#include <utility>
#include <memory>

int main() {
    try {
        foobarloader fbl;

        auto f = fbl.make_Foo(); // std::unique_ptr<Foo> example
        f->set_name("Howdy");
        std::cout << "Foo name: " << f->get_name() << "\n";

        Bar* b = f->get_Bar();   // raw Bar* example
        std::cout << "Bar value: " << b->get_value() << "\n";
        delete b;

    } catch(const std::exception& ex) {
        std::clog << "Exception: " << ex.what() << "\n";
        return 1;
    }
}

building

Если вы используете clang++, добавьте -Wno-return-type-c-linkage для подавления предупреждений о заводском методе.Я использовал -DNDEBUG -std=c++14 -O3 -Wall -Wextra -Wshadow -Weffc++ -pedantic -pedantic-errors, но для краткости исключил его ниже.

g++ -fPIC -c -o foobar.o foobar.cpp
g++ -shared -o libfoobar.so foobar.o

g++ -c -o dynlib.o dynlib.cpp
g++ -c -o foobarloader.o foobarloader.cpp
g++ -c -o test_app.o test_app.cpp

g++ -rdynamic -o test_app test_app.o foobarloader.o dynlib.o -ldl

Нет libfoobar.so связь:

% ldd test_app
    linux-vdso.so.1 (0x00007ffcee58c000)
    libdl.so.2 => /lib64/libdl.so.2 (0x00007fb264cb3000)
    libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007fb264aba000)
    libm.so.6 => /lib64/libm.so.6 (0x00007fb264974000)
    libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fb26495a000)
    libc.so.6 => /lib64/libc.so.6 (0x00007fb264794000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fb264cfd000)

Но функции-члены класса работают, как и ожидалось:

% ./test_app
Foo name: Howdy
Bar value: Howdy
2 голосов
/ 08 июля 2019

есть ли лучший способ создания экземпляра и использования класса из загруженного общего объекта?

Если вы хотите быть в безопасности и поддерживать какой-либо общий объект (т. Е. Скомпилированный любым компилятором / флагами и т. Д.), То нет: вам нужно пройти через C ABI.

Помните, что вы не должны использовать объекты C ++ в интерфейсе, например, как std::string вы проходите в Person_setName.

Есть ли способ автоматизировать создание этих оболочек? Для этого должна быть какая-то магия шаблонов, нет? В настоящее время я использую некоторые макросы для этой работы, но меня может заинтересовать шаблонное решение.

Нет, вы не можете создавать функции-члены на лету (у нас пока нет отражения, метаклассов и подобных функций времени компиляции).

Все они просто добавляют первый аргумент, который является указателем на экземпляр для каждого метода, и пересылают на реальную вещь.

Вы можете, конечно, создать шаблон с переменным числом аргументов для данной функции extern "C", но на самом деле вы не получаете от этого ничего полезного по сравнению с простым вызовом функций Си. Другими словами, не делайте этого: либо создайте правильный класс C ++, либо предоставьте пользователям возможность вызывать функции C.

Может быть, есть какой-то инструмент, который может делать такие вещи автоматически, например, SWIG? Насколько я видел, SWIG предназначен только для экспорта интерфейса C ++ в языки высокого уровня.

В прошлом я использовал поддержку инструментов Clang для выполнения подобных задач в качестве шага перед сборкой, так что это действительно возможно!

...