Automatically Creating Wrappers for Classes Loaded with dlopen()

佐手、 提交于 2019-12-08 01:52:29

问题


I'm writing a proxy class that loads a shared library with dlopen() and forwards its member functions to the appropriate members of the proxied class instance inside the loaded shared object behind the scenes.

For example, the shared object has a Person class:

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

I've added a wrapper file that includes the person.h header and defines the following symbol:

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

This call just forwards to the person object which is the first argument, extern "C" to avoid the whole name mangling issue. On the client side I've written a Person class with the same members that holds a pointer to the wrapped class and forwards all calls.

Now some question arise:

  1. is there a better way to instantiate and use a class from a loaded shared object? I've found some other solution that can live without the wrappers, but it's discouraged on Usenet and highly dependent on GCC and its version, and undefined behavior, so I decided against that route.
  2. is there a way to automate the creation of those wrappers? They are all just adding a first argument that is a pointer to an instance to each method and forward to the real thing. 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.
  3. 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.

回答1:


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

If you want to be safe and support any shared object (i.e. compiled by any compiler/flags etc.), then no: you have to go through the C ABI.

Remember you should not use C++ objects in the interface either, e.g. like the std::string you are passing in 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.

No, you cannot create member functions on the fly (we do not have reflection, metaclasses and similar compile-time features yet).

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

You can create a variadic template that forwards arguments to a given extern "C" function, of course, but you are not really getting anything useful from that compared to simply calling the C functions. In other words, don't do that: either create a proper C++ class or leave the users to call the C functions.

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.

I have used Clang's tooling support in the past to perform similar tasks as a pre-build step, so it is indeed possible!




回答2:


The original question has been answered, but there was this question in the comments that I think needs an answer too.

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?

You make the methods virtual. Here's an example in which libfoobar.so is created. It provides one factory function (make_Foo) to create a Foo. A Bar can be created from the Foo instance. All methods can be used via the instance pointers. I've mixed using raw pointers and unique_ptrs to show some options.

First, foobar.hpp and foobar.cpp that will be put into 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>();
}
}

Then a generic dynamic library helper:

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

And a dynlib descendant to load 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()));
}

And finally an application that is not linked with libfoobar.so in any way:

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

If you use clang++ add -Wno-return-type-c-linkage to suppress warnings about the factory method. I've used -DNDEBUG -std=c++14 -O3 -Wall -Wextra -Wshadow -Weffc++ -pedantic -pedantic-errors but excluded it below for brevity.

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

No libfoobar.so linkage:

% 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)

But class member functions work as expected:

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


来源:https://stackoverflow.com/questions/56932785/automatically-creating-wrappers-for-classes-loaded-with-dlopen

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!