26

GCCコンパイラを使用してC++でライブラリA、B、Cを使用している場合。Lib AとBはどちらもCに依存していますが、バージョンが異なります。その後、別のプログラムでAとBを一緒に使用できますか?または、AとBによってCに必要な異なるバージョンが競合しますか?そして、どうすればこれを解決できますか?

4

5 に答える 5

6

動的にリンクしていると思います。AとBの両方がそれぞれのバージョンのCを完全にカプセル化する場合、これを行うことができる可能性があります。実行時にロードされるときの混乱を避けるために、Cの異なるバージョンの名前が異なることを確認する必要がある場合があります(つまり、libMyC.1.soとlibMyC.2.so)。

実行時の負荷の混乱の可能性を回避するために、静的に構築されたAとBを調査することもできます。

見つける最も簡単な方法は、単にそれを試すことです。それが機能するかどうかを判断するのにそれほど時間はかからないはずです。

最後に、もちろん、はるかに簡単な解決策であり、メンテナンスの観点から最も良いのは、AまたはBをもう一方のレベルに上げて、両方が同じバージョンのCを使用するようにすることです。これは多くの点で優れています。実際の問題を回避するのではなく、そうすることを強くお勧めします。

于 2012-04-27T13:26:34.603 に答える
2

ダイナミックライブラリは強力なバージョンチェックを行いません。つまり、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検索パスを確認できます。

于 2012-04-27T13:26:07.487 に答える
2

答えを探しているときにこの質問を見つけました。@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を呼び出す関数とlibCv1を呼び出す関数が含まれます。call_ClibBcall_BlibCcall_C

次に、のみ、のみ、および両方にmain_Aリンクします。libAmain_Blib_Bmain_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で)動作します!

于 2017-06-21T21:45:13.347 に答える
2

@SergAのソリューションは、フラグ RTLD_LAZY |を使用して共有ライブラリを開くと、Linuxでも機能します。RTLD_LOCAL
出力は次のとおり
です。1。Main_AB_dlopen->CallA->callC(v1)
2. Main_AB_dlopen-> callB-> callC(v2)

于 2020-06-15T06:56:59.997 に答える
0

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()

参照:https ://man7.org/linux/man-pages/man3/dlopen.3.html

于 2021-11-06T10:50:05.827 に答える