为使用 dlopen() 加载的 类 自动创建包装器

Automatically Creating Wrappers for Classes Loaded with dlopen()

我正在编写一个代理 class,它使用 dlopen() 加载一个共享库,并将其成员函数转发给加载的共享 class 实例中的代理 class 的适当成员 object幕后花絮

例如共享object有一个Person class:

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

我添加了一个包装文件,其中包含 person.h header 并定义了以下符号:

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

这个调用只是转发给第一个参数 object 的人,extern "C" 以避免全名混淆问题。在客户端,我写了一个 Person class 和相同的成员,这些成员持有指向包装 class 的指针并转发所有调用。

现在出现了一些问题:

  1. 是否有更好的方法从加载的共享 object 中实例化和使用 class?我找到了一些其他的解决方案,可以在没有包装器的情况下使用,但在 Usenet 上不鼓励使用它,并且高度依赖于 GCC 及其版本,以及未定义的行为,所以我决定反对这条路线。
  2. 有没有办法自动创建这些包装器?他们都只是添加第一个参数,该参数是指向每个方法的实例的指针,并转发给真实的东西。必须有一些模板魔法,不是吗?目前我正在使用一些宏来完成这项工作,但我对模板解决方案感兴趣。
  3. 也许有一些工具可以自动完成这样的事情,比如 SWIG?据我所知,SWIG 仅用于将 C++ 接口导出到高级语言。

is there a better way to instantiate and use a class from a loaded shared object?

如果你想要安全并支持任何共享对象(即由任何 compiler/flags 等编译),那么不行:你必须通过 C ABI。

记住你也不应该在接口中使用 C++ 对象,例如就像你传递的 std::string Person_setName.

is there a way to automate the creation of those wrappers? There has to be some template magic for that, no? Currently I'm using some macros for the job but I'd be interested in a template solution.

不,您不能即时创建成员函数(我们还没有反射、元classes 和类似的compile-time 功能)。

They are all just adding a first argument that is a pointer to an instance to each method and forward to the real thing.

当然,您可以创建一个可变参数模板,将参数转发给给定的 extern "C" 函数,但是与简单地调用 C 函数相比,您实际上并没有从中获得任何有用的东西。换句话说,不要那样做:要么创建一个合适的 C++ class,要么让用户调用 C 函数。

Maybe there is some tool that can do such a thing automatically, like SWIG? As far as I've seen SWIG is just for exporting a C++ interface to high level languages.

我以前用Clang's tooling support来执行类似的任务作为pre-build步骤,所以确实可以!

原来的问题已经回答了,但是评论里有这个问题我觉得也需要回答

how will the client program that uses dlopen() to open the shared object find the correct symbol if it calls setName on such a returned instance?

你制定方法 virtual。这是创建 libfoobar.so 的示例。它提供了一个工厂函数 (make_Foo) 来创建 Foo。可以从 Foo 实例创建 Bar。所有方法都可以通过实例指针使用。我混合使用原始指针和 unique_ptrs 来显示一些选项。

首先,foobar.hppfoobar.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;
    }
}

建筑物

如果您使用 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)

但是 class 成员函数按预期工作:

% ./test_app
Foo name: Howdy
Bar value: Howdy