Shared library Nutzung in python "undefined symbol"?
Ich versuche ein möglichst einfaches Beispiel zu verwenden um überhaupt erstmal irgendwas zum Laufen zu bringen. Ich möchte die Funktionen einer shared library in python nutzen:
Calc.h
#ifndef CALC_H
#define CALC_H
class Calc {
public:
int add();
};
#endif
Calc.cpp
#include "include/Calc.h"
int Calc::add() {
return 5;
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(calc)
set(CMAKE_CXX_STANDARD 11)
# Define sources and executable
set(SOURCES Calc.cpp)
add_library(calc SHARED ${SOURCES})
target_link_libraries(calc ${LIBS} -Wl,--export-dynamic)
install(TARGETS calc DESTINATION lib)
install(FILES Calc.h DESTINATION include)
mkdir build && cd build && cmake .. && make
...
[100%] Linking CXX shared library libcalc.so
[100%] Built target calc
main.py
import ctypes
lib = ctypes.CDLL('./libcalc.so')
lib.add.restype = ctypes.c_int
result = lib.add()
print(result)
...$ python3 main.py
Traceback (most recent call last):
File ".../main.py", line 7, in <module>
lib.add.restype = ctypes.c_int
File "/usr/local/lib/python3.9/ctypes/__init__.py", line 387, in __getattr__
func = self.__getitem__(name)
File "/usr/local/lib/python3.9/ctypes/__init__.py", line 392, in __getitem__
func = self._FuncPtr((name_or_ordinal, self))
AttributeError: ./libcalc.so: undefined symbol: add <----------
Was mache ich falsch?
2 Antworten
Wie schon gesagt ist Dein Problem das Name Mangling
Du könntest jetzt natürlich mal versuchen den gemangelten Namen zu nutzen, Problem: Das Mangling hat in der Regel keine stabile ABI.
Du könntest mit
extern C
arbeiten, Dein PRoblem ist aber, daß Du kein instanziiertes Objekt hast, auf dessen Methoden Du arbeiten könntest.
Ich würde ja mal nach Docs zum Thema C++ DSO und Python schauen, ich vermute Du wirst nicht um ein extern C Interface mit static storage einer Schnittstellenklasse herumkommen - aber ich mag mich irren.
Schaue Dir einfach mal:
https://isocpp.org/wiki/faq/mixing-c-and-cpp#call-cpp
an. Im Endeffekt erwartet Python ja ne C Linkage. Wenn Du wirklich mit Klassen arbeiten möchtest, dann wirst Du um einen Wrapper nicht herumkommen.
Ich hatte ursprünglich angenommen (oder eher gehofft), daß es mit statischen Membern einfacher ist und Du einfach eine Wrapperklase nehmen könntest. Aber nein, geht wohl doch nicht :-/.
Das Beispiel geht davon aus, daß Du anderweitig instanziierst. Hier böte sich ggf. der Zeitpunkt das Ladens des Moduls an, um das zu erledigen.
Und ja, es wird definitiv unschön.
Das verstehe ich nicht so ganz. Also am einfachsten möchte ich erstmal dem ganzen cpp code der bereits existiert meine eigene function hinzufügen die erstmal nut 5 zurückgibt, nur um es zu testen. Dann füge ich also direkt zB über der main() meinen Code ein?
extern "C" int run_program() {
return 5;
}
Wenn ich das jetzt als shared library kompiliere sehe ich über nm -D leider wieder keine Funktion die so heißt. Also irgendwas fehlt noch.
Ich habe das gerade mal mit
g++ -fPIC -shared -Wl,-soname dummy.so.1 -o dummy.so dummy.cc
durch g++ gejagt und less meint:
[...]
Symbol table '.dynsym' contains 6 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (2)
2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTable
3: 0000000000000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
4: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
5: 00000000000010f5 11 FUNC GLOBAL DEFAULT 10 run_program
Das sieht aus, als wäre kein Mangling gemacht worden.
ich versuche es dann lieber auch erstmal an einem testprojekt. Melde mich dann sobald es geklappt hat.
Tatsächlich funktioniert alles ohne Probleme. Hm. Dann muss es ja scheinbar am makefile liegen, ich nutze dort folgenden Befehl um die .so zu erstellen:
ocr_db_crnn: fetch_clipper fetch_opencv crnn_process.o db_post_process.o clipper.o cls_process.o
$(CC) -shared $(SYSROOT_LINK) $(CXXFLAGS_LINK) crnn_process.o db_post_process.o clipper.o cls_process.o -o libocr_db_crnn.so $(CXX_LIBS) $(LDFLAGS)
Leider habe ich nur sehr wenig Erfahrung in sowas, wie müsste ich die Flags anpassen/welche hinzufügen um die selbe Funktionalität wie bei
g++ -fPIC -shared -Wl,-soname dummy.so.1 -o dummy.so dummy.cc
zu erreichen?
Das mit dem soname kann erstmal entfallen, das wird eh nur an den Linker durchgeeicht, damit ein soname Symbol vorhanden ist - kannste zu einem 'will do later' machen (JE nachdem was Python da erwartet).
Sollte es nicht eher $(CXX) sein?
Wo definierst Du denn die anderen Variablen?
Ich hab es tatsächlich durch ausprobieren hinbekommen. Einfach die Flags
-fPIC -shared -Wl
hinter das $(CC) . Hab eben nachgelesen, dass es nur Formsache ist ob man CC oder CXX schreibt. Angeblich nutzt der linker bei beidem die gleiche Referenz. Sagt mir jetzt nicht viel aber scheint wohl so zu funktionieren.
nm -D libocr_db_crnn.so' | grep run
00000000000a50b8 T run_program
Vielen Dank soweit, das bringt mich schonmal sehr viel weiter!
Ah okay. Ich sags mal so, man hat es nicht immer alles im Kopf :-D.
Und:
Bitte, gern geschehen, gut wenn es Dich schonmal weiter gebracht hat. Manchmal hat man bei so Dingen echt zu kämpfen ;-).
Schau Dir mal die Symbole an, die die shared Library exportiert. Ich würde wetten, dass add nicht dabei ist.
C++ macht normalerweise Name Mangling, d.h. Die Namen werden umgesetzt um z.B. verschiedene Typen von Eingangsparametern umzusetzen. Add könnte ja eine Funktion von void add(void), von int add(int) und von add(const string&) String sein. Dieses müsste der Linker wissen, um calls zur entsprechenden Funktion linken zu können. Darum gibt es Name Mangling.
nm -D libcalc.so w __cxa_finalize w __gmon_start__ w _ITM_deregisterTMCloneTable w _ITM_registerTMCloneTable 00000000000010fa T _ZN4Calc3addEv
Hab gegoogelt, T steht wie du sagst für name mangling und _ZN4Calc3addEv müsste dann die add function sein. Also wird sie ja scheinbar doch exportiert? Und was genau muss ich dann im linker angeben damit ich sie als "lib.add" in python nutzen kann?
Ja, aber nicht zwingend unter dem Namen, den Python erwartet, kann das sein?
Wenn ich Dir sage, ich suche nach “calc” und Du mir sagst: “hallo, ich habe da _ZN4Calc3adEv, das ist doch das gleiche”, dann führt das zu Heiterkeit im richtigen Leben. Du musst schauen, ob Python C++ oder C export erwartet.
Tatsächlich.. ich muss einfach 1zu1 den mangled name verwenden und dann funktioniert auch schon alles ohne Probleme! Vielen vielen Dank dir für den Tipp!
Super! Du kannst das Mangling auch abstellen, d.h. Dem C++ sagen, dass es den C-Namen verwenden soll für einige Methoden. Wie das geht, weiß ich nicht mehr.
Ist meine Herangehensweise richtig? Ich versuche nun die Funktionen die in der cpp implementiert wurden zu ändern. ZB extern "C" __attribute__((visibility("default"))) void det(int argc, char **argv) { .... ich möchte auf diese Methode der lib von außen zugreifen. Scheinbar reicht das noch nicht denn ich sehe nach der Kompilierung der cpp Datei in ein .so über nm -D mylib.so" immer noch keine "det" methode. Im Makefile erstelle ich die .so über : ocr_db_crnn: fetch_clipper fetch_opencv crnn_process.o db_post_process.o clipper.o cls_process.o
$(CC) -shared $(SYSROOT_LINK) $(CXXFLAGS_LINK) crnn_process.o db_post_process.o clipper.o cls_process.o -o libocr_db_crnn.so $(CXX_LIBS) $(LDFLAGS)