Ich bin in der DLL Hell :-(
-
Ich versuche mit den Borland C++ Builder ein .dll zu erstellen, welche jeder (auch im Visual Studio) verwenden kann.
Die .dll und ein Testprogramm unter Borland funktionieren optimal. Leider kann ich die .dll nicht im VS aufrufen. Der .DLL Export Viewer zeigt, dass aus meiner Funktion
__declspec(dllexport) __stdcall int GetTestInt(void){return 44;};@GetTestInt$qqvs
wurde.Bei den Compiler-Settings habe ich unter Output u. Source nichts angeklickt und Language compliance = Borland (alle anderen gehen nicht).
Im VS bekomme ich mit LoadLibrary einen pointer aber GetProcAddress liefert NULL, egal ob ich es mit GetTestInt oder @GetTestInt$qqvs aufrufe.
Q: Wie kann muss ich die .dll gestalten damit die Schnittstellen nicht @xxx$qqvs haben sondern den wirklichen Funktionsnamen. (Ich nehme an, dass dann auch der Aufruf unter VS geht).
Danke,
Mario
-
Hallo,
Versuchst mal so
extern "C" __declspec(dllexport) int GetTestInt(void){return 44;};Dann wird die Funktion mit einer C-Signatur exportiert und die Parameterliste ($...) fehlt. Dann kannst du beim Import einfach den Funktionsnamen (mit _ davor) angeben.
Ciao
-
Das hat mit "DLL Hell" nichts zu tun.
-
Gibt es zum Importieren nicht die Spezifikation "__declspec(dllimport)"? Die benötigst du afaik im Hauptprogramm, um die DLL-Funktion einbinden zu können.
Deshalb beginne DLL-Header oft so:
#ifdef MY_DLL_IMPL #define MY_DLL_DEC __declspec(dllexport) #else #define MY_DLL_DEC __declspec(dllimport) #endif MY_DLL_DEC inf myFunc(void); //...(und die CPP-Datei zur Implementation definiert als erstes MY_DLL_IMPL, bevor sie den Header included)
-
Das brauchst du aber nur, wenn du statisch bindest. Dynamisch ist das unnötig.
Bei statischer Bindung hast du ja noch das Problem mit den libs welche du dann noch in das Microsoft-Format konvertieren mußt (COFF). Ich würd das hier lieber vermeiden.
-
@Braunstein:
Danke, das mit extern "C" funktioniert, jetzt erkennt man eine schöne Schnittstellt im DLL-Viewer. Das Problem ist nur, dass ich eine Klasse exportieren möchte und vor class kann ich nicht export "C" hinschreiben (vermutlich weil Klassen nicht C sind).Aus diesem Grund habe ich folgendes für meine Klasse versucht:
class __declspec(dllexport) __stdcall MyClass{};Diese Art führt jedoch zu unmöglichen .dll Schnittstellen.
Q: Wie exportiert man eine Klasse korrekt?
Einbinden möchte ich die .dll im VS ohne .h oder .lib nach folgender Vorgabe:
[url]
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/_core_link_explicitly.asp
[/url]Zu diesem Zweck bin ich wie folgt vorgegangen:
HINSTANCE hDll = LoadLibrary("MyDll.dll"); FARPROC pFunc = GetProcAddress(hDll, "GetTestInt"); // der Compiler hat gesagt ich muss FARPROC nehmen.... // jetzt habe ich einen Pointer, aber wohin...Q: Was müsste ich mit pFunc anfangen damit ich den Wert 44 (wie in der .dll programmiert) bekomme? (int res = pFunc() geht nicht)
@hinweis: Danke für deinen großartigen Hinweis.
-
// im nem header typedef int (*PFUN_GET_TEST_INT) (); PFUN_GET_TEST_INT GetTestInt; // in deiner cpp (bindet obigen header ein) HINSTANCE hDll = LoadLibrary("MyDll.dll"); GetTestInt = (PFUN_GET_TEST_INT) ::GetProcAddress(dDll, "_GetTestInt"); // verwendung int a = GetTestInt();
-
Ob sich Klassen so direkt von BCB nach MS exportieren lassen bezweifle ich mal. Bastel dir doch zwei Funktionen, von denen eine dir einen Pointer auf diese Klasse liefert und die andere diesen Pointer dann löscht. Du mußt dann natürlich die Klassendeklaration mit einbinden.
Bezüglich der Funktion, weiß ich jetzt auch nicht was ein FARPROC ist.
Ich würde es so machen.
1. ein typedef auf deine Funktion erzeugentypedef int myfunctType();2. Zuweisen
myfunctType* myfunct = GetProcAddress(hDll, "_GetTestInt");3. Testen
if( myfunct == NULL ) { // Fehelerbehandlung }4. aufrufen
int res = myfunct();[edit]ZU spät.
Man sollte zwischen drin halt nicht telefonieren.[edit]
-
DANKE für eure Unterstützung, es geht!
Jetzt brauche ich nur noch einen Weg finden eine Klasse zugänglich zu machen (sonst muss ich mit einem neuen Konzept von vorne beginnen).
cu,
Mario
-
Wie schon gesagt, kannst du das Einbinden der Klassen über Pointer machen. Irgandwie so
// In Headerdatei class MyClass { // Deklarationen }; //cpp von dll extern "C" __declspec(dllexport) MyClass * get_myclass() { MyClass *test= new MyClass; return test; } //--------------------------------------------------------------------------- extern "C" __declspec(dllexport) void free_myclass(MyClass * cc) { delete cc; } //---------------------------------------------------------------------------Die Funktionen geben dir dann einen Pointer auf deine gewünschte Klasse. Du mußt dann natürlich die Headerdatei mit den Deklarationen mit einbinden. Wenn du auch das verhindern willst, kannst du eine Art Wrapperklasse mit den public-Funktionen bereitstellen, die wiederum deine KLasse verwendet (siehe PIMPL). Dann brauchst du nur noch diese Headerdatei einzubinden.
Die free-myclass Funktion ist notwendig, da der Pointer im Speicherbereich der dll erzeugt wird und ein delete im Aufrufenden Programm zu Fehlern führen würde.
-
@Braunstein:
Dein Vorschlag scheint zu funktionieren, ich muss vermutlich nicht von vorne beginnen!Die .dll mit deinen zwei Funktionen hat compiliert.
Ich habe bei meinem VS-Projekt wo ich die .dll aufrufe jetzt das .h File eingebunden und folgendes programmiert:#include "MyDLL.h" typedef MyDllClass (*PGET_MYDLLCLASS); PGET_MYDLLCLASS GetMyClass; . . . GetMyClass = (PGET_MYDLLCLASS)::GetProcAddress(hDll, "get_myclass"); // Cool, jetzt habe ich einen Pointer auf meine dll klasseEine kleine Frage ist jedoch noch offen:
Q: Wenn GetMyClass jetzt ein Pointer auf die Klasse in meiner dll ist warum funktioniert dann der -> operator nicht? (ich habe GetMyClass->GetData(); oder auch MyDllClass * pDll = GetMyClass; probiert)DANKE
Mario
-
Du hast jetzt lediglich eine Funktion, die dir einen Pointer auf deine Klasse liefern kann.
Du mußt dann also so etwas machen.GetMyClass = (PGET_MYDLLCLASS)::GetProcAddress(hDll, "_get_myclass"); FreeMyClass = (PFREE_MYDLLCLASS)::GetProcAddress(hDll, "_free_myclass"); MyClass *my = GetMyClass(); // dann mit my arbeiten ... // zum Schluß nicht vergessen FreeMyClass(my);Bitte vergess auch die Fehlerbehandlung (testen auf Nullpointer) nach GetProcAddress nicht.
-
Du hast natürlich wieder recht:
MyClass *my = GetMyClass();
funktioniert!Wenn ich jetzt MyClass-> schreibe kommt das Menü mit allen Methoden der Klasse.
....nur...
egal welche Methode ich aufrufen will, ich bekomme immer einen:
error LNK2001: unresovled external symbol......
fatal error LNK1120: 1 unresolved externals... dabei bin ich schon so nahe am Ziel

-
Ist die cpp, in der die Definition deiner Klasse steht mit in die dll gelinkt?
-
Noch was:
Die Funktionen deiner Klasse sollten als Parameter nur POD-Typen (also char, int etc.) bzw. Pointer enthalten. Bei Übergaben von Klassen per Referenz kann es zu Problemen mit unterschiedlichen Bibliotheksimplementationen kommen.
Ich hatte da mal ein Problem mit einer Referenz auf std::string zwischen BCB5 und 6.
-
Ich bin der Meinung, dass mein .cpp File passt, es geht ja unter Borland, nur unter VS nicht.....
Mein .h File enthält eigentlich keine besonderen, außer selbst definierte, Typen:
#ifndef ENCODER_H #define ENCODER_H #define READ_TEMP 0 #define READ_ADC 3 #define SIUSIZE 4 #define OTPSIZE 4 #define SSISIZE 3 #define FESIZE 8 #define I2C_SIU 0x01 #define I2C_FE 0x02 #define I2C_ALSD 0x04 #define I2C_CORDIC 0x08 #define I2C_OFFSET 0x10 #define I2C_AGC 0x20 #define I2C_SYSTEM 0x40 //--------------------------------------------------------------------------- /** Error-codes used as return values for the usb-interfacing. */ typedef enum { ERROR_NO_ERR, /**< No error occured. */ ERROR_CMD_FAILED, /**< The uC encoutered an error. */ ERROR_CANNOT_CONNECT, /**< The demoboard is probably not plugged in. */ ERROR_NO_CONN, /**< Transfer function was called on a closed connection */ ERROR_TRANSMIT, /**< An error occured during the transmission of a command. */ ERROR_RECEIVE, /**< An error occured during receiption. */ ERROR_ILLEGAL_ARGUMENTS, /**< The arguments are out of bounds. */ ERROR_GENERIC /**< An unspecified error occured. */ } UsbComError; typedef enum {FRONTEND, ALSD, CORDIC, OFFSET, AUTOGC, SYSTEM, SIU } I2cReg; typedef enum {AS5040, AS5043, AS5045} EncoderType; typedef enum {RED, GREEN, YELLOW} EncoderState; typedef struct I2cRegisters{ unsigned char siu[SIUSIZE]; unsigned char hsfe[FESIZE]; unsigned char alsd[1]; unsigned char cordic[3]; unsigned char cordicState[1]; unsigned char offset_comp[3]; unsigned char autogc[1]; unsigned char system[1]; } sI2cData; typedef struct EncoderData{ unsigned char otp[OTPSIZE]; unsigned char ssi[SSISIZE]; sI2cData i2c; unsigned int position; unsigned char magnitude; int sin; int cos; int ch0; int ch1; } sEncoderData; //class __declspec(dllexport) __stdcall Encoder{ class Encoder{ private: sEncoderData mData; bool IsAGCEnabled; bool IsMagCompEn; bool IsExternal; EncoderType type; EncoderState state; void SetEncoderType(EncoderType dev); public: Encoder(); EncoderType GetEncoderType(); bool IsConnected(); UsbComError Connect(const EncoderType dev); UsbComError Disconnect(); sI2cData ReadI2C(unsigned char regList); void ReadOTP(); double GetAngle(); unsigned int GetPosition(); void SetOTPByte(const unsigned char val, const unsigned char& byteNum); char GetOTPByte(const unsigned char byteNum); unsigned int ReadSSI(); unsigned char GetSSIByte(unsigned char num); char GetI2CByte(I2cReg reg, unsigned char byteNum); void SetI2CByte(I2cReg reg, unsigned char val, unsigned char byteNum = 0); int GetSIN(); int GetCOS(); int GetCh0(); int GetCh1(); unsigned char GetAGC(); void SetAGC(unsigned char val); void DisableAGC(); void EnableAGC(); bool GetAGCState(); unsigned char GetCordicState(); // just with AS5040 bool IsExternalEncoder(); void EnableExternalEncoder(); void EnableOnboardEncoder(); unsigned char GetMag(); double GetADCChannel(unsigned char ch); UsbComError Zapp(); EncoderState GetState(); }; #endifIm .cpp habe ich dann: (das ist nur ein Auszug aus dem File)
#include "Encoder.h" USEFILE("Encoder.h"); //--------------------------------------------------------------------------- UsbConnection mUsbCon; bool WINAPI DllMain(HINSTANCE hinstDLL, DWORD fwdReason, LPVOID lpvReserved) { return 1; } //--------------------------------------------------------------------------- extern "C" __declspec(dllexport) double GetDllVersion(void){ return 1.0; } extern "C" __declspec(dllexport) Encoder * GetEncoder(){ Encoder * pEnc = new Encoder; return pEnc; } extern "C" __declspec(dllexport) void FreeEncoder(Encoder * enc){ delete enc; } /////////////////////////////////////////////////////////////////////////////// // Constructor // /////////////////////////////////////////////////////////////////////////////// Encoder::Encoder(){ IsAGCEnabled = true; IsExternal = false; IsMagCompEn = false; type = AS5043; } . . . // usw....
-
Kann es sein, dass jetzt die Methoden innerhalb der Klasse dekoriert sind?
Der genaue Fehler lautet nämlich:
error LNK2001 unresolved external symbol .... "Encoder::GetCh0(void)" ?GetCh0@Encoder@@$$FQSDF...Das schaut aus wie das Problem, dass ich vor dem extern "C" gehabt habe. Nur jetzt innerhalb der Methode.
-
Lass das USEFILE mal weg.
Nur als Hinweis. Bei Deklarationen von enums, structs oder Klassen brauchst du kein typedef.
statttypedef enum {RED, GREEN, YELLOW} EncoderState;das hier
enum EncoderState {RED, GREEN, YELLOW};Genau so bei struct. Das ist aber kein Fehler.
Dei Problem besteht wohl darin, das der Kompiler nach inkludieren der Header auch die Definitionen deiner Klassenfunktionen sehen will. Da wirst du wohl nicht drumrumkommen die schon erwähnte Wrapperklasse zu schreiben oder statisch zu linken.
-
Hier mal ein Bsp. für so eine Wrapperklasse. Ist gekürzt.
class TChart2DForm; typedef const char* Tget_version(); // for TChartForm typedef TChart2DForm * Tget_chart(); typedef void Tfree_chart(TChart2DForm *&); typedef bool Tshow_file(TChart2DForm * ch, const char* fname); class TPostProcDll { private: bool loadDll() { hdll = LoadLibrary(DllPath.c_str()); if( hdll == 0 ) return false; Fget_chart = reinterpret_cast<Tget_chart*>(GetProcAddress(hdll, "_get_chart")); if( Fget_chart == NULL ) return false; Ffree_chart = reinterpret_cast<Tfree_chart*>(GetProcAddress(hdll, "_free_chart")); if( Ffree_chart == NULL ) return false; Fshow_file = reinterpret_cast<Tshow_file*>(GetProcAddress(hdll, "_show_file")); if( Fshow_file == NULL ) return false; Fget_version = reinterpret_cast<Tget_version*>(GetProcAddress(hdll, "_get_version")); if( Fget_version == NULL ) return false; return true; } bool loadFreeFunct() { hdll = LoadLibrary(DllPath.c_str()); if( hdll == 0 ) return false; Ffree_chart = reinterpret_cast<Tfree_chart*>(GetProcAddress(hdll, "_free_chart")); if( Ffree_chart == NULL ) return false; return true; } public: TPostProcDll(const char* dllpath = "") : Fget_version(0), Fget_chart(0), Ffree_chart(0), Fshow_file(0), DllPath(dllpath), m_chart(0), hdll(0) { if( DllPath.empty()) DllPath = "Postprocdll.dll"; } ~TPostProcDll() { if( hdll != 0) { Ffree_chart(m_chart); FreeLibrary(hdll); } } bool showFile(const char* fname) { if( hdll == 0 && !loadDll()) return false; if( m_chart == NULL ) m_chart = Fget_chart(); if( !Fshow_file(m_chart, fname)) return false; return true; }; const char* getVersion() { if( hdll == 0 && !loadDll()) return ""; return Fget_version(); } private: std::string DllPath; TChart2DForm * m_chart; HINSTANCE hdll; Tget_version* Fget_version; Tget_chart* Fget_chart; Tfree_chart* Ffree_chart; Tshow_file* Fshow_file; };die cpp
//--------------------------------------------------------------------------- #pragma argsused int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved) { return 1; } //--------------------------------------------------------------------------- const char* Version = "1.0.1"; namespace DELPHIN_POSTPROC { extern "C" __declspec(dllexport) TChart2DForm * get_chart() { TChart2DForm *chart = new TChart2DForm(NULL); return chart; } //--------------------------------------------------------------------------- extern "C" __declspec(dllexport) const char* get_version() { return Version; } //--------------------------------------------------------------------------- extern "C" __declspec(dllexport) void free_chart(TChart2DForm *& ch) { delete ch; ch = NULL; } //--------------------------------------------------------------------------- extern "C" __declspec(dllexport) bool show_file(TChart2DForm * ch, const char* fname) { TStringList *list = new TStringList; list->Add(fname); DimOutp::TDIMReadError res = ch->ShowChart(list, true, false, PostProcCallBacks()); delete list; if( res != DimOutp::NoError) { return false; } return true; } //---------------------------------------------------------------------------Das ist ein Beispiel von mir, also stör dich nicht an den Namen. Hier muß man dann nur die oben genannte Headerdatei einbinden, die Wrapperklasse im Hauptprogramm instanziieren und schon gehts (zumindest bei mir
).
-
Danke für deinen Input mit den enums. Ich kenne sonst keine Programmierer daher sind solche Hinweise für mich sehr wertvoll.
Da die Schnittstelle meiner .dll jetzt nur aus 3 Funktionen besteht werde ich versuchen sie statisch zu linken.
Mal schauen ob das geht. An meiner .dll muss ich da ja nichts ändern.
Ich werde dich auf dem Laufenden halten.
Danke,
Mario