GCCコンパイラを使用してC++でライブラリA、B、Cを使用している場合。Lib AとBはどちらもCに依存していますが、バージョンが異なります。その後、別のプログラムでAとBを一緒に使用できますか?または、AとBによってCに必要な異なるバージョンが競合しますか?そして、どうすればこれを解決できますか?
5 に答える
動的にリンクしていると思います。AとBの両方がそれぞれのバージョンのCを完全にカプセル化する場合、これを行うことができる可能性があります。実行時にロードされるときの混乱を避けるために、Cの異なるバージョンの名前が異なることを確認する必要がある場合があります(つまり、libMyC.1.soとlibMyC.2.so)。
実行時の負荷の混乱の可能性を回避するために、静的に構築されたAとBを調査することもできます。
見つける最も簡単な方法は、単にそれを試すことです。それが機能するかどうかを判断するのにそれほど時間はかからないはずです。
最後に、もちろん、はるかに簡単な解決策であり、メンテナンスの観点から最も良いのは、AまたはBをもう一方のレベルに上げて、両方が同じバージョンのCを使用するようにすることです。これは多くの点で優れています。実際の問題を回避するのではなく、そうすることを強くお勧めします。
ダイナミックライブラリは強力なバージョンチェックを行いません。つまり、AがCで使用するエントリポイントが変更されていない場合でも、新しいバージョンのCを使用できます。とはいえ、Linuxディストリビューションはシンボルリンクを使用することがよくあります。バージョンサポートを提供するファイルシステム方式。これは、実行可能ファイルが1.2.2でのみ機能するように設計されている場合、それを検索するために特別にリンクできることを意味します/usr/lib/mylib-1.2.2
。
ほとんどの場合、プログラムは一般的なケースを見つけるためにリンクされています。/usr/lib/mylib
これは、マシン上にあるバージョンにシンボリックにリンクされます。例/usr/lib/mylib -> /usr/lib/mylib-1.2.2
:特定のバージョンにリンクせず、実際のインターフェイスが変更されない限り、上位互換性は問題になりません。
ライブラリAとBが特定の名前のバージョンのCにバインドされているかどうかを確認する場合は、ldd
それらのコマンドを使用してdll検索パスを確認できます。
答えを探しているときにこの質問を見つけました。@Component-10が示唆しているように、この動作を調査するための最小限のファイルセットを作成し、MacOS+CLANGでテストしました。
- AとBを共有ライブラリとして構築する場合、AとBの依存関係であるが、バージョンが異なる依存ライブラリCに対して適切な解決策を得ることができます。
- AとBを静的として構築すると、失敗します。
編集
コメントで指摘されているように、共有ライブラリのアプローチはクロスプラットフォームではなく、Linuxでは機能しません。
@SergAは、Dynamicly Loaded Library(dl)API(https://www.dwheeler.com/program-library/Program-Library-HOWTO/x172.html)を使用してソリューションを作成しました。
dlopenを使用した@SergAのソリューション
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
// #define DLOPEN_FLAGS RTLD_LAZY | RTLD_LOCAL
#define DLOPEN_FLAGS RTLD_LAZY
#if defined(_WIN32) || defined(__CYGWIN__)
// Windows (x86 or x64)
const char* libA = "libA.shared.dll";
const char* libB = "libB.shared.dll";
#elif defined(__linux__)
// Linux
const char* libA = "libA.shared.so";
const char* libB = "libB.shared.so";
#elif defined(__APPLE__) && defined(__MACH__)
// Mac OS
const char* libA = "libA.shared.dylib";
const char* libB = "libB.shared.dylib";
#elif defined(unix) || defined(__unix__) || defined(__unix)
// Unix like OS
const char* libA = "libA.shared.so";
const char* libB = "libB.shared.so";
#else
#error Unknown environment!
#endif
int main(int argc, char **argv)
{
(void)argc;
(void)argv;
void *handle_A;
void *handle_B;
int (*call_A)(void);
int (*call_B)(void);
char *error;
handle_B = dlopen(libB, DLOPEN_FLAGS);
if(handle_B == NULL) {
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
handle_A = dlopen(libA, DLOPEN_FLAGS);
if(handle_A == NULL) {
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
call_A = dlsym(handle_A, "call_A");
error = dlerror();
if(error != NULL) {
fprintf(stderr, "%s\n", error);
exit(EXIT_FAILURE);
}
call_B = dlsym(handle_B, "call_B");
error = dlerror();
if(error != NULL) {
fprintf(stderr, "%s\n", error);
exit(EXIT_FAILURE);
}
printf(" main_AB->");
call_A();
printf(" main_AB->");
call_B();
dlclose(handle_B);
dlclose(handle_A);
return 0;
}
静的と共有を示す以前のソリューション
これが私のファイルのセットです。簡潔にするために、ここではそれらすべてを示しません。
$ tree .
.
├── A
│ ├── A.cc
│ └── A.hh
├── B
│ ├── B.cc
│ └── B.hh
├── C
│ ├── v1
│ │ ├── C.cc
│ │ └── C.hh
│ └── v2
│ ├── C.cc
│ └── C.hh
├── compile_shared_works.sh
├── compile_static_fails.sh
├── main_A.cc
├── main_AB.cc
└── main_B.cc
AはCバージョン1に依存し、BはCバージョン2に依存します。各ライブラリには単一の関数がlibA
含まれます。たとえば、v1call_A
を呼び出す関数とlibC
v1を呼び出す関数が含まれます。call_C
libB
call_B
libC
call_C
次に、のみ、のみ、および両方にmain_A
リンクします。libA
main_B
lib_B
main_AB
compile_static_fails.sh
libA
次の一連のコマンドは、libB
静的にビルドされます。
#clean slate
rm -f *.o *.so *.a *.exe
#generate static libA
g++ -I . -c C/v1/C.cc A/A.cc
ar rvs libA.a *.o
rm -f *.o
#generate static libB
g++ -I . -c C/v2/C.cc B/B.cc
ar rvs libB.a *.o
rm -f *.o
#generate 3 versions of exe
g++ -L . -lA main_A.cc -o main_A.exe
g++ -L . -lB main_B.cc -o main_B.exe
g++ -L . -lA -lB main_AB.cc -o main_AB.exe
./main_A.exe
./main_B.exe
./main_AB.exe
出力は
main_A->call_A->call_C [v1]
main_B->call_B->call_C [v2]
main_AB->call_A->call_C [v1]
main_AB->call_B->call_C [v1]
main_AB
実行するとcall_B
間違った場所に移動します!
compile_shared_works.sh
#clean slate
rm -f *.o *.so *.a *.exe
#generate shared libA
g++ -I . -c -fPIC C/v1/C.cc A/A.cc
g++ -shared *.o -o libA.so
rm *.o
#generate shared libB
g++ -I . -c -fPIC C/v2/C.cc B/B.cc
g++ -shared *.o -o libB.so
rm *.o
#generate 3 versions of exe
g++ -L . -lA main_A.cc -o main_A.exe
g++ -L . -lB main_B.cc -o main_B.exe
g++ -L . -lA -lB main_AB.cc -o main_AB.exe
./main_A.exe
./main_B.exe
./main_AB.exe
出力は
main_A->call_A->call_C [v1]
main_B->call_B->call_C [v2]
main_AB->call_A->call_C [v1]
main_AB->call_B->call_C [v2]
それは(MacOSで)動作します!
@SergAのソリューションは、フラグ
RTLD_LAZY |を使用して共有ライブラリを開くと、Linuxでも機能します。RTLD_LOCAL
出力は次のとおり
です。1。Main_AB_dlopen->CallA->callC(v1)
2. Main_AB_dlopen-> callB-> callC(v2)
dlopen(RTLD_LOCAL | RTLD_DEEPBIND)
またはdlmopen(LM_ID_NEWLM, "filename.so", ...)
(上記の他の回答としておなじみ)による解決策があります
まず、この記事を読んで、.dynsymがLinux ELFの「エクスポートされたシンボルテーブル」であることを確認する必要があります: https ://blogs.oracle.com/solaris/post/inside-elf-symbol-tables
各elfファイルには実行可能ファイルが含まれ、ダイナミックライブラリのヘッダーには.dynsymが含まれています。で宣言された関数は__attribute__((visibility("default")))
、このヘッダーゾーンに記録されます。
ただし、がコンパイラ(gcc)に割り当てられていない場合、すべての関数はコンパイラによって自動的-fvisibility=hidden
に宣言されます。__attribute__((visibility("default")))
(エクスポートとしてマークされたすべての機能)
.soの読み込みはどうですか?
すべての.soは、プログラムの開始前にメモリにロードされint main()
、ELFヘッダーでDT_NEEDEDとしてマークされたすべてのファイルが1つずつ読み込まれます。
現在のプログラムのシステムにはグローバルシンボルテーブルがあり、すべての.so loadadがあり、このテーブルに保持されている関数名のアドレスを入力します。2つの.soファイルが同じ関数名を持っている場合、最初の1つだけが受け入れられます。
dlopen()
ロードされるプログラムdlopen()
は、ELFヘッダーによるロードと違いはなく、グローバルシンボルテーブルも。で埋められdlopen()
ます。
例えば
// main.cpp
__attribute__((visibility("default"))) int fn() { return 10; }
void print();
int main() { print(); return 0; }
// libfn.cpp => libfn.so
__attribute__((visibility("default"))) int fn() { return 999; }
__attribute__((visibility("default"))) void print() { cout<<fn(); }
in main.cppはlibfn.soの前にロードされるため、10が出力されます。fn()
dlopen(libfn.so, RTLD_LOCAL)
なしで使用する場合でもRTLD_DEEPBIND
、数字の10が出力されます。
コンパイル段階で通常のリンクを使用する場合、lib_try_in_middle.soなどを使用してELFヘッダーの.dynsymを区切る場合でも、常にマークされているファイルの関数はグローバルシンボルテーブルに表示されます。__attribute__((visibility("default")))
既知の問題
アドレスサニタイザーはで動作できません。RTLD_DEEPBIND
また、新しい名前空間でasanを実行するための解決策は見つかりませんでしたdlmopen()
。