Powiązanie z biblioteką dynamiczną z zależnościami

79

Rozważ następujący scenariusz:

  • Biblioteka współdzielona libA.so, bez zależności.
  • Biblioteka współdzielona libB.so, z libA.so jako zależnością.

Chcę skompilować plik binarny, który łączy się z biblioteką libB. Czy powinienem połączyć plik binarny tylko z libB, czy też z libA?

Czy istnieje sposób na połączenie tylko z bezpośrednimi zależnościami, pozwalając na rozwiązanie nierozwiązanych symboli z zależności w czasie wykonywania?

Martwi mnie fakt, że implementacja biblioteki libB może się w przyszłości zmienić, wprowadzając inne zależności (na przykład libC, libD, libE). Czy będę miał z tym problemy?

Innymi słowy:

  • Pliki libA: a.cpp ah
  • Pliki libB: b.cpp bh
  • główne pliki programu: main.cpp

Oczywiście b.cpp zawiera ah, a main.cpp zawiera bh

Polecenia kompilacji:

Których z poniższych opcji powinienem użyć?

lub

Nie mogłem skorzystać z pierwszej opcji. Linker skarży się na nierozwiązane symbole z biblioteki libA. Ale to brzmi dla mnie trochę dziwnie.

Dziękuję bardzo.

- Zaktualizowane komentarze:

Kiedy łączę plik binarny, konsolidator spróbuje rozwiązać wszystkie symbole z main i libB. Jednak libB ma niezdefiniowane symbole z libA. Dlatego linker narzeka na to.

Dlatego też muszę połączyć się z libA. Jednak znalazłem sposób na ignorowanie nierozwiązanych symboli z bibliotek współdzielonych. Wygląda na to, że powinienem użyć następującego wiersza poleceń, aby to zrobić:

Wygląda na to, że nadal można skorzystać z tej -rpathopcji. Jednak muszę to trochę lepiej zrozumieć.

Czy ktoś zna możliwe pułapki podczas korzystania z tej -Wl,-unresolved-symbols=ignore-in-shared-libsopcji?

- Zaktualizowano komentarze 2:

-rpathnie powinny być używane do tego celu. Przydatne jest wymuszenie znalezienia biblioteki w podanym katalogu. -unresolved-symbolPodejście wygląda o wiele lepiej.

Dzięki jeszcze raz.

Marcus
źródło
Oto działający minimalny przykład dla tych, którzy chcą przetestować tego typu rzeczy: github.com/cirosantilli/cpp-cheat/tree/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Odpowiedzi:

42

Wygląda na to, że już tam jesteś przez większość drogi. Dobra robota z twoim dochodzeniem. Zobaczmy, czy mogę pomóc wyjaśnić „dlaczego” za tym stoi.

Oto, co robi konsolidator. Kiedy łączysz swój plik wykonywalny („main” powyżej), zawiera on pewne symbole (funkcje i inne rzeczy), które są nierozwiązane. Spojrzy w dół listy bibliotek, które następują, próbując rozwiązać nierozwiązane symbole. Po drodze stwierdza, że ​​niektóre symbole są dostarczane przez bibliotekę libB.so, więc zauważa, że ​​są teraz rozwiązywane przez tę bibliotekę.

Jednak odkrywa również, że niektóre z tych symboli używają innych symboli, które nie zostały jeszcze rozwiązane w twoim pliku wykonywalnym, więc teraz musi rozwiązać również te symbole. Bez linków do libA.so, Twoja aplikacja byłaby niekompletna. Gdy łączy się z libA.so, wszystkie symbole są rozwiązane i łączenie jest zakończone.

Jak widzieliście, użycie -unresolved-symbols-in-shared-libs, nie rozwiązuje problemu. Po prostu odracza to, aby te symbole były rozwiązywane w czasie wykonywania. Do tego -rpathsłuży: określenie bibliotek, które mają być przeszukiwane w czasie wykonywania. Jeśli tych symboli nie można rozwiązać, aplikacja nie uruchomi się.

Nie jest łatwo ustalić zależności bibliotek, ponieważ symbol może być dostarczony przez więcej niż jedną bibliotekę i być usatysfakcjonowany, łącząc się z dowolną z nich.

Tutaj jest inny opis tego procesu: Dlaczego kolejność łączenia bibliotek czasami powoduje błędy w GCC?

kithril
źródło
2
Wiem, że to było dawno temu, zapomniałem oznaczyć to jako rozwiązane. Bardzo dziękuję za odpowiedź.]
Marcus
Dziękuję wam za niesamowite pytanie i trafną odpowiedź! Mam prawie dokładnie ten sam scenariusz. Próbuję zawinąć udostępnione obiekty openCV w niestandardowy obiekt udostępniony. powiedzmy, że C.so to mój zwyczaj, a CV.so jest jakimś openCV. Pomyślnie połączyłem C.so z CV.so i chcę połączyć main.cpp (rozumiesz!) Z C.so, ale oprócz używania obiektów z C.so, main.cpp używa obiekt „cv :: Mat”, który jest zdefiniowany w CV.so .. W tym przypadku, aby uruchomić main.cpp, czy muszę łączyć się z C.so i CV.so? Bardzo bym chciał, żeby ktoś rzucił na to światło. Dziękuję Ci! :)
Lenny Linus
2
Dlaczego linker nie rozwiązałby wszystkich wymaganych symboli libA.so podczas tworzenia libB.so? Główna aplikacja nie powinna łączyć się z libA, ponieważ powinna być przezroczysta, np. Pośrednio chroniona przez bibliotekę libB?
Wilson
To jest niepoprawne. Linker nie korzysta z biblioteki libA inaczej niż do powiadamiania użytkownika o niekompletnym łańcuchu zależności. Bez podania biblioteki libA.so, ale zamiast przekazywania -unresolved-symbols = ignore-in-shared-libs, otrzymasz ten sam dokładny plik wykonywalny.
listerreg
21

Do dynamicznego linkowania tylko z bezpośrednimi zależnościami możesz użyć -Wl,--as-neededdodając biblioteki po -Wl,--as-needed :

Do sprawdzenia bezpośrednich zależności należy użyć readelf zamiast ldd, ponieważ ldd pokazuje również zależności pośrednie.

ldd pokazuje również zależności pośrednie:

Jeśli używasz cmake , możesz dodać następujące wiersze, aby uwzględnić tylko bezpośrednie zależności:

user1047271
źródło
Przepraszam, zrobiłem twój przykład jako program c zamiast c ++. Dlatego użyłem kompilatora gcc. Ale działa również z g ++.
user1047271
1
To naprawdę przydatne informacje. Nie wiedziałem o używaniu readelf zamiast ldd. Dziękuję bardzo.
Marcus
1

Inną opcją jest użycie libtool

Jeśli zmienisz g++wywołanie na, libtool --mode=compile g++aby skompilować kod źródłowy, a następnie libtool --mode=link g++utworzysz aplikację z libB, tolibA zostanie ona automatycznie połączona.

mattmilten
źródło
0

To ciekawy post - też waliłem się tym w głowę, ale myślę, że przegapiłeś tutaj punkt ..

Pomysł jest następujący, prawda?

Rozważmy dalej, że ...

  • W a.cpp (i tylko tam) definiujesz klasę / zmienną, nazwijmy ją „symA”
  • W b.cpp (i tylko tam) definiujesz klasę / zmienną, nazwijmy ją „symB”.
  • symB używa symA
  • main.cpp używa symB

Teraz libB.so i libA.so zostały skompilowane tak, jak opisano powyżej. Po tym Twoja pierwsza opcja powinna działać, tj .:

Myślę, że twój problem wynika z tego

w main.cpp odnosisz się również do symA

Mam rację?

Jeśli używasz symbolu w swoim kodzie, to ten symbol musi znajdować się w pliku .so

Cała idea powiązania bibliotek współdzielonych (tj. Tworzenia interfejsów API) polega na tym, że symbole w głębszych warstwach są ukryte (pomyśl o obieraniu cebuli) i nie są używane. .. tj. nie odwołuj się do symA w twoim main.cpp, ale zamiast tego tylko do symB (i pozwól symB odnosić się tylko do symA).

El Sampsa
źródło
1
Źle to zrozumiałeś. Najwyraźniej musisz natychmiast rozwiązać wymagane symbole. Problem jest związany z symbolami, których mogą wymagać twoje zależności. Często zdarza się, że podczas opracowywania bibliotek, które mają być używane przez firmę zewnętrzną, użytkownik biblioteki musi zapewnić implementację funkcjonalności wykorzystywanych przez bibliotekę. Tworzysz przy użyciu kodu pośredniczącego, bez żadnych zależności, a integrator opracuje bibliotekę zastępującą stub, a następnie połączy wszystko razem. Program ładujący będzie musiał rozwiązać wszystkie zależności. Jednak twoje skrypty kompilacji (i twoich użytkowników) muszą to uwzględnić.
Marcus