0

我写了一个小型的工作插件服务器。插件是使用.so共享对象实现的,这些共享对象在运行时通过调用dlopen(header <dlfcn.h>) 在“服务器”中手动加载。

所有共享对象插件都具有相同的接口:

extern "C" void* do_something() {
    return SharedAllocator<T>{}.allocate(...); // new T
}
extern "C" size_t id = ...; // unique
  • 基本上,do_something返回一个指向堆内存的指针,调用者应该释放它。
  • id只是一个唯一的标识符 per .so
  • T是每个特定的结构.so。其中一些共享相同的返回类型,而另一些则没有。这里的重点是,sizeof(T).so具体的。

服务器负责动态加载和读取.so二进制文件的符号。所有.so插件都可以通过do_something_proxy服务器二进制文件中定义的方法相互调用,该方法充当调用者和被调用者之间的粘合剂:

extern "C" void* do_something_proxy(size_t id) {
    // find the requested handle
    auto handle = some_so_map.find(id)->second;

    // call the handle's `do_something`
    void* something_done = handle.do_something();

    // forward the result
    return something_done;
}

为了简化一点,假设这some_so_map是一个简单的std::unordered_map<size_t, so_handle_t>填充,使用一堆调用dlopen何时执行代理。

我的问题是每个调用者在编译时都do_something_proxy知道。T正如我之前所说,T可能因呼叫站点而异;但是对于任意呼叫站点T 永远不会改变。

作为参考,这是所有调用者使用的定义:

template <typename T, size_t id>
T* typed_do_soemthing_proxy() {
    // simple cast of the proxy
    return reinterpret_cast<T*>(do_soemthing_proxy(id));
}

换句话说,do_something_proxy对于某些任意插件,id总是具有相同的返回类型。

如果不是为了代理,我可以只使用模板do_soemthing_proxy并传递T或使用std::array<int8_t, N>with ,并且当调用可以移动到堆栈时sizeof(T) == N,分配的不必要的内存不会被切片。但是,代理无法在编译期间知道所有可能的返回类型并导出无数版本的.Tdo_something_proxydo_something_proxy

所以我的问题是,有没有办法在其堆栈上do_soemthing_proxy分配有效大小T(即使用alloca或其他形式的堆栈分配)?

据我所知,alloca这里似乎不起作用,因为do_soemthing_proxy只能从do_something请求的插件的函数中接收一个值。do_soemthing_proxy将同时接收要分配的大小和要复制到分配的内存的字节。要是alloca能在两者之间“压扁”就好了……

std::array<int8_t, N>我知道我可以使用256 甚至 1024 的值在堆栈上分配固定数量的内存N。但是,这个解决方案有点脏。它不必要地将数据从一个堆栈帧复制到另一个堆栈帧,并限制了插件可以返回的数据量。最重要的是,(虽然我还没有对这个解决方案进行基准测试)除非编译器可以跨越动态边界删除副本,否则我假设复制 1024 字节比复制 iesizeof(std::string)字节更工作。

在一个理想的世界里,我相信do_soemthing_proxy应该返回一个用 RAII 处理这个的结构。一个const std::any堆栈分配的,如果你愿意的话。这甚至可能吗?

如果这在 c++ 中根本不可能,是否可以在汇编中以可移植的方式实现这种行为,即通过手动劫持堆栈或基指针?

谢谢。

4

1 回答 1

0

实际上,我刚刚找到了解决方案。它归结为反转分配的内存位置的方向T

有没有办法在其堆栈上do_soemthing_proxy分配有效大小?T

也许。但是代码实际需要的是T在调用者的位置分配有效大小,而不是在代理内部。既然调用者知道sizeof(T),你所要做的就是在调用之前在调用T者的堆栈上分配空间,然后在调用时将分配的缓冲区的地址传递给它:do_somethingdo_something_proxy

对于来电者:

template <typename T, size_t id>
T typed_do_something_proxy() {
    std::aligned_storage_t<sizeof(T), alignof(T)> return_buffer;
    do_something_proxy(id, &return_buffer);
    return *std::launder(reinterpret_cast<T*>(&return_buffer));
}

对于代理:

extern "C" void do_something_proxy(size_t id, void* return_buffer) {
    auto handle = some_so_map.find(id)->second;
    handle.do_something(return_buffer);
}

对于被叫方

extern "C" void do_something(void* return_buffer) {
    new(return_buffer) T(...); // placement new
}
于 2022-01-24T15:55:45.750 回答