0

私は小さな作業用プラグイン サーバーを作成しました。プラグインは共有オブジェクトを使用して実装されます。共有オブジェクトは実行時に(header ).soの呼び出しによって「サーバー」に手動でロードされます。dlopen<dlfcn.h>

すべての共有オブジェクト プラグインは同じインターフェースを持っています。

extern "C" void* do_something() {
    return SharedAllocator<T>{}.allocate(...); // new T
}
extern "C" size_t id = ...; // unique
  • 基本的にdo_something、呼び出し元が解放すると予想されるヒープメモリへのポインタを返します。
  • idは単に ごとに一意の識別子.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_proxydo_somethingdo_soemthing_proxyalloca

std::array<int8_t, N>の値に 256 または 1024 を使用して、スタックに一定量のメモリを割り当てることができることを知っていますN。ただし、このソリューションは少し汚れています。あるスタックフレームから別のスタックフレームにデータを不必要にコピーし、プラグインが返すことができるデータの量を制限します。さらに、(私はまだこのソリューションのベンチマークを行っていませんが) コンパイラが動的境界を越えてコピーを削除できない限り、1024 バイトをコピーすることは、つまりsizeof(std::string)バイトをコピーすることよりも多くの作業であると想定します。

理想的な世界では、do_soemthing_proxyこれを RAII で処理する構造体を返す必要があると思います。const std::any必要に応じて、スタック割り当てされた A。これは可能ですか?

これが c++ 内でまったく不可能な場合、スタックまたはベース ポインターを手動でハイジャックすることによって、アセンブリで移植可能な方法でこの動作を実現することは可能でしょうか?

ありがとう。

4

1 に答える 1

0

実際、私はちょうど解決策を見つけました。要するに、割り当て用のメモリ位置Tが渡される方向を逆にすることです。

スタックにdo_soemthing_proxy有効なサイズを割り当てる方法はありますか?T

多分。しかし、コードが実際に必要とするのはT、プロキシ内ではなく、呼び出し元の場所での有効サイズの割り当てです。そして、呼び出し元は を知っているので、あなたがしなければならないことは、 を呼び出す前に呼び出し元のスタックに のsizeof(T)スペースを割り当て、それを呼び出すときに割り当てられたバッファのアドレスを に渡すことだけです:Tdo_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 に答える