übergebener Buffer in Funktion vergrößern



  • Hallo zusammen,

    bin gerade nicht sicher ob das Forum hier richtig ist, aber ich versuche es trotzdem einmal...
    Ich will für meine C++-Bibliothek eine C-Schnittstelle bereitstellen und möchte zur Übergabe von Daten eine Funktion verwenden, die beispielsweise so aussehen könnte:

    void doSomething(unsigned char *data, unsigned int len)
    {
       // ... Verarbeitungsroutine ...
    }
    

    Übergibt mir das C-Programm diese Daten, so kann ich diese beliebig anpassen. Allerdings bin ich durch die Größe beschränkt und kann diese bei Bedarf nicht ohne weiteres erweitern. Wie wäre solch einem Fall das beste Vorgehen? Ich hätte jetzt folgende Idee:

    int doSomething(unsigned char *data, unsigned int *len,  unsigned int bufferSize)
    {
       /*
            - 'len' ist die Größe der Nutzdaten, die in 'data' hinterlegt sind
            - 'bufferSize' ist die Puffergröße, auf das die Nutzdaten max. erweitert werden können
       */
    
       // ... Verarbeitungsroutine ...
       *len = newLength;
       if (bufferSize == tooSmall)
       { 
          return -1; // Puffer zu klein
       }
       else
       { 
          return numBytesWritten; // neue Größe der Nutzdaten
       }
    }
    

    ....oder gebe ich im Fehlerfall besser die benötigten Puffergröße zurück, falls dieser zu klein ist? ...oder macht man sowas mit Callbacks, übergibt einen neuen Buffer oder macht doch was ganz anders?

    Da der aufrufende C-Code und diese Funktion i.d.R. zeitkritisch sind, möchte ich gerne unnötiges kopieren oder reallozieren von Speicher vermeiden. Da ich mit klassischem C nur sehr selten Kontakt habe, weiß ich nicht was C-Entwickler normalerweise erwarten (Parameter/Rückgabewerte). Habt ihr ggf. ein paar Tipps für mich? 🙂

    viele Grüße,
    SBond



  • @SBond

    Übergibt mir das C-Programm diese Daten, so kann ich diese beliebig anpassen.

    ? habe ich nicht verstanden - ich denke dein C++ will an C was übergeben und erhält es dann verarbeitet zurück.

    Nutze Callbacks, d.h. also Funktionszeiger. Das ist das etablierte Verfahren für deinen Anwendungsfall.
    Wenn du keine großen Strukturen als Parameter übergibst, wirst du keine Performanzeinbußen haben.
    Nutze den Returnwert von Funktionen nie für unterschiedliche Aufgaben, so wie gezeigt.
    Callbacks kannst du dir anschauen z.B. https://linux.die.net/man/3/qsort, insbesondere auch die dort genannte qsort_r Variante, bei der du ein zusätzliches void* Argument benutzt, um den Funktionsaufruf selbst nochmals zu parametrisieren - flexibler im Gebrauch zu machen.



  • @Wutz sagte in übergebener Buffer in Funktion vergrößern:

    ? habe ich nicht verstanden - ich denke dein C++ will an C was übergeben und erhält es dann verarbeitet zurück.

    Genau umgekehrt 😉
    Das C-Programm ruft eine Funktion aus der C++-Lib auf und übergibt darüber die Daten, die zu verarbeiten sind.

    Wenn ich dich richtig verstanden habe, dann rufe ich also jedes mal, wenn meine Lib mehr Speicher benötigt, den Callback auf. Ist das richtig? Habe noch nicht wirklich mit Callbacks gearbeitet, würde es denn etwa so aussehen?:

    int doSomething(unsigned char *data, size_t *len, void (*cb)(void *ptr, size_t newLen, void *arg), void *arg)
    {
    
       // ... Verarbeitungsroutine ...
       if (*len < newLength)
       { 
          if (cb(data, newLength, arg) == NULL)
          {
             return -1;   // fehler
          }
       }
    
       // ... weiter verarbeiten ...
       *len = newLength;   // neue Größe der Daten (sinnvoll falls 'data' nach der Verarbeitung weniger Daten enthält?)
       return 0;  // alles ok
    }
    

    irgendwo im C-Programm:

    void resizeData(void *ptr, size_t newLen, void *arg)
    {
       if (arg != NULL)
       {
          // was der C-Entwickler auch immer machen will ;)
       }
       return realloc(ptr, newLen);
    }
    
    
    void main()
    {
       size_t len = 100;
       unsigned char *data = (unsigned char*)malloc(len);
    
       // ...befüllen von 'data'...
    
       doSomething(data, &len, resizeData);    // feherüberprüfung überspringe ich hier mal
       printf("new data length: %i", len);
    
       free(data);
    }
    

    ist das so ungefähr das was man erwartet? Hab den Code jetzt nicht getestet, könnte also noch 'nen Bug drinne sein.

    viele Grüße,
    SBond



  • void main() ist kein Standard.
    Speicher für zu verarbeitende Daten stellt man immer aus dem übergeordneten aufrufenden Kontext bereit, und niemals im aufgerufenen Kontext.
    Ich bezweifle, dass es wirklich sinnvoll ist, im Subcontext neuen Speicher anzufordern, wenn überhaupt, dann macht man auch das über Callbacks; hier also, indem man z.B. einen Zeiger auf die C-Standardfunktion realloc übergibt, oder einen Zeiger auf einer Wrapperfunktion darauf. Diese wird dann in deinem C++ Kontext aufgerufen und so bleibt die Speicherverwaltung komplett in deinem Hostkontext.



  • Aber der Speicher wird doch im 2. Beispiel vom aufrufenden Kontext bereitgestellt bzw. duch den Callback erweitert. Bin jetzt verwirrt und nicht sicher ob ich dich richtig verstanden habe. Ist mein zweites Beispiel (bis auch ein paar kleine Fehler) jetzt falsch?

    Die Funktion 'doSomething()' wird in c++ implementiert.

    viele Grüße,
    SBond



  • so würde es zumindest funktionieren, auch wenn ich nicht so der Freund von Pointer-to-Pointer bin:

    // in C++ implementiert und in der Lib bereitgestellt
    int doSomething(unsigned char **data, size_t *len, void (*cb)(void **ptr, size_t newLen, void *arg), void *arg)
    {
    
       // ... Verarbeitungsroutine ...
       if (*len < newLength)
       { 
          if (cb(data, newLength, arg) == -1)
          {
             return -1;   // fehler
          }
       }
    
       // ... weiter verarbeiten ...
       *len = newLength;   // neue Größe der Daten (sinnvoll falls 'data' nach der Verarbeitung weniger Daten enthält?)
       return 0;  // alles ok
    }
    

    irgendwo im C-Programm:

    // Callback-Funktion
    int resizeData(void **ptr, size_t newLen, void *arg)
    {
       if (arg != NULL)
       {
          // was der C-Entwickler auch immer machen will ;)
       }
    
       unsigned char *newPtr = (unsigned char*)realloc(*ptr, newLen);
       if (newPtr != NULL)
       {
          *ptr = newPtr;
          return 0;
       }
       else
       {
          return -1;
       }
    }
    
    
    
    int main()
    {
       size_t len = 100;
       unsigned char *data = (unsigned char*)malloc(len);
    
       // ...befüllen von 'data'...
    
       unsigned char **ptr= &data ;
       doSomething(ptr, &len, resizeData);
       printf("new data length: %i", len);
    
       free(data);
       return 0;
    }
    

    viele Grüße,
    SBond



  • Der Anwender muss sich jetzt die Dokumentationen zu beiden Funktionen anschauen (DoSomething und wie die Callback-Funktion genau auszusehen hat). Das mag okay sein, aber bei diesem Anwendungsfall wird es in APIs häufig so gemacht, dass der Datenzeiger 0 sein kann. In diesem Fall wird die benötigte Größe zurückgegeben bzw. in len geschrieben. Du könntest dann optional auch eine weitere Funktion anbieten und entsprechend benennen, die das dann genau so macht -> das ist selbsterklärend und eine Falschnutzung wird zusätzlich erschwert.



  • ja, das kommt mir in der Tat bekannt vor. Ich denke das ist eine gute Ergänzung 🙂


Anmelden zum Antworten