Undefined symbols vtable for Node Node::Node() in main.cpp.o bei shared library
-
Ok ich habe es jetzt versucht mit static linking und so funktioniert es.
Danke für die Hilfe.
-
@hendrik-weiler sagte in Undefined symbols vtable for Node Node::Node() in main.cpp.o bei shared library:
Ok ich habe es jetzt versucht mit static linking und so funktioniert es.
Danke für die Hilfe.Es muss nicht gleich static linking sein. Gegen die
.so
/.dylib
/.dll
linken sollte reichen (falls du das nicht eh damit meinst).BTW: Ich hab die Problematik nochmal etwas recherchiert und die übliche Lösung scheint zu sein, die Klassen-Instanz nicht im Client der Shared Library zu erzeugen (
main.cpp
, Zeile29
), sondern in der Bibliothek selbst über eine Factory-Funktion. Das Problem tritt ja beim Aufruf des Konstruktors auf, wenn der Compiler eine (unsichtbare) Referenz auf den VTable erzeugt. Polymorphe Objekte (mitvirtual
-Funktionen) haben ja einen versteckten Pointer auf diesen. Innerhalb der Library ist der VTable ja bekannt, da Teil des Objektcodes, außerhalb müsste der anderweitig zur Verfügung gestellt werden - zumindest wenn man die mitdlopen
/LoadLibrary
etc lädt.
-
Wenn ich es linke mit
target_include_directories(xmlparserrunner PRIVATE /Volumes/Expansion/xmlparser-cpp/lib/includes) target_link_libraries(xmlparserrunner PRIVATE /Volumes/Expansion/xmlparser-cpp/cmake-build-debug/libxmlparser.dylib)
Bekomme ich diese Fehler:
Undefined symbols for architecture arm64: "vtable for Node", referenced from: Node::Node() in main.cpp.o NOTE: a missing vtable usually means the first non-inline virtual member function has no definition. "vtable for XMLParser", referenced from: XMLParser::XMLParser() in main.cpp.o NOTE: a missing vtable usually means the first non-inline virtual member function has no definition.
Mit static linking linke ich mit target_link_libraries eine .a Datei und damit geht es.
Ich habe es jetzt auch probiert mit einer Factory-Funktion für die Node Klasse. Das hat funktioniert https://github.com/hendrik-weiler/xmlparser-cpp/commit/f5ab976bdfda68d76ecfe831e6c8e31654020af6
-
@hendrik-weiler sagte in Undefined symbols vtable for Node Node::Node() in main.cpp.o bei shared library:
Wenn ich es linke mit
target_include_directories(xmlparserrunner PRIVATE /Volumes/Expansion/xmlparser-cpp/lib/includes) target_link_libraries(xmlparserrunner PRIVATE /Volumes/Expansion/xmlparser-cpp/cmake-build-debug/libxmlparser.dylib)
Bekomme ich diese Fehler:
Das ist merkwürdig. Ich hätte erwartet, dass das genau so klappt wie mit der statischen
.a
-Library. Hier sollte sich der Linker eigentlich den VTable aus der.dylib
holen, wo er ja offensichtlich existiert, wenn ...Ich habe es jetzt auch probiert mit einer Factory-Funktion für die Node Klasse. Das hat funktioniert https://github.com/hendrik-weiler/xmlparser-cpp/commit/f5ab976bdfda68d76ecfe831e6c8e31654020af6
... DAS funktioniert. Als würde der nicht exportiert.
Ich muss aber auch zugeben, dass ich in C++ nur wenig mit dynamischen Bibliotheken mache. Die Projekte, mit denen ich zu tun habe sind meistens statisch gelinkt. Und wenn nicht, dann sind es fast nur C-Bibliotheken.
Ich bin mir auch nicht ganz sicher, ob man auf MacOS auch gegen die dynamische Bibliothek selbst linkt. Unter Windows mit den DLLs muss man z.B. gegen eine Importbibliothek (
.lib
oder.a
) für die DLL linken. Das ist aber so wie du es hier machst z.B. unter Linux mit den.so
-Bibliotheken üblich, daher vermute ich, dass das so in Ordnung ist. Ansonsten gäbe es auch sicher eine andere Fehlermeldung.NOTE: a missing vtable usually means the first non-inline virtual member function has no definition.
Wenn du experimentierfreudig bist, kannst du auch noch was ganz doofes probieren: Statt den virtuellen Destruktor
default
zu machen, probier's mal mit einem expliziten leeren Destruktor in derNode.cpp
. Ich erinnere mich vage an ein ähnlich gelagertes Problem und ich meine, das war eine Sache, die wir probiert hatten. Ich weiß aber nicht mehr, ob das wirklich die Lösung war.Edit: Ich seh grad
Node
hat als Basisklasse keinen virtuellen Destruktor. Gib der mal einen, auch wenn es nur ein Default-Destruktor ist. Sonst passieren komische Sachen - normalerweise aber nicht beim Linken sondern eher wenn man eindelete
einer abgeleiteten Klasse über einen Basisklassen-Pointer macht.
-
@hendrik-weiler Ich habe gerade nur kurz auf dem Smartphone geschaut, aber soweit ich das sehe exportiert deine Bibliothek die Symbole einfach nicht.
Unter Windows gibt es dafür
__declspec(dllexport)
. Unter Linux gibt es soweit ich weiß__attribute__((visibility("default")))
.
Normalerweise definiert man sich ein Makro dafür, das, je nach dem ob die Library kompiliert wird oder inkludiert wird, das entsprechende Export Flag gesetzt wird.Wenn du in der Suchmaschine deiner Wahl nach "Export symbols of shared c++ library" suchst, solltest du dazu jede Menge finden.
-
@Schlangenmensch sagte in Undefined symbols vtable for Node Node::Node() in main.cpp.o bei shared library:
@hendrik-weiler Ich habe gerade nur kurz auf dem Smartphone geschaut, aber soweit ich das sehe exportiert deine Bibliothek die Symbole einfach nicht.
Ich würde davon ausgehen, dass Symbole sehr wohl exportiert werden, da sonst der Fehler z.B. auch bei der Konstruktor-Funktion auftreten würde. Es fehlt der VTable, und den kann man ohne Tricksereien sowieso nicht explizit exportieren.
-
@Finnegan Bei den im GitHub eingecheckten Branches wird aus Node nichts exportiert. Und im ersten Beitrag stehenden Code auch nicht. Kann natürlich sein, dass der Export beim Pasten verloren gegangen ist und ins GitHub nicht gepusht wurde, weil es nicht funktioniert, aber der eine Branche heißt extra "Shared Library".
Der XMLParser wird unter Windows exportiert, aber der dazugehörige Import fehlt. Und für andere Systeme ist das EXPORT Macro leer.
Daher ist da, zumindest was den eingecheckten Code betrifft, was im Argen. Ob das ursächlich für den Fehler ist, weiß ich nicht.
-
@Schlangenmensch sagte in Undefined symbols vtable for Node Node::Node() in main.cpp.o bei shared library:
Unter Linux gibt es soweit ich weiß
__attribute__((visibility("default")))
.Das heißt "default", weil es auch gemacht wird, wenn nix da steht (sofern nicht via Compiler-Flag der Default anders eingestellt wurde). Ich weiß zwar nicht, wie es unter MacOS ist, würde mich aber nur wenig wundern, wenn dort auch alles exportiert wird sofern man es nicht explizit unterbindet. Würde tatsächlich gar nichts exportiert, gäbe es nicht nur beim VTable "Undefinded symbols".
-
Ich habe es jetzt getestet indem ich der Klasse Node das Export Macro hinzugefügt habe:
#define EXPORT __attribute__((visibility("default")))
und so konnte ich auch ohne Factory-Funktion eine neue Instanz erzeugen.
Edit: Ich habe es jetzt noch auf Ubuntu und Windows 11 getestet. Auf Ubuntu kommt folgender Fehler:
xmlparser-runner.cpp:(.text._ZN4NodeC2Ev[_ZN4NodeC5Ev]+0xc): undefined reference to `vtable for Node' /usr/bin/ld: xmlparser-runner.cpp:(.text._ZN4NodeC2Ev[_ZN4NodeC5Ev]+0x10): undefined reference to `vtable for Node'
In windows 11:
\Programs\CLion\bin\mingw\bin/ld.exe: CMakeFiles/xmlparser_runner.dir/main.cpp.obj:main.cpp:(.rdata$.refptr._ZTV4Node[.refptr._ZTV4Node]+0x0): undefined reference to `vtable for Node'
Die Factory Methode ging aber auf allen Systemen.
-
@hendrik-weiler sagte in Undefined symbols vtable for Node Node::Node() in main.cpp.o bei shared library:
Ich habe es jetzt getestet indem ich der Klasse Node das Export Macro hinzugefügt habe:
#define EXPORT attribute((visibility("default")))und so konnte ich auch ohne Factory-Funktion eine neue Instanz erzeugen.
Wenn das zutrifft, dann nehme ich zurück, was ich in meiner Antwort an @Schlangenmensch geschrieben habe, bin dann aber ganz schön verwirrt. Eigentlich hatte ich ja mental abgespeichert, dass unter unixoiden Systemen grundsätzlich erstmal alle Symbole exportiert werden und man das mit
__attribute__((visibility("hidden")))
selektiv unterbindet - es sei denn man gibt dem Compiler den-fvisibility=hidden
-Parameter mit, dann wird standardmäßig nichts exportiert (vielleicht wird das ja irgendwo gemacht?). Auch frage ich mich, weshalb z.B. dernode->toXML()
-Aufruf inmain.cpp
kein "Undefined symbol" erzeugt, wenn die Klasse explizit exportiert werden muss. Diese Member-Funktion liegt ja in der Bibliothek, wenn ich das richtig sehe. Seltsam.Die Factory Methode ging aber auf allen Systemen.
Das wäre meine Wahl für eine Shared Library: Erstellen und Zerstören der Objekte in Bibliotheks-Funktionen. Immerhin gibt es auch Systeme/Setups, wo die Shared Libraries einen eigenen Heap haben können, da ist es besser wenn das alles über die Lib läuft. Ein
delete
oderfree
auf einen Speicherbereich aus dem falschen Heap wird kein Happy End haben
-
@Finnegan sagte in Undefined symbols vtable for Node Node::Node() in main.cpp.o bei shared library:
Wenn das zutrifft, dann nehme ich zurück, was ich in meiner Antwort an @Schlangenmensch geschrieben habe, bin dann aber ganz schön verwirrt. Eigentlich hatte ich ja mental abgespeichert, dass unter unixoiden Systemen grundsätzlich erstmal alle Symbole exportiert werden und man das mit
__attribute__((visibility("hidden")))
selektiv unterbindet - es sei denn man gibt dem Compiler den-fvisibility=hidden
-Parameter mit, dann wird standardmäßig nichts exportiert (vielleicht wird das ja irgendwo gemacht?)AFAIK ist das mit gcc immer noch der standard das alles exportiert wird.
hendrik.weiler nutzt cmake. Gut möglich das cmake dem compiler parameter -fvisibility=hidden setzt
Eventuell durch das aktivieren des C++20 standards durch das cmake makroset(CMAKE_CXX_STANDARD 20)
Um zu sehen welche parameter an den compiler übergeben werden kann man den verbose modus nutzen.
z.b. so:make VERBOSE=1
-
Ich habe
make VERBOSE=1
in ubuntu ausprobiert. Das wurde geloggt:/home/parallels/.local/lib/python3.10/site-packages/cmake/data/bin/cmake -S/home/parallels/Documents/xmlparser-cpp-shared-library -B/home/parallels/Documents/xmlparser-cpp-shared-library --check-build-system CMakeFiles/Makefile.cmake 0 Re-run cmake file: Makefile older than: CMakeLists.txt -- Configuring done (0.0s) -- Generating done (0.0s) -- Build files have been written to: /home/parallels/Documents/xmlparser-cpp-shared-library /home/parallels/.local/lib/python3.10/site-packages/cmake/data/bin/cmake -E cmake_progress_start /home/parallels/Documents/xmlparser-cpp-shared-library/CMakeFiles /home/parallels/Documents/xmlparser-cpp-shared-library//CMakeFiles/progress.marks make -f CMakeFiles/Makefile2 all make[1]: Entering directory '/home/parallels/Documents/xmlparser-cpp-shared-library' make -f CMakeFiles/xmlparser.dir/build.make CMakeFiles/xmlparser.dir/depend make[2]: Entering directory '/home/parallels/Documents/xmlparser-cpp-shared-library' cd /home/parallels/Documents/xmlparser-cpp-shared-library && /home/parallels/.local/lib/python3.10/site-packages/cmake/data/bin/cmake -E cmake_depends "Unix Makefiles" /home/parallels/Documents/xmlparser-cpp-shared-library /home/parallels/Documents/xmlparser-cpp-shared-library /home/parallels/Documents/xmlparser-cpp-shared-library /home/parallels/Documents/xmlparser-cpp-shared-library /home/parallels/Documents/xmlparser-cpp-shared-library/CMakeFiles/xmlparser.dir/DependInfo.cmake "--color=" Dependencies file "CMakeFiles/xmlparser.dir/lib/Document.cpp.o.d" is newer than depends file "/home/parallels/Documents/xmlparser-cpp-shared-library/CMakeFiles/xmlparser.dir/compiler_depend.internal". Dependencies file "CMakeFiles/xmlparser.dir/lib/Lexer.cpp.o.d" is newer than depends file "/home/parallels/Documents/xmlparser-cpp-shared-library/CMakeFiles/xmlparser.dir/compiler_depend.internal". Dependencies file "CMakeFiles/xmlparser.dir/lib/Parser.cpp.o.d" is newer than depends file "/home/parallels/Documents/xmlparser-cpp-shared-library/CMakeFiles/xmlparser.dir/compiler_depend.internal". Dependencies file "CMakeFiles/xmlparser.dir/lib/Token.cpp.o.d" is newer than depends file "/home/parallels/Documents/xmlparser-cpp-shared-library/CMakeFiles/xmlparser.dir/compiler_depend.internal". Dependencies file "CMakeFiles/xmlparser.dir/lib/includes/Node.cpp.o.d" is newer than depends file "/home/parallels/Documents/xmlparser-cpp-shared-library/CMakeFiles/xmlparser.dir/compiler_depend.internal". Dependencies file "CMakeFiles/xmlparser.dir/lib/includes/XMLParser.cpp.o.d" is newer than depends file "/home/parallels/Documents/xmlparser-cpp-shared-library/CMakeFiles/xmlparser.dir/compiler_depend.internal". Dependencies file "CMakeFiles/xmlparser.dir/library.cpp.o.d" is newer than depends file "/home/parallels/Documents/xmlparser-cpp-shared-library/CMakeFiles/xmlparser.dir/compiler_depend.internal". Consolidate compiler generated dependencies of target xmlparser make[2]: Leaving directory '/home/parallels/Documents/xmlparser-cpp-shared-library' make -f CMakeFiles/xmlparser.dir/build.make CMakeFiles/xmlparser.dir/build make[2]: Entering directory '/home/parallels/Documents/xmlparser-cpp-shared-library' [ 12%] Building CXX object CMakeFiles/xmlparser.dir/library.cpp.o /usr/bin/c++ -Dxmlparser_EXPORTS -std=gnu++20 -fPIC -MD -MT CMakeFiles/xmlparser.dir/library.cpp.o -MF CMakeFiles/xmlparser.dir/library.cpp.o.d -o CMakeFiles/xmlparser.dir/library.cpp.o -c /home/parallels/Documents/xmlparser-cpp-shared-library/library.cpp [ 25%] Building CXX object CMakeFiles/xmlparser.dir/lib/includes/XMLParser.cpp.o /usr/bin/c++ -Dxmlparser_EXPORTS -std=gnu++20 -fPIC -MD -MT CMakeFiles/xmlparser.dir/lib/includes/XMLParser.cpp.o -MF CMakeFiles/xmlparser.dir/lib/includes/XMLParser.cpp.o.d -o CMakeFiles/xmlparser.dir/lib/includes/XMLParser.cpp.o -c /home/parallels/Documents/xmlparser-cpp-shared-library/lib/includes/XMLParser.cpp [ 37%] Building CXX object CMakeFiles/xmlparser.dir/lib/Parser.cpp.o /usr/bin/c++ -Dxmlparser_EXPORTS -std=gnu++20 -fPIC -MD -MT CMakeFiles/xmlparser.dir/lib/Parser.cpp.o -MF CMakeFiles/xmlparser.dir/lib/Parser.cpp.o.d -o CMakeFiles/xmlparser.dir/lib/Parser.cpp.o -c /home/parallels/Documents/xmlparser-cpp-shared-library/lib/Parser.cpp [ 50%] Building CXX object CMakeFiles/xmlparser.dir/lib/Document.cpp.o /usr/bin/c++ -Dxmlparser_EXPORTS -std=gnu++20 -fPIC -MD -MT CMakeFiles/xmlparser.dir/lib/Document.cpp.o -MF CMakeFiles/xmlparser.dir/lib/Document.cpp.o.d -o CMakeFiles/xmlparser.dir/lib/Document.cpp.o -c /home/parallels/Documents/xmlparser-cpp-shared-library/lib/Document.cpp [ 62%] Building CXX object CMakeFiles/xmlparser.dir/lib/includes/Node.cpp.o /usr/bin/c++ -Dxmlparser_EXPORTS -std=gnu++20 -fPIC -MD -MT CMakeFiles/xmlparser.dir/lib/includes/Node.cpp.o -MF CMakeFiles/xmlparser.dir/lib/includes/Node.cpp.o.d -o CMakeFiles/xmlparser.dir/lib/includes/Node.cpp.o -c /home/parallels/Documents/xmlparser-cpp-shared-library/lib/includes/Node.cpp [ 75%] Linking CXX shared library libxmlparser.so /home/parallels/.local/lib/python3.10/site-packages/cmake/data/bin/cmake -E cmake_link_script CMakeFiles/xmlparser.dir/link.txt --verbose=1 /usr/bin/c++ -fPIC -shared -Wl,-soname,libxmlparser.so.0 -o libxmlparser.so.0.1.4 CMakeFiles/xmlparser.dir/library.cpp.o CMakeFiles/xmlparser.dir/lib/includes/XMLParser.cpp.o CMakeFiles/xmlparser.dir/lib/Token.cpp.o CMakeFiles/xmlparser.dir/lib/Lexer.cpp.o CMakeFiles/xmlparser.dir/lib/Parser.cpp.o CMakeFiles/xmlparser.dir/lib/Document.cpp.o CMakeFiles/xmlparser.dir/lib/includes/Node.cpp.o /home/parallels/.local/lib/python3.10/site-packages/cmake/data/bin/cmake -E cmake_symlink_library libxmlparser.so.0.1.4 libxmlparser.so.0 libxmlparser.so make[2]: Leaving directory '/home/parallels/Documents/xmlparser-cpp-shared-library' [100%] Built target xmlparser make[1]: Leaving directory '/home/parallels/Documents/xmlparser-cpp-shared-library' /home/parallels/.local/lib/python3.10/site-packages/cmake/data/bin/cmake -E cmake_progress_start /home/parallels/Documents/xmlparser-cpp-shared-library/CMakeFiles 0
Da steht auch nichts von dem flag drin.
Ich habe auch versucht den Compiler-Flag in cmake zu setzen mit:target_compile_options(xmlparser PRIVATE -fvisibility=default)
Das Projekt wurde zwar neu erstellt aber die Instanziierung wirft wieder den gleichen Fehler:
xmlparser-runner.cpp:(.text._ZN4NodeC2Ev[_ZN4NodeC5Ev]+0xc): undefined reference to `vtable for Node' /usr/bin/ld: xmlparser-runner.cpp:(.text._ZN4NodeC2Ev[_ZN4NodeC5Ev]+0x10): undefined reference to `vtable for Node'
-
@hendrik-weiler sagte in Undefined symbols vtable for Node Node::Node() in main.cpp.o bei shared library:
Das Projekt wurde zwar neu erstellt aber die Instanziierung wirft wieder den gleichen Fehler:
xmlparser-runner.cpp:(.text._ZN4NodeC2Ev[_ZN4NodeC5Ev]+0xc): undefined reference to `vtable for Node' /usr/bin/ld: xmlparser-runner.cpp:(.text._ZN4NodeC2Ev[_ZN4NodeC5Ev]+0x10): undefined reference to `vtable for Node'
Und nur diesen Fehler? Mein Argument war ja, dass wenn nichts exportiert wird, auch ein Aufruf wie
node->toXML()
einen "Undefined Symbol"-Fehler verursachen sollte. Die nicht vom Linker auflösbaren Symbole werden eigentlich alle aufgelistet (im Gegensatz zu Fehlern beim Kompilieren, wo meist nach dem ersten Fehler abgebrochen wird).Edit: Moment, mir wird grad bewusst, dass der bei einem virtuellen Member ja gar nicht weiß, dass sich
node->toXML()
auf eine Funktion in der Bibliothek bezieht, wenn er den Inhalt des VTable nicht kennt. Gibt es vielleicht noch irgendeine nicht-virtuelle Bibliotheksfunktion, die du aufrufst? Die müsste dann mit-fvisibility=hidden
auch auftauchen, wenn das__attribute__((visibility("default")))
fehlt.