Library: return eines std::strings möglich?



  • Hallo,
    ich möchte eine Implementierung für eine Bibliothek möglichst plattformunabhängig implementieren. Wie ich gesehen habe, liefert GetProcAddr die Adresse der angegebenen Funktion zurück.

    Meine Frage:
    kann man auch Funktionen definieren, die std::strings zurückliefern? Oder ist der umständliche Weg über malloc und free erforderlich?

    Die Library soll wieder von C++ verwendet werden. Es geht darum, einem Programm nach Bedarf die richtigen Datenbankfunktionen (verschiedene DB-Formate) zur Verfügung zu stellen.

    Danke,
    Joe



  • Natürlich können Funktionen auch std::string zurückliefern. Da hast du auch nix mit der Speicherverwaltung zu tun.



  • [url]kann man auch Funktionen definieren, die std::strings zurückliefern?[/url]

    Klar.



  • Du musst aber darauf achten, dass sowohl die DLL als auch das aufrufende Client-Programm mit dem gleichen Compiler uebersetzt wurden, sowie die gleichen Bibliotheken benutzen.

    Linkst Du naemlich eine DLL statisch mit der Standardbibliothek, bekommt die DLL eine eigene Instanz der Standard-Library. Diese wird initialisiert, bevor DllMain() -- falls vorhanden -- ausgefuehrt wird.

    Das ausgefuehrte Programm, das die DLL verwendet, muss dann auch statisch an die Standard-Library gelinkt werden, da ja der Speicher, dem Heap der DLL allokiert wurde, im Kontext des EXE-Files wieder freigegeben wird. Dies funkitoniert nur, wenn die Speicherfreigabe nach demselben Schema wie in der DLL verlaeuft.

    Es gibt u.U. auch die Moeglichkeit, sowohl die DLL als auch das EXE-File an die nichtstatische (DLL-Version der) Standardbibliothek anzukoppeln. Dann existiert sowohl fuer die DLL als auch fuer das EXE nur ein Heap.



  • Dann existiert sowohl fuer die DLL als auch fuer das EXE nur ein Heap.

    genau. deswegen kann man *nur* die dynamische bibliothek nehmen.



  • Hallo nochmals.
    Habe jetzt folgendes ausprobiert. Integer gehen, std::strings nicht. Seht ihr den Fehler?

    Gruß, Joe

    dyndll.cpp:

    #include <string>
    
    int getInt()
    {
    	return 987;
    }
    
    std::string getStr()
    {
    	return std::string("Returned Value getStr");
    }
    

    bzw das Hauptprogramm:

    #include <string>
    #include <windows.h>
    
    typedef std::string (CALLBACK* GETSTR)();
    typedef int (CALLBACK* GETINT)();
    
    int main(int argc, char* argv[])
    {
    	GETINT getInt;
    	int i;
    
    	GETSTR getStr;
    	std::string s;
    
    	HMODULE hdll;
    	hdll=LoadLibrary("dyndll.dll");
    
    	getInt=(GETINT) GetProcAddress(hdll,"getInt");
    	i = getInt();  //funktioniert
    
    	getStr=(GETSTR) GetProcAddress(hdll,"getStr");
    	s = getStr();  //funktioniert nicht
            //"The value of ESP was not properly saved across a function call.
            //This is usually a result of calling a function declared with one    
            //calling convention with a function pointer declared with a different 
            //calling convention."
    
    	FreeLibrary(hdll);
    	return 0;
    }
    


  • (1) calling convention

    GETSTR verwendet CALLBACK, was zu stdacall auflöst, und das DLL istwahrscheinlich mit Standardeinstellungen (cdecl). getInt geht gut, weil keine Daten über den Stack gehen.
    probier:

    std::string CALLBACK getStr()
    {
    return std::string("Returned Value getStr");
    }

    (stdcall ist nicht wirklich notwendig, es müssen nur beide Seiten die gleiche calling convention verwenden. stdcall ist aber gängig für DLL-Interfaces)

    (2) Auf das Problem mit der CRT für DLL's hätte ich natürlich hinweisen können 🙂
    Die einfachste Lösung ist, beide Seiten auf die gleiche DLL-Einstellung (z.B. Multithreaded DLL) zu setzen.

    etwas komplizierter, erlaubt aber getrennte Übersetzung: du gibst keinen std::string, sondern einen smart pointer mit custom deleter zurück (der Custom Deleter muß nicht-inline in der DLL liegen).

    z.B. mit boost:

    typedef boost::shared_ptr<std::string> string_ptr;
    
    struct deleter_std_string
    {
      void operator()(std::string * p) { delete p; } // impl. nicht inline!!
    }
    
    string_ptr getStr()
    {
      return string_ptr(new std::string(""), deleter_std_string());
    }
    

    Du hast dann aber immer die Dereferenzierungnen. Je nach STL-Implementation und daten *kann* das sogar schneller sein.

    Eine andere Variante wäre ein dll_string mit custom allocator, wie gut dann aber die operationen zwischen einem dll-string und einem normalen gehen, weiß ich nicht. (Ich denke mal schlecht, aber ich denke immer schlecht von der STL)

    [edit] fundamentalen Fehler im boost-code gefixt 🤡 [/edit]



  • peterchen schrieb:

    (1) calling convention

    probier:

    std::string CALLBACK getStr() 
    { 
      return std::string("Returned Value getStr"); 
    }
    

    Das funktioniert gut!
    Allerdings muß der Code auf einen Pointer angepaßt werden:

    int main(int argc, char* argv[])
    {
    	GETSTR getStr;
    	std::string *s = new std::string();  //geändert
    
    	HMODULE hdll;
    	hdll=LoadLibrary("dyndll.dll");
    
    	getStr=(GETSTR) GetProcAddress(hdll,"getStr");
    	*s = getStr();  //geändert
    
    	FreeLibrary(hdll);
    
    	printf("Hallo Welt!\n");
    	return 0;
    }
    

    falls man das nicht ändert (also nicht in Pointer umschreibt), kriegt man ein Problem beim return. ("Access Violation" in XString)

    freigeben kann man das ganze aber nicht mehr

    delete s;
    

    liefert den gleichen Fehler wie oben.

    Optionen sind jetzt:
    😉 Multithreaded bei library
    😉 Single Threaded debug bei executable

    (2) hab ich nicht probiert. Habe ich das richtig verstanden, daß man dafür die Boost-Library benötigt?

    Danke vielmals!
    Joe



  • Hi,

    Im Prinzip passiert folgendes:

    Wenn DLL und EXE die gleiche CRT als DLL einbinden, verwenden EXE und DLL die gleichen CRT-Instanzdaten, und somit den gleichen Heap.
    Bei deiner Einstellung eht das "new" im DLL auf einen anderen Heap als das "delete" in der EXE ==> gleicher effekt wie das delete eines ungültigen Zeigers, BUMM.

    Du kannst also entweder die gleiche CRT-Instanz forcieren (schränkt dich aber in den Compile-Einstellungen ein), oder das "delete" muß immer auf der gleichen Seite wie das "new" erfolgen.

    Du könntest also in dein DLL zwei Funktionen aufnehmen:

    string * GetStr();
    void FreeStr(string * s);
    

    Ist natürlich nervig.

    mit boost::shared_ptr kannst du das Pärchen in einen string_ptr verpacken (der auch noch referenzgezählt ist, du bist also die Sorge los, wann du delete machen mußt). Geht natürlich auch z.B. mit Loki.

    Intern speichert der Smart Pointer zusätzlich einen Zeiger auf die Funktion, die den String freigeben soll. Wennn diese Funktion im DLL implementiert ist, geht alles magisch glatt.

    Für die boost smart pointer brauchst dur keine libs bauen, nur "ein paar" header (boost/config, boost/detail, und ein paar aus bost/).

    Unbescheidenerweise poste ich mal folgenden Link 🤡 :
    http://www.codeproject.com/vcpp/stl/boostsmartptr.asp



  • peterchen schrieb:

    Bei deiner Einstellung geht das "new" im DLL auf einen anderen Heap als das "delete" in der EXE

    So habe ich das verstanden:
    In main() ist ein Pointer angelegt, der auf einen leeren std::string zeigt.

    Durch den Funktionsaufruf wird der Inhalt dieses leeren strings überschrieben.
    *s = getStr();

    also müßte ich diesen auch wieder freigeben können.

    peterchen schrieb:

    string * GetStr();
    void FreeStr(string * s);
    

    Ist natürlich nervig.

    Dann kann ich ja gleich char* nehmen 🙂

    Bei mir ist aber kein Pointer definiert: (oder steht das implizit im CALLBACK drinnen?)

    std::string CALLBACK getStr()
    {
    	return std::string("Returned Value getStr");
    }
    

    PS. Warum wird das bei mir mit der Formatierung nichts? verwende Firefox.



  • joerider schrieb:

    PS. Warum wird das bei mir mit der Formatierung nichts? verwende Firefox.

    Das hat nichts mit dem Browser zu tun, sondern damit, dass du BBCode deaktivieren ausgewählt hast 🙄



  • kingruedi schrieb:

    Das hat nichts mit dem Browser zu tun, sondern damit, dass du BBCode deaktivieren ausgewählt hast 🙄

    Danke, habe meine Beiträge dahingehend modifiziert.
    Joe



  • (oder steht das implizit im CALLBACK drinnen?)

    nein, du müßtest das als std::string * CALLBACK getStr() deklarieren

    *s = getStr();

    Ob da geht oder nicht, hängt stark von der STL-Implementation ab (eher nicht).

    Wenn du die CRT-Einstellungen nicht umstellen willst, bleibt dir wahrscheinlich nix anderes als ein smart pointer übrig.


Anmelden zum Antworten