コードでdlopen()
とdlsym()
を使用し、gcc
でコンパイルしようとしています。
これが最初のファイルです。
_/* main.c */
#include <dlfcn.h>
int main()
{
void *handle = dlopen("./foo.so", RTLD_NOW);
if (handle) {
void (*func)() = dlsym(handle, "func");
func();
}
return 0;
}
_
これが2番目のファイルです。
_/* foo.c */
#include <stdio.h>
void func()
{
printf("hello, world\n");
}
_
これが私がコードをコンパイルして実行する方法です。
_$ gcc -std=c99 -pedantic -Wall -Wextra -shared -fPIC -o foo.so foo.c
$ gcc -std=c99 -pedantic -Wall -Wextra -ldl -o main main.c
main.c: In function ‘main’:
main.c:10:26: warning: ISO C forbids initialization between function pointer and ‘void *’ [-Wpedantic]
void (*func)() = dlsym(handle, "func");
^
$ ./main
hello, world
_
警告を取り除くにはどうすればよいですか?
型キャストは役に立ちません。 dlsym()
の戻り値を関数ポインターに型キャストしようとすると、代わりにこの警告が表示されます。
_main.c:10:26: warning: ISO C forbids conversion of object pointer to function pointer type [-Wpedantic]
void (*func)() = (void (*)()) dlsym(handle, "func");
^
_
このコードが問題ないことをコンパイラに納得させるものは何ですか?
根本的に正しいことを望む場合は、関数のアドレスを解決しようとしないでください。代わりに、ダイナミックライブラリからある種の構造をエクスポートします。
struct export_vtable {
void (*helloworld)(void);
};
struct export_vtable exports = { func };
struct export_vtable {
void (*helloworld)(void);
};
int main() {
struct export_vtable* imports;
void *handle = dlopen("./foo.so", RTLD_NOW);
if (handle) {
imports = dlsym(handle, "exports");
if (imports) imports->helloworld();
}
return 0;
}
この手法は実際には非常に一般的であり、移植性ではありません。POSIXは、関数ポインターがvoid *との間で変換できることを保証しますが、柔軟性が高いためです。
ここでの問題は、オブジェクトへのポインタが関数ポインタから微妙に分離されていることです。 ISO/IEC 9899:201x紙§6.3.2.3ポインターそれは述べられています:
- Voidへのポインタは、任意のオブジェクトタイプへのポインタとの間で変換できます。任意のオブジェクトタイプへのポインタは、voidへのポインタに変換して元に戻すことができます。結果は元のポインタと同じになります。
。
- あるタイプの関数へのポインターを別のタイプの関数へのポインターに変換して、元に戻すことができます。結果は元のポインタと同じになります。変換されたポインターを使用して、指定された型と互換性のない型の関数を呼び出す場合、動作は未定義です。
したがって、関数ポインタはオブジェクトポインタとは異なり、その結果、関数ポインタへのvoid *
の割り当ては常に厳密に非準拠です。
とにかく、コメントで言ったように、99.9999 .... 9999%のケースでは、前述の論文のANNEX J-移植性の問題、§J.5.7関数ポインタキャストのおかげで許可されています。状態:
- オブジェクトまたはvoidへのポインターを関数へのポインターにキャストして、データを関数として呼び出すことができます(6.5.4)。
- 関数へのポインターは、オブジェクトへのポインターまたはvoidにキャストでき、関数を(たとえば、デバッガーによって)検査または変更できるようにします(6.5.4)。
実用面では、コードがより多くのファイルに分割されるのを回避する手法は、プラグマを使用して、小さなコードの衒学的な警告を抑制することです。
より残忍な形式は次のとおりです。
/* main.c */
#include <dlfcn.h>
#pragma GCC diagnostic Push //Save actual diagnostics state
#pragma GCC diagnostic ignored "-pedantic" //Disable pedantic
int main()
{
void *handle = dlopen("./foo.so", RTLD_NOW);
if (handle) {
void (*func)() = dlsym(handle, "func");
func();
}
return 0;
}
#pragma GCC diagnostic pop //Restore diagnostics state
より洗練された方法で、問題のあるコードを小さな関数で分離し、そのインライン化を強制することができます。これは効果的な解決策というよりは構成ですが、不要な診断を抑制します。
/* main.c */
#include <dlfcn.h>
#pragma GCC diagnostic Push //Save actual diagnostics state
#pragma GCC diagnostic ignored "-pedantic" //Disable pedantic
void (*)() __attribute__((always_inline)) Assigndlsym(void *handle, char *func)
{
return dlsym(handle, func); //The non compliant assignment is done here
}
#pragma GCC diagnostic pop //Restore diagnostics state
int main()
{
void *handle = dlopen("./foo.so", RTLD_NOW);
if (handle) {
void (*func)() = Assigndlsym(handle, "func"); //Now the assignment is compliant
func();
}
return 0;
}
厳密に準拠していないコードの部分がある間、コードの-pedantic
オプションを維持するには、カスタム警告オプションを使用してそのコードを別のファイルに分割します。
したがって、dlsym
関数をラップし、関数ポインターを返す関数を作成します。別のファイルに入れて、-pedantic
なしでそのファイルをコンパイルします。
これにより、私のコードは十分に衒学的になりました。
*(void**)(&func_ptr) = dlsym(handle, "function_name");
(私はここでそれを見つけました http://pubs.opengroup.org/onlinepubs/009695399/functions/dlsym.html )
次のように、union
を使用できます。
union {
void *ptr;
void (*init_google_logging) (char* argv0);
} orig_func;
orig_func.ptr = dlsym (RTLD_NEXT, "_ZN6google17InitGoogleLoggingEPKc");
orig_func.init_google_logging (argv0);