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

时间:2019-07-08 10:15:24

标签: c++ templates shared-libraries dlopen

我正在编写一个代理类,该代理类使用dlopen()加载共享库,并将其成员函数转发到幕后加载的共享对象内部的代理类实例的相应成员。

例如,共享库具有一个Person类:

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

我添加了一个包含person.h标头的包装器文件,并定义了以下符号:

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

此调用仅转发给作为第一个参数extern "C"的person对象,以避免整个名称更改问题。在客户端,我编写了一个Person类,该类具有相同的成员,该成员持有指向包装好的类的指针并转发所有调用。

现在出现一些问题:

  1. 是否有更好的方法从加载的共享对象实例化和使用类?我发现了其他一些无需包装即可使用的解决方案,但是不鼓励在Usenet上使用,并且高度依赖GCC及其版本和未定义的行为,因此我决定采用这种方法。
  2. 有没有一种方法可以自动创建这些包装?它们都只是添加了第一个参数,该参数是指向每个方法的实例并指向真实事物的指针。为此必须有一些模板魔术,不是吗?目前,我正在使用一些宏来完成这项工作,但我会对模板解决方案感兴趣。
  3. 也许有些工具可以自动执行此类操作,例如 SWIG ?据我所知, SWIG 仅用于将C ++接口导出为高级语言。

2 个答案:

答案 0 :(得分:2)

  

是否有更好的方法从加载的共享对象实例化和使用类?

如果您想安全并支持任何共享库(即通过任何编译器/标志等进行编译),则否:您必须通过C ABI。

请记住,您也不应该在接口中使用C ++对象,例如就像您传入std::string的{​​{1}}。

  

有没有一种方法可以自动创建这些包装器?为此必须有一些模板魔术,不是吗?目前,我正在使用一些宏来完成这项工作,但我会对模板解决方案感兴趣。

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

  

它们都只是添加了第一个参数,该参数是指向每个方法的实例并转发到真实对象的指针。

您可以创建一个可变参数模板,该模板将参数转发给给定的Person_setName函数,但是与简单地调用C函数相比,您并没有从中得到任何有用的东西。换句话说,不要这样做:要么创建一个适当的C ++类,要么让用户调用C函数。

  

也许有像SWIG这样的工具可以自动执行此操作?据我所知,SWIG仅用于将C ++接口导出为高级语言。

我过去曾使用Clang's tooling support来执行类似预构建步骤的任务,所以确实有可能!

答案 1 :(得分:2)

最初的问题已经回答,但是我认为评论中也有这个问题。

  

使用dlopen()打开共享的客户端程序将如何   对象在返回时调用setName会找到正确的符号   实例?

您将方法设为virtual。这是一个创建libfoobar.so的示例。它提供了一个工厂功能(make_Foo)来创建Foo。可以从Bar实例创建一个Foo。可以通过实例指针使用所有方法。我混合使用了原始指针和unique_ptr来显示一些选项。

首先,将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)

但是类成员函数可以按预期工作:

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