Speicherplatz in Hauptprogramm für Routine aus DLL reservieren



  • Hallo,

    Ich möchte eine Routine aus einer DLL nutzen und stehe auf dem Schlauch was die Speicherzuweisung der Strings im Hauptprogramm betrifft.

    Auszug aus DLL:

    typedef unsigned char *PChar;
    typedef PChar *StringParam[100];
    
    DLLEXPORT void _stdcall CCalculateEx(double *PInput, double *POutput, double *PUser, StringParam PStrings)
    {
    sprintf((char*)PChar(PStrings[0]), "%s", "Test");
    }
    

    Auszug aus aufrufendem Programm:

    typedef int (_stdcall *RunRoutine)(double[100],double[100], double[100], ???);
    
    int main()
    {
    double PInput[100];
    double POutput[100];
    double PUser[100];
    
    HMODULE hDll;
    hDll = LoadLibrary("MYLib.dll");
    
    RunRoutine goRun;
    
    goRun = (RunRoutine)GetProcAddress(hDll,"CCalculateEx");
    goRun(PInput, POutput, PUser, ???);
    }
    

    Die PStrings in der DLL benötigt letztendlich Speicherplatz für 100 Strings a 1000 Zeichen.
    Es wird doch ein 2D Array benötigt, auf den über Pointer verwiesen wird? Nur wie genau? Ich wäre dankbar, wenn mir jemand erklären könnte, wie ich den Strings aus der DLL den benötigten Speicherplatz zuweise.


  • Mod

    Spricht was gegen einen passend dimensionierten vector<array<char, 1000>> bzw. ein statisches array<array<char, 1000>, 100> ?

    Das Ganze kommt mir übrigens nicht sehr gut gemacht vor. Ist die aufgerufene Routine von dir geschrieben? Ich habe doch gesunde Zweifel, dass die "100" und die "1000" tatsächlich für die Funktionalität wichtig sind und nicht einfach nur für "große Zahl" stehen. Es wäre vermutlich sehr viel besser, die Länge der Felder als Parameter mit an die Funktion zu geben.



  • Danke für deine Antwort.
    Die aufgerufene Routine stammt nicht von mir. Die DLL wird eigentlich in ein Elektroniksimulationsprogramm eingebunden. 100 ist die maximale Anzahl an Ein/Ausgängen und 1000 die maximale Länge der Strings, welches das Programm vorgibt.

    Spricht was gegen einen passend dimensionierten vector<array<char, 1000>> bzw. ein statisches array<array<char, 1000>, 100>?

    Ich glaube dagegen spricht nichts, wahrscheinlich tue ich mich nur bei der Erstellung schwer 🙄
    Hättest du noch kurz ein Code-Beispiel dafür? - Danke!


  • Mod

    vector/array kennst du aber, oder? Wenn nicht: Unbedingt mal ein gutes C++-Buch schnappen. Das ist wichtig!

    Ansonsten:

    array<array<char, 1000>, 100> foo;
    // bzw.
    vector<array<char, 1000>> bar(100);
    

    Natürlich nach Einbindung der entsprechenden Header und ggf. mit expliziter Angabe des Namespaces std .



  • Hallo SeppJ,

    so in etwa habe ich es schon probiert aber es funktioniert beides nicht.

    Ich habe die Funktion aus der DLL jetzt mal sinngemäß übernommen und in ein eigenes Projekt kopiert.

    Die übergebende Variable scheint nicht die korrekte Form zu besitzen.

    Ein Beispiel:

    #include <iostream>
    #include <vector>
    #include <array>
    #include <windows.h>
    #include <string.h>
    #include <stddef.h>
    #include <stdio.h>
    
    using namespace std;
    
    typedef unsigned char *PChar;
    typedef PChar *StringParam[100];
    
    double PArray[100];
    
    void copy(double *PUser, StringParam PStrings)
    {
    	sprintf((char*)PChar(PStrings[0]), "%s", "a");
    }
    
    int main()
    {
    
    vector<array<char, 1000>> bar(100);
    array<array<char, 1000>, 100> foo;
    
    copy(PArray, bar);
    
    return 0;
    }
    

    Fehler: \src\ArrayVector.cpp:30:17: error: no matching function for call to 'copy(double [100], std::vector<std::array<char, 1000u> >&)'

    Hast du noch eine Idee wie die Übergabe korrekt auszusehen hat?

    Danke!


  • Mod

    Du musst natürlich nicht das vector-Objekt selbst übergeben (das ist eine komplexe Klasse, mit der der C-Code nix anfangen kann), sondern einen Zeiger auf die Daten, die der vector verwaltet.

    Also zum Beispiel

    copy(PArray, reinterpret_cast<StrinParam>(bar.data()));
    // oder
    copy(PArray, reinterpret_cast<StrinParam>(foo.data()));
    // oder
    copy(PArray, reinterpret_cast<StrinParam>(&bar[0]));
    // oder
    copy(PArray, reinterpret_cast<StrinParam>(&foo[0]));
    

    Ich hoffe, ich habe bei all dem Gepointere jetzt nichts falsch gemacht bei den Typen. Ich kann den Code gerade nicht vernünftig testen und programmiere im Kopf. Was bei so vielen Verschachtelungen schon leicht verwirrend ist 😃 .



  • Danke für deine Geduld!

    Leider kommt es immer zu einem invalid cast Error:

    invalid cast from type 'std::array<std::array<char, 1000u>, 100u>::pointer {aka std::array<char, 1000u>*}' to type 'StringParam {aka unsigned char** [100]}'
    

    hier am Beispiel von:

    array<array<char, 1000>, 100> foo;
    copy(PArray, reinterpret_cast<StringParam>(foo.data()));
    

    Ich habe versucht es noch mit array<array<unsigned char, 100>, 1000> foo; etc versucht aber Änderungen in der Form haben es auch nicht gebracht.

    Könntest du bitte noch mal drüber schauen?

    Wäre es eigentlich auch möglich den Array durch char Array[100][1000] zu erstellen, und dann über zwei Pointer darauf zu zeigen und diese dann an die DLL-Routine zu übergeben?


  • Mod

    Argh! Ja ich hab's falsch rum gemacht. Deine Funktion will ja 100 Zeiger auf Zeiger auf unsigned char, nicht einen Zeiger auf 100 Zeiger auf unsigned char. Das macht immer weniger Sinn, aber wenn's so vorgegeben ist, dann ist das eben so.

    Jedenfalls muessen wir bei so einer Struktur tatsaechlich mit ein paar haerteren Mitteln ran und uns den Speicher manuell besorgen:

    #include <cstdio>
    
    typedef unsigned char Pchar;
    typedef Pchar *StringParam[100];
    
    void test(double *values, StringParam strings)
    {
      for(unsigned i = 0; i < 100; ++i)
        sprintf((char*) strings[i], "Blah: %f\n", values[i]);
    }
    
    #include <cstddef>
    #include <algorithm>
    // Wrapper around a C-style list of lists (T**)
    // but with static inner dimension. Makes no sense
    // (why not use an array?) but is needed by external
    // library function.
    template<typename T> class List_of_Lists
    {
    private:
      std::size_t outer;
      std::size_t inner;
      T** internal_data;
    public:
      List_of_Lists(std::size_t outer, std::size_t inner):
        outer(outer), inner(inner), internal_data(new T*[outer]())
      {
        try
          {
            for(std::size_t i = 0; i < outer; ++i)
              {
                internal_data[i] = new T[inner];
              }
          }
        catch (...)
          {
            for(std::size_t i = 0; i < outer; ++i)
              delete[] internal_data[i];       
            delete[] internal_data;
            throw;
          }
      }
    
      ~List_of_Lists()
      {
        for(std::size_t i = 0; i < outer; ++i)
          delete[] internal_data[i];       
        delete[] internal_data;
      }
    
      List_of_Lists(const List_of_Lists& other):
        outer(other.outer),
        inner(other.inner),
        internal_data(new T*[outer]())
      {
        try
          {
            for(std::size_t i = 0; i < outer; ++i)
              {
                internal_data[i] = new T[inner];
                std::copy(other.internal_data[i], 
                          other.internal_data[i] + inner, 
                          internal_data[i]);
              }
          }
        catch (...)
          {
            for(std::size_t i = 0; i < outer; ++i)
              delete[] internal_data[i];       
            delete[] internal_data;
            throw;
          }
      }
    
      List_of_Lists& operator=(List_of_Lists other)
      {
        std::swap(other.inner, inner);
        std::swap(other.outer, outer);
        std::swap(other.internal_data, internal_data);
        return *this;
      }
    
      T** data() { return internal_data; }
      const T* operator[](std::size_t index) const { return internal_data[index]; }
      T* operator[](std::size_t index) { return internal_data[index]; }
    };
    
    #include <iostream>
    #include <vector>
    int main()
    {
      std::vector<double> bar(100);
      for(int i = 0; i < 100; ++i) bar[i] = 25.37326 * i + 7.4747;
    
      List_of_Lists<unsigned char> strings(100, 1000);
      test(bar.data(), strings.data());
    
      for(int i = 0; i < 100; ++i) std::cout << strings[i];
    }
    

    Diese Mal ist es auch getestet, dass es tatsaechlich funktioniert 🙂 .

    Move constructor und move assignment spare ich mir mal, das moege man sich bei Bedarf selber implementieren. Wahrscheinlich werden selbst der Kopierkonstruktor und der Zuweisungsoperator hier nie benutzt werden.



  • Etwas modifiziert um Code-Duplication und das phöse re-throw zu vermeiden:

    template<typename T>
    class List_of_Lists_Base
    {
    protected:
    	std::vector<T*> internal_data;
    
    	explicit List_of_Lists_Base(std::size_t outer)
    		: internal_data(outer) {}
    
    	~List_of_Lists_Base()
    	{
    		for (auto p : internal_data)
    			delete[] p;
    	}
    
    	List_of_Lists_Base(List_of_Lists_Base const&) = delete;
    	List_of_Lists_Base& operator = (List_of_Lists_Base const&) = delete;
    };
    
    // Wrapper around a C-style list of lists (T**) 
    // but with static inner dimension. Makes no sense 
    // (why not use an array?) but is needed by external 
    // library function. 
    template<typename T>
    class List_of_Lists : private List_of_Lists_Base<T>
    {
    private:
    	std::size_t inner;
    
    public:
    	List_of_Lists(std::size_t outer, std::size_t inner)
    		: List_of_Lists_Base(outer), inner(inner)
    	{
    		for (auto& p : internal_data)
    			p = new T[inner];
    	}
    
    	List_of_Lists(List_of_Lists const& other)
    		: List_of_Lists(other.internal_data.size(), other.inner)
    	{
    		for (std::size_t i = 0; i < internal_data.size(); ++i)
    			std::copy(other.internal_data[i], other.internal_data[i] + inner, internal_data[i]);
    	}
    
    	List_of_Lists& operator = (List_of_Lists other)
    	{
    		std::swap(other.inner, inner);
    		std::swap(other.internal_data, internal_data);
    		return *this;
    	}
    
    	T const* const* data() const { return internal_data.data(); }
    	T** data() { return internal_data.data(); }
    
    	T const* operator[](std::size_t index) const { return internal_data[index]; }
    	T* operator[](std::size_t index) { return internal_data[index]; }
    };
    

  • Mod

    hustbaer schrieb:

    Etwas modifiziert um Code-Duplication und das phöse re-throw zu vermeiden:

    Ich wusste schon, dass genau diese Antwort kommen würde, während ich das schrieb. Aber in dem Fall siegte die Faulheit 😃 . Ich rede mich aber trotzdem mal damit raus, dass der TE meinen Code wohl eher versteht (wenn er überhaupt noch was versteht), auch wenn es nicht gerade eine Demonstration von best-practices* ist.

    *: Mein Code zeigt ja sogar ganz im Gegenteil, wieso es keine gute Idee ist, mehrere Ressourcen in einem Objekt zu halten.



  • Meine Faulheit äussert sich darin dass ich gerade solchen Code schreibe. Weil ich keine Lust habe mir über Dinge den Kopf zu zerbrechen, wie eben ob alles in jedem Fall richtig freigegeben wird etc. 😉
    Und weil ich noch viel weniger Lust habe das wiederholt zu tun - wie wenn ich ein Programm lese, Fehler suche, Änderungen machen muss etc.
    (Was in dem Fall natürich wurscht wäre, aber das is einfach schon so ne starke Angewohnheit...)

    ~Mir sind Leute suspekt die dreckigen Test- oder Demo-Code schreiben. Ich bin nämlich der Meinung dass die meisten davon auch dreckigen Produktiv-Code schreiben. ;)~



  • Ich danke vielmals - Es geht alles - einfach wunderbar 🙂
    Das hätte ich aber selber garantiert nicht hinbekommen 🙄



  • Was ist denn bitte an throw; boese?


  • Mod

    Kellerautomat schrieb:

    Was ist denn bitte an throw; boese?

    Es ist halt ein Zeichen für einen Designfehler. Wenn man ein rethrow macht, dann hat man vorher höchstwahrscheinlich ein catch(...) gemacht. Das einzig sinnvolle, was man in einem catch(...) -Block machen kann, sind jedoch Aufräumarbeiten. Das wiederum bedeutet, dass man RAII nicht konsequent durchgezogen hat, denn dan wäre das unnötig. Und C++ ohne RAII ist aber nicht wirklich C++.
    Genau das ist in meinem Beispiel passiert: Zwar ist das gesamte Datenfeld geRAIIt (das ist ja gerade der Zweck der Klasse), aber sauber wäre es gewesen, so wie bei hustbaer auch die einzelnen Unterfelder mittels RAII zu verwalten, nicht nur das Gesamtobjekt. Dann entfallen nämlich die Aufräumarbeiten und die daraus folgende Codeduplikation in den Konstruktoren.

    Man sieht: Das faul programmierte Beispiel war anscheinen didaktisch wertvoll 😃 . Manchmal muss man eben auch mal demonstrieren, wie es falsch aussähe, damit klar wird, warum der richtige Weg besser ist.



  • Exceptions aus einem Thread entkommen lassen ist auch ein Designfehler. Was machst du da? :p



  • Kellerautomat schrieb:

    Exceptions aus einem Thread entkommen lassen ist auch ein Designfehler.

    Nicht wirklich.
    Wenn man die Exception nur mit "Mist, bitte 1x Programm abbrechen" behandeln kann, dann kann man sie auch gleich rausfliegen lassen. Hat sogar einige Vorteile.


Log in to reply