4 に答える
完全に標準的な適合ソリューション:
extern "C" typedef int (func_t)(char, double); // desired API function signature
int main()
{
static_assert(sizeof(void *) == sizeof(func_t *), "pointer cast impossible");
void * p = dlsym(handle, "magic_function");
char const * cp = reinterpret_cast<char const *>(&p);
func_t * fp;
std::copy(cp, cp + sizeof p, reinterpret_cast<char *>(&fp));
return fp('a', 1.25);
}
これを書くためのより単純な、しかしより疑わしい方法の1つは、型のパンニングを少し使用します。
static_assert(sizeof(void *) == sizeof(func_t *), "pointer cast impossible");
void * vp = dlsym(handle, "magic_function");
func_t * fp;
*reinterpret_cast<void **>(&fp) = vp; // this is type-punning
まず、g++はリンケージを型の一部として扱いません。
(関数へのポインタ)を関数である。でso.hpp
初期化しようとするため、コンパイルしないでください。これは違法であり、コンパイラエラーが必要ですが、g++は警告なしでそれを受け入れます。myclass* (*)()
extern "C"
make
extern
"C"
それを超えて、が定義されていない場合、なぜhost.cpp
警告を生成する必要があります。usefunction
DLLでは、関数へのポインタを含むinterface
データ型のインスタンスです。データ型にキャストするこの変数(関数ではない)のアドレスを取得するために使用します。データへのポインタを関数へのポインタに変換することは決してありません。関数へのポインタを含むデータオブジェクトへのポインタを逆参照します。これは問題ありません。dlsym
が付いているバージョンのreinterpret_cast
場合、警告は正当化されdlsym
ます。関数へのポインター(変数ではなく)を返しますが、それを。として返しますvoid*
。標準(少なくともC ++ 03を介して)では、この変換は違法であり、関数へのポインターがデータへのポインターよりも大きいため、動作させることができないコンパイラーを使用しました。Cで許可される制限として、Unix(Posix)では、関数へのポインターとデータへのポインターが同じサイズと表現である必要があり、Posix標準では、の戻り値をdlsym
次のように変換するように指示されています。
myclass* (*func)();
*reinterpret_cast<void**>( &func ) = dlsym( th, "make" );
amyclass* (*)()
とvoid*
実際のサイズと表現が同じである場合、これは合法であり、機能します(警告をトリガーするべきではありません)。
gccからの警告は、gccがPosixを想定する意思があるかどうかを知らないためです。したがって、それはそうではないと想定し、(C ++標準で要求されているように)不正な形式のプログラムを診断します。
ただし、 Posixを使用しており、Posixと同じように機能dlsym
することを期待しているdlsym
ため、Posixに依存することをいとわないでしょう。次に、関数ポインタ型へのCスタイルのキャストを実行できますvoid*
。gccは、C++では問題がない場合でもこれが問題ないことを保証します。模倣するPosix以外のシステムはdlsym
、同様のことを保証する必要があります。そうしないと、関数ポインタをで返すのは意味がないからvoid*
です。
自分が何をしているのかがわかっているので、gccからの警告を消すことができます。
構造体を含むコードでAPI
警告が表示されない理由は、オブジェクトへのポインターであるvoid*
可能性があるためです。static_cast
データメンバーにアクセスするときは、厳密なエイリアシングに違反していると思います。API
これは、その場所にある実際のオブジェクトがである場合に、関数へのポインタ型の左辺値と型の左辺値を介してメモリを参照するためですvoid*
。void*
ただし、構造体のレイアウトは、実装のaおよび関数へのポインターのレイアウトと同じであるため、とにかく機能します。理論的には、同じレイアウトであっても、厳密なエイリアシング違反のために破損する可能性があります(より多くの最適化を使用する可能性が高くなります)。
厳密なエイリアシング違反を回避する安全な方法はstd::memcpy(&fp, &p, sizeof p)
、Kerrekと同じですが、完全な型が必要なのに対し、テイクが原因で場所が乱雑になることstd::copy
が少なくなります。厳密なエイリアシング違反を回避するだけでなく、これにより診断も回避できます。関数ポインタとオブジェクトポインタの間でキャストすることはもうありません。一方のオブジェクト表現をもう一方に直接コピーしているのです。のオブジェクト表現が関数へのポインタ(Posixの場合)と同じであることが保証されている場合、これは機能します。reinterpret_casts
memcpy
void*
std::copy
void*
関数ポインタを構造体でラップしている場合は、間接レベルを追加することで問題を回避できます。関数ポインタが10バイトかかったが、オブジェクトポインタが4バイトかかったと想像してみてください。構造体は少なくとも10バイトであり、それへの4バイトのポインターがあります。これはすべて完全に問題ありません。構造体にアクセスして関数ポインタにアクセスすると、10バイト全体が引き出されます。何も失われません。ただし、10バイトの関数ポインターを4バイトのオブジェクトポインターにキャストすると、必然的に6バイトの情報が失われます。
dlsymをサポートするプラットフォームには、関数ポインターのアドレスを格納するのに十分な大きさのvoidポインターが必要であるため、これは実際の問題ではありませんが、コンパイラーは、移植性のないコードを記述できないようにしようとしています。