Re: [問題] dynamic shared library設計問題

作者: PkmX (阿貓)   2017-10-07 22:11:27
我覺得這個直接拿個例子來解釋如何在執行時載入 C++ shared library,
並建構和操作 DSO 內定義的 class 和 function:
==============================================================================
// foo.hpp
struct foo {
virtual ~foo() = 0;
virtual auto vfn() const -> int = 0;
auto fn() const -> int;
};
inline foo::~foo() = default;
template<typename... Ts>
using make_foo_fn = auto (*)(Ts...) -> foo*;
==============================================================================
// foo.cpp
#include "foo.hpp"
struct foo_impl : foo {
virtual ~foo_impl() = default;
virtual auto vfn() const -> int override { return fn(); }
};
extern "C" {
auto new_foo() -> foo* { return new foo_impl; }
}
==============================================================================
// main.cpp
#include <cassert>
#include <iostream>
#include <memory>
#include <dlfcn.h>
#include "foo.hpp"
// C++17 scopeguard utility
#include <type_traits>
template<typename F>
struct scopeguard : F {
scopeguard(scopeguard&&) = delete;
~scopeguard() { (*this)(); }
};
template<typename F> scopeguard(F) -> scopeguard<std::decay_t<F>>;
#define PP_CAT_IMPL(x, y) x##y
#define PP_CAT(x, y) PP_CAT_IMPL(x, y)
#define at_scope_exit \
[[maybe_unused]] const auto PP_CAT(sg$, __COUNTER__) = scopeguard
auto foo::fn() const -> int { return 42; }
int main() try {
auto libfoo = dlopen("./libfoo.so", RTLD_NOW | RTLD_GLOBAL);
assert(libfoo);
at_scope_exit { [&] { dlclose(libfoo); } };
const auto new_foo =
reinterpret_cast<make_foo_fn<>>(dlsym(libfoo, "new_foo"));
assert(new_foo);
const auto f = std::unique_ptr<foo>{new_foo()};
std::cout << f->vfn() << std::endl;
} catch (...) { throw; }
==============================================================================
$ GXX='g++ -std=c++17 -Wall -Wextra -pedantic -O2'
$ ${GXX} -fPIC -shared foo.cpp -o libfoo.so
$ ${GXX} -rdynamic main.cpp -ldl
$ ./a.out
42
線上版:http://coliru.stacked-crooked.com/a/0f8900f100523fc7
這裡在 foo.hpp 裡面宣告 foo 為一個 abstract class 並定義 foo 的 interface,
main() 可以透過這個 interface 操作 foo,
而 foo 的實做 foo_impl 定義在 foo.cpp 裡面,
並編譯成 libfoo.so 讓 main() 透過 dlopen("path/to/libfoo.so", ...) 載入,
但這會有一個問題,main() 根本就不知道 foo_impl 的存在,
所以 foo.cpp 會定義一個 make_foo 的 function 回傳一個 foo*,
而 main() 可以用 dlsym(..., "make_foo"); 拿到 foo*,
並透過呼叫 foo 的 virtual function 就可以 dispatch 到 foo_impl 的實做
這裡更有趣的就是:
foo_impl::vfn() 會呼叫 foo::fn() 這個不是 virtual 的 function,
而且他的定義在 main.cpp 裡面 (當然也有可能是別的 translation unit),
所以 libfoo.so 是看不到 foo:fn() 的定義的,
而 link 的時候要加上 -rdynamic (也就是 ld 的
作者: xam (聽說)   2017-10-07 23:00:00
我覺得看得懂這篇的話, 沒道理google找的看不懂..
作者: Bencrie   2017-10-07 23:10:00
推詳解 XD
作者: stucode   2017-10-07 23:17:00
推,這要解釋清楚真的需要一點篇幅。
作者: mabinogi805 (焚離)   2017-10-08 00:03:00
也太詳細QQ
作者: dreamboat66 (小嫩)   2017-10-08 02:37:00
謝謝詳細的解說,努力理解中

Links booklink

Contact Us: admin [ a t ] ucptt.com