MFC DLL erstellen - Unresolved External Linker Error - nur eine Methode kann aufgerufen werden
-
Hallo
Ich bin grade dabei eine MFC DLL zu erstellen. Paar Details: Ich arbeite mit Visual Studio 2013. Die DLL erstelle ich in C++ und die Anwendung mit der die DLL aufgerufen werden soll ist ebenfalls in C++ geschrieben.
Mein Vorgehen war so, dass ich ein neues Projekt erstellt habe (MFC DLL mit C++) und eine kleine Klasse geschrieben habe.
Header File:
class CIO_Manager { public: __declspec(dllexport) CIO_Manager(); __declspec(dllexport) virtual ~CIO_Manager(); __declspec(dllexport) void Io_DummyA(char*& c); __declspec(dllexport) void Io_DummyB(CString& c); };
CPP File:
#include "stdafx.h" #include <afx.h> #include "Io_Manager.h" CIO_Manager::CIO_Manager() { } CIO_Manager::~CIO_Manager() { } void CIO_Manager::Io_DummyA(char*& c) { CString s = _T("Kraut und rĂŒben"); int L = s.GetLength() + 1; CT2A ascii(s); c = new char[L]; strncpy_s(c, L, ascii, L); c[L - 1] = 0; } void CIO_Manager::Io_DummyB(CString& c) { c = c + _T(":hallo"); }
Dann habe ich den Header zu meiner "Clienten"-Anwendung hinzugefĂŒgt. Die erzeugte DLL zu der Exe gepackt und zusĂ€tzlich die .Lib als Additional Dependency bekannt gemacht in den Einstellungen.
(Kann mir jemand vielleicht erklÀren wie Lib und DLL zusammenhÀngen, braucht man beides oder eines nur im Debug Modus?)
Der Code aus der Anwendung:
CIO_Manager manager; CString test = "test"; char* cSave = NULL; manager.Io_DummyA(cSave); manager.Io_DummyB(test);
Als ich nun getestet habe, ob ich die Methoden aus der DLL aufrufen kann, konnte ich die Methode "IO_DummyA" ohne weiteres aufrufen, bei der Methode "IO_DummyB" bekomme ich allerdings ein Unresolved External Linker Fehler.
Sobald ich mehr als eine Methode in der DLL habe bekomme ich unresolved External
Hat jemand vielleicht eine Idee woran das liegen könnte oder einen Hinweis was ich vergessen / falsch gemacht haben könnte?
Schon mal vielen lieben Dank fĂŒr eure Hilfe!
-
Das declspec sollte fĂŒr die Klasse gelten. AuĂerdem sollte die Benutzerseite auch ein import machen. MS empfiehlt dafĂŒr eine Makrokonstruktion:
#ifdef _EXPORTING #define CLASS_DECLSPEC __declspec(dllexport) #else #define CLASS_DECLSPEC __declspec(dllimport) #endif class CLASS_DECLSPEC CIO_Manager { public: CIO_Manager(); virtual ~CIO_Manager(); void Io_DummyA(char*& c); void Io_DummyB(CString& c); };
-
Wenn deine DLL gebaut wird, musst du
__declspec(dllexport)
verwenden, wenn die Funktionen aus der DLL importiert werden sollen, musst du__declspec(dllimport)
verwenden.
Normalerweise lĂ€sst man dafĂŒr in den Projekteinstellungen deiner DLL irgendein Flag (sagen wir einfach,IO_MANAGER
) in den PrĂ€prozessoreinstellungen definieren, dann prĂŒft man im Code, ob das Flag definiert ist. Wenn dem so ist, werden die Funktionen exportiert, wenn nicht, werden sie importiert:#if defined(IO_MANAGER) # define IO_MANAGER_DLL_FUNCTION __declspec(dllexport) #else # define IO_MANAGER_DLL_FUNCTION __declspec(dllimport) #endif
Dann packst du
IO_MANAGER_DLL_FUNCTION
vor jede Funktionsdeklaration oder vor jede Klasse, die in der DLL landen soll:class IO_MANAGER_DLL_FUNCTION CIO_Manager { public: CIO_Manager(); ~CIO_Manager(); void Io_DummyA(char*& c); void Io_DummyB(CString& c); };
So kannst du den gleichen Header in unterschiedlichen Projekten verwenden, ohne manuell nach einer Ănderung wieder was an den Export-/Import-Einstellungen drehen zu mĂŒssen.
EDIT: Zu der LIB/DLL-Geschichte ... ugh. OK. Wenn du eine statische Lib baust, dann hat die Datei die Endung "LIB". Das ist immer eine statische LIB, sprich, damit hantiert der Linker rum. In einer Lib kann jetzt der komplette Code deiner Lib drinstehen. Muss aber nicht. Denn wenn du eine dynamische Bibliothek baust, dann bekommst du eine LIB UND eine DLL. In einem solchen Fall hantiert der Linker immer noch mit der LIB-Datei rum, aber diesmal steht da im wesentlichen nur drin, dass er die Fresse halten soll, weil dein Code in der DLL X zum Startzeitpunkt deiner Anwendung eingeladen wird. In der DLL ist dann dein eigentlicher Code drin.
Die LIB-Datei brauchst du IMMER, wenn du deine Anwendung neu bauen willst - weil, sagen wir mal, sich das ABI/API geÀndert hat und jetzt nicht mehr mit den gleichen Parametern oder Strukturausrichtungen gearbeitet werden kann. Die DLL brauchst du dann, wenn du die Anwendung auch starten willst.
Klar soweit?
EDIT 2: Ăbrigens, noch eine Sache - die Linkage der Funktionen. MS hat sich da einen wunderschönen Schwachsinn ausgedacht und gibt Funktionen, die statisch exportiert werden (die also ĂŒber eine LIB und nicht ĂŒber eine DLL kommen), andere Namen - DLL-Funktionen bekommen das PrĂ€fix "
__imp_
", statische Bibliotheksfunktionen bekommen unter x86 ein "_
", und unter x64 gar kein PrÀfix.Warum sage ich das? Weil du dem Linker irgendwie mitteilen musst, dass wenn deine Bibliothek auf einmal statisch gelinkt wurde, dass er dann nicht mehr nach den Funktionen mit
__imp_
-PrÀfix suchen soll. Sonst kann der Linker, wenn er deine Anwendung (nicht die LIB) baut, deine Funktionen nicht finden und stellt die Latschen auf. Wenn statisch gebaut wird, darf im Header spÀter nix mitdllimport
stehen.
-
dachschaden schrieb:
... Denn wenn du eine dynamische Bibliothek baust, dann bekommst du eine LIB
UND eine DLL. In einem solchen Fall hantiert der Linker immer noch mit der LIB-Datei rum, aber
diesmal steht da im wesentlichen nur drin, dass er die Fresse halten soll, weil dein Code in der
DLL X zum Startzeitpunkt deiner Anwendung eingeladen wird. In der DLL ist dann dein eigentlicher
Code drin.Die LIB-Datei brauchst du IMMER, wenn du deine Anwendung neu bauen willst - weil, sagen
wir mal, sich das ABI/API geÀndert hat und jetzt nicht mehr mit den gleichen Parametern oder
Strukturausrichtungen gearbeitet werden kann. Die DLL brauchst du dann, wenn du die Anwendung
auch starten willst.1. Das trifft es wohl nicht ganz. NatĂŒrlich macht eine Lib sinvolle Dinge wie z.B. dafĂŒr zu sorgen
das die DLL geladen wird und die enthaltenen Einsprungstellen auflösen. Und weil das so ist, ist auch
der Linker zufrieden ...Woher soll die Exe wissen welche DLL sie laden soll und wie z.B. enthaltene Funktionen aufzurufen sind?
Also etwas mehr als "Fresse halten" mĂŒsste da schon kommen.2. Die Lib Datei braucht man nicht unbedingt, da man die DLL natĂŒrlich auch mit Systemaufrufen
selbst laden könnte. Die DLL wird dann erst benötigt, wenn man sie selbst lÀd und nicht schon beim
Start der Exe.3. Das von dir ins lĂ€cherliche gezogene "namewrangling" lĂ€sst sich natĂŒrlich auch abschalten.
(Ob das eine gute Idee ist, muss jeder selbst wissen ..)
-
merano schrieb:
1. Das trifft es wohl nicht ganz. NatĂŒrlich macht eine Lib sinvolle Dinge wie z.B. dafĂŒr zu sorgen
das die DLL geladen wird und die enthaltenen Einsprungstellen auflösen. Und weil das so ist, ist auch
der Linker zufrieden ...dachschaden schrieb:
dass er die Fresse halten soll, weil dein Code in der DLL X zum Startzeitpunkt deiner Anwendung eingeladen wird.
Nichts zu danken.
merano schrieb:
2. Die Lib Datei braucht man nicht unbedingt, da man die DLL natĂŒrlich auch mit Systemaufrufen
selbst laden könnte. Die DLL wird dann erst benötigt, wenn man sie selbst lÀd und nicht schon beim
Start der Exe.OK, stimmt natĂŒrlich. Ist aber aufwendig und blöd zu machen. Und anstatt Funktionsprototypen hast du dann Funktionszeigertypen UND Funktionszeiger. Manchmal geht es nicht anders. Aber wenn es anders geht, sollte man es auch anders machen.
merano schrieb:
3. Das von dir ins lĂ€cherliche gezogene "namewrangling" lĂ€sst sich natĂŒrlich auch abschalten.
Dann gib mal Beispiele, warum das nicht lĂ€cherlich sein soll. Denn .a- und .so-Dateien unter Linux kennen sowas nicht. FĂŒr den GCC muss ich nicht noch ein Extra-Flag angeben, welches sagt, dass die Namen andere sind als die, nach denen er normalerweise sucht. Und nein, mir geht es nicht um FunktionsĂŒberladungen. FĂŒr die ist das "namewrangling" nachvollziehbar. Aber doch bitte nicht fĂŒr Code, der unterschiedlich exportiert wurde.
Oder gibt es einen rationalen Grund fĂŒr diese PrĂ€fix? Denn hier finde ich nix zu.
-
dachschaden schrieb:
OK, stimmt natĂŒrlich. Ist aber aufwendig und blöd zu machen. Und anstatt Funktionsprototypen hast du dann Funktionszeigertypen UND Funktionszeiger. Manchmal geht es nicht anders. Aber wenn es anders geht, sollte man es auch anders machen.
Nö.
Ich brauche nur eine Funktion, die mir einen Interface Zeiger zurĂŒckgibt und schon habe ich das komplexeste Interface, dass ich mir vorstellen kann...
Das mache ich oft genug. Dann benötige ich keine LIB, lade die DLL on Demand und habe immer den gleichen Prozedur Einsprungpunkt...
-
Martin Richter schrieb:
Ich brauche nur eine Funktion, die mir einen Interface Zeiger zurĂŒckgibt und schon habe ich das komplexeste Interface, dass ich mir vorstellen kann...
Das mache ich oft genug. Dann benötige ich keine LIB, lade die DLL on Demand und habe immer den gleichen Prozedur Einsprungpunkt...typedef return_type(CALLING_CONVENTION*function_ptr_1_t)(all_my_parameters); typedef return_type(CALLING_CONVENTION*function_ptr_2_t)(all_my_parameters); typedef return_type(CALLING_CONVENTION*function_ptr_3_t)(all_my_parameters); typedef return_type(CALLING_CONVENTION*function_ptr_4_t)(all_my_parameters); typedef return_type(CALLING_CONVENTION*function_ptr_5_t)(all_my_parameters); function_ptr_1_t function_ptr_1; function_ptr_2_t function_ptr_2; function_ptr_3_t function_ptr_3; function_ptr_4_t function_ptr_4; function_ptr_5_t function_ptr_5; int main(void) { int exit_code = EXIT_FAILURE; HMODULE my_dll; if(NULL == (my_dll = LoadLibraryA("my_dll.dll"))) goto PROG_END; function_ptr_1 = (function_ptr_1_t)GetProcAddress(my_dll,"function_1"); function_ptr_2 = (function_ptr_2_t)GetProcAddress(my_dll,"function_2"); function_ptr_3 = (function_ptr_3_t)GetProcAddress(my_dll,"function_3"); function_ptr_4 = (function_ptr_4_t)GetProcAddress(my_dll,"function_4"); function_ptr_5 = (function_ptr_5_t)GetProcAddress(my_dll,"function_5"); if(!function_ptr_1 || !function_ptr_2 || !function_ptr_3 || !function_ptr_4 || !function_ptr_5) goto PROG_END; /*Mach was damit.*/ exit_code = EXIT_SUCCESS; PROG_END: if(my_dll) FreeLibrary(my_dll); exit(exit_success); }
Und das ist noch ein relativ einfaches Interface. Verglichen mit dem Linken zur Linkzeit ist solcher Code hÀsslich.
Ja, klar kannst du das wegabstrahieren. Aber hÀsslicher Code wird nicht weniger hÀsslich, nur weil du ihn nicht mehr siehst. Er stört dich nur nicht mehr.EDIT: Kleiner Tippfehler - den Code habe ich so schnell abgetippt.
EDIT 2: Uhm, wobei es natĂŒrlich sein kann, dass MS es genauso macht in ihren Startup-Code. So genau kenn ich mich mit Windows jetzt auch nicht aus. :p
-
Nö! Du hast es nicht verstanden.
Ich habe eine Funktion, die ein Interface auf eine Klasse zurĂŒckgibt...Ich brauche also nur ein GetProcAddress und einen Aufruf der Funktion und bekomme einen Zeiger auf eine Klasse mit x Methoden zurĂŒck. Die Klasse ist pure virtuell definiert, also Ă€hnlich wie bei COM.