我正在编写一个代理类,该代理类使用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
类,该类具有相同的成员,该成员持有指向包装好的类的指针并转发所有调用。
现在出现一些问题:
答案 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.hpp
和foobar.cpp
放入libfoobar.so
:
#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();
}
#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>();
}
}
然后是通用动态库帮助器:
#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;
};
#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
:
#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();
};
#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
链接的应用程序:
#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