Safearray an Dll übergeben/zurückgeben



  • Hallo,

    ich möchte gerne aus VB ein array an eine C-DLL übergeben dort dann damit arbeiten und ein weiteres array wieder zurückgeben. Da in VB jedem array eine safearray struktur zu grunde liegt muss ich es in C als safearray behandeln (richtig?).
    Leider kenne ich mich mit safearrays in C nicht sonderlich aus, würde das was ich gemacht habe (siehe unten) überhaupt funktionieren? Oder gibt es da noch einen anderen weg?
    Momentan scheint es nicht zu funktionieren, leider hab ich keine Ahnung wie ich in meiner DLL debuggen kann um das alles mal zu überprüfen! Könnte mir bitte jemand sagen wie ich das am besten anstelle?!

    Der Datentyp des input und output arrays in VB ist jeweils Byte. In meiner C-Funktion möchtes ich dann den input in ein float array kopieren um damit arbeiten zu können. Das 'Ergebnis' meiner Funktion liegt dann in einem char array vor was ich dann in das byte-array in VB kopieren möchte. Der equivalente Datentyp zu Byte in VB ist meiner Meinung nach unsigned char in C. Müsste ich dann mein Ergebnis erst noch von char nach unsigend char konvertieren oder kann ich es einfach so in das safearray schreiben.

    So sieht (grob) meine Funktion in der DLL aus:

    declspec__ (dllexport) int __stdcall MeineFunktion (LPSAFEARRAY *input_frame, LPSAFEARRAY *output_bytes, int size)
    {
    
        int MAX_NB, nbBytes;
        long int i;
    
        char *output_buffer;
        float *input_buffer;
    
        /* alloc memory for input */
        input_buffer = new float [size];
    
        /* copy input safearray to floatarray */
        for(i=0; i<size; i++)
            SafeArrayGetElement(input_frame[ i ], &i, &input_buffer[ i ]);
    
        /* other functions */
    
        /* delete memory used for input */
        delete input_buffer;
    
        /* get frame size in bytes */
        MAX_NB = /* other function */
    
        /* alloc memory for output */
        output_buffer = new char [MAX_NB];
    
                /* other functions - return char* */
    
        /* copy to output safearray */
        for(i=0; i<MAX_NB; i++)
            SafeArrayPutElement(output_bytes[ i ], &i, &output_buffer[ i ]);
    
        /* delete memory used for output */
        delete output_buffer;
    
        return nbBytes;
    }
    

    Der aufruf in VB geschieht folgendermaßen, also müssten die zeiger im Funktionskopf der DLL-Funktion dann auf meine arrays in VB zeigen (richtig?):

    'EineKlasse
    
    Private m_nbBytes As Long
    '[...]
    
    Private Declare Function MeineFunktion Lib "irgendeine.dll" (ByRef InputFrame() As Byte, _
                ByRef OutputBytes() As Byte, ByVal ArraySize As Long) As Long
    
    Public Sub Methode(InputArray() As Byte, OutputArray() As Byte)
       m_nbBytes = MeineFunktion(InputArray, OutputArray, UBound(InputArray))
    End Sub
    '[...]
    
    Dim clsKlasse As New EineKlasse
    
    Dim InputArray() As Byte
    Dim OutputArray() As Byte
    
    'Funktionsaufruf
    clsKlasse.Methode InputArray, OutputArray
    

    Danke und gruss
    Sören



  • /* alloc memory for input */ 
    input_buffer = new float [size]; 
    
    /* dynam. Speicherplatz von einem array sollte so freigegeben werden: */
    
    delete [] input_buffer;
    

    kann sein das du das übersehn hast....

    cu surf.



  • Nachdem Du das Problem mit dem falschen Löschen beseitigt hast, solltest Du Dir mal diesen Artikel in der MS KB ansehen: HOWTO: Pass Arrays Between Visual Basic and C

    Außerdem bekommst Du vom Array kein Array geliefert. Es sind also nur input_frame[0] und output_frame[0] gültig. Desweiteren solltest Du erstmal checken, ob es überhaupt ein Array ist, wie Du es erwartest. Du willst ein eindimensionales Array. Z.B.:

    UINT nDimensions = SafeArrayGetDim(*input_frame);
    
    if(nDimension != 1)
    {
        // Huh?
    }
    

    Und noch besser ist's, wenn Du zusätzlich noch den im Array gespeicherten Daten-Typen ermittelst (SafeArrayGetVartype) und mit der Forderung vergleichst.

    Auch kannst Du bei solchen Arrays nicht einfach bei 0 zu zählen anfangen. Du mußt vielmehr die Unter- und Obergrenze abfragen:

    long lLBound;  // Untere Grenze
    SafeArrayGetLBound(*input_frame, 1, &lLBound);
    
    long lUBound;  // Obere Grenze
    SafeArrayGetUBound(*input_frame, 1, &lUBound);
    
    /* copy input safearray to floatarray */
    for(i = lLBound; i <= lUBound; i++)
        SafeArrayGetElement(*input_frame, &i, &input_buffer[i]);
    


  • -King- schrieb:

    Nachdem Du das Problem mit dem falschen Löschen beseitigt hast, solltest Du Dir mal diesen Artikel in der MS KB ansehen: HOWTO: Pass Arrays Between Visual Basic and C

    Nette Lektüre, ich arbeite Sie gerade durch 🙂

    Außerdem bekommst Du vom Array kein Array geliefert. Es sind also nur input_frame[0] und output_frame[0] gültig.

    Wie meinst du das? Ja die Zeiger input_frame und output_bytes zeigen jeweils auf das erste element des arrays und von da aus kann ich dann das array per index auslesen usw. - oder nicht ??

    Auch kannst Du bei solchen Arrays nicht einfach bei 0 zu zählen anfangen. Du mußt vielmehr die Unter- und Obergrenze abfragen:

    OK, hab ich eingebaut.

    Nachtrag:
    Könntest du mir sagen wie ich am besten innerhalb der DLL debuggen kann??



  • Spontex schrieb:

    Wie meinst du das? Ja die Zeiger input_frame und output_bytes zeigen jeweils auf das erste element des arrays und von da aus kann ich dann das array per index auslesen usw. - oder nicht ??

    Nein, die Zeigen nicht auf Array-Elemente, sondern auf das Array selbst. Du darfst da nicht mit einem Index rübergehen.

    // Deine Variante - FALSCH!!
    SafeArrayGetElement(input_frame[ i ], ...);
    
    // Meine Variante (siehe oben) - richtig
    SafeArrayGetElement(input_frame[0], ...);
    

    Welches Element Du aus dem einen einzigen Array bekommst, gibt der Index im zweiten Parameter von SafeArrayGetElement an.

    Könntest du mir sagen wie ich am besten innerhalb der DLL debuggen kann??

    Einfach einen Breakpoint in der Dll setzen. Unter dem Menüpunkt 'Debuggen' findest Du das Item 'Prozesse'. Da wählst Du nun VB aus. Wird der Breakpoint erreicht, wird das Programm an dieser Stelle unterbrochen. Jedenfalls funktioniert das bei mir. Wie das bei Dir geht, hängt natürlich vom verwendeten Werkzeug ab.



  • Vielen dank, endlich mal jemand der Ahnung von Safearrays hat!

    -King- schrieb:

    Einfach einen Breakpoint in der Dll setzen. Unter dem Menüpunkt 'Debuggen' findest Du das Item 'Prozesse'. Da wählst Du nun VB aus. Wird der Breakpoint erreicht, wird das Programm an dieser Stelle unterbrochen. Jedenfalls funktioniert das bei mir. Wie das bei Dir geht, hängt natürlich vom verwendeten Werkzeug ab.

    Mmhh wenn ich das bei mir im VC6 so mache, deaktiviert er mir beim start alle breakpoints, was so ja nicht im Sinne des Erfinders ist. Was mache ich da falsch?



  • OK, so siehts mittlerweile aus:

    declspec__ (dllexport) int __stdcall MeineFunktion (SAFEARRAY **input_frame, SAFEARRAY **output_bytes, int input_size)
    {
    
    	int MAX_NB, nbBytes = 0;
    	long i, in_LBound, in_UBound, out_LBound, out_UBound;
    
    	char *output_buffer;
    	float *input_buffer;
    
    	SAFEARRAYBOUND re_array[1];
    
    	/* get input array lbound */
    	SafeArrayGetLBound(*input_frame, 1, &in_LBound); 
    
    	/* get input array ubound */
    	SafeArrayGetUBound(*input_frame, 1, &in_UBound); 
    
    		/* alloc memory for input */
    		input_buffer = new float [input_size];
    
    		/* copy input safearray to float-array */
    		for(i = in_LBound; i <= in_UBound; i++) 
    			SafeArrayGetElement(*input_frame, &i, &input_buffer[i]);
    
    		/* ** other functions ** */
    
    		/* delete memory used for input */
    		delete [] input_buffer;
    
    		/* get frame size in bytes */
    		MAX_NB = /* ** other function ** */
    
    		/* alloc memory for output */
    		output_buffer = new char [MAX_NB];
    
    		/* set new elements in bound */
    		re_array[0].cElements = MAX_NB;
    
    		/* redim array to new size */
    		SafeArrayRedim(*output_bytes, re_array);
    
    		/* get output array lbound */
    		SafeArrayGetLBound(*output_bytes, 1, &out_LBound); 
    
    		/* get output array ubound */
    		SafeArrayGetUBound(*output_bytes, 1, &out_UBound); 
    
    		output_buffer = /* ** other function - return char* ** */
    
    		/* copy char-array to output safearray */
    		for(i=out_LBound; i<out_UBound; i++)
    			SafeArrayPutElement(*output_bytes, &i, &output_buffer[i]);
    
    		/* delete memory used for output */
    		delete [] output_buffer;
    
    	return nbBytes;
    }
    

    So müsste ich doch das VB array aus meiner DLL raus neudimensionieren können:

    SAFEARRAYBOUND re_array[1];
    
    /* set new elements in bound */
    re_array[0].cElements = ANZAHL_DIM;
    
    * redim array to new size */
    SafeArrayRedim(*output_bytes, re_array);
    

    Danke und Gruss
    Sören



  • Du solltest die Struktur richtig initialisieren:

    re_array[0].cElements = ANZAHL_DIM;
    re_array[0].lLbound   = out_LBound;  // <-- fehlt bei Dir!
    

    BTW: Wie groß sind denn Deine Arrays im Mittel? Aber einer gewissen Größe gibt es eine wesentlich schnellere Möglichkeit des Kopierens:

    VARTYPE vt = VT_EMPTY;
    SafeArrayGetVartype(*input_frame, &vt);
    
    if(VT_R4 != vt)
    {
        // Es sind keine floats im Array gespeichert!
        // Was nun?
    }
    
    if(input_size > abs(in_UBound - in_LBound))
    {
        // input_size ist groesser als das Array!
        // Was nun?
    }
    
    input_buffer = new float [input_size];
    
    float* pData = NULL;
    SafeArrayAccessData(*input_frame, (void**)&pData);
    CopyMemory(input_buffer, pData, input_size * sizeof(float));
    SafeArrayUnaccessData(*input_frame);
    

    Aber sicherer ist's natürlich mit SafeArrayGetElement/ PutElement. Musst schauen, was für Dich geeigneter ist.



  • Spontex schrieb:

    Mmhh wenn ich das bei mir im VC6 so mache, deaktiviert er mir beim start alle breakpoints, was so ja nicht im Sinne des Erfinders ist. Was mache ich da falsch?

    Für den VC6 mußt Du einen 'harten' Breakpoint verwenden. Darum brauchst Du Dich glücklicherweise nicht selbst zu kümmern, sonst kannst auf eine API-Funktion zurückgreifen: DebugBreak.

    Du setzt nun diesen 'harten' Breakpoint gleich an den Funktions-Anfang, z.B. so:

    declspec__ (dllexport) int __stdcall MeineFunktion (SAFEARRAY **input_frame, SAFEARRAY **output_bytes, int input_size)
    {
        DebugBreak();
        .
        .
        .
    

    Und so übersetzt Du jetzt die Dll. Danach geht's über das Menü 'Erstellen'. Im SubMenu 'Debug Starten' wählst Du das Item 'Verbinden mit Prozess'. Der Debugger sollte jetzt beim Erreichen des Breakpoints anhalten. Du wirst dann aber vermutlich noch zweimal auf F11 drücken müssen, bevor Du das Disassembly-Window schliessen kannst. 😉



  • BTW: Wie groß sind denn Deine Arrays im Mittel? Aber einer gewissen Größe gibt es eine wesentlich schnellere Möglichkeit des Kopierens:

    Im Mittel ca. 10000 Einträge.



  • Spontex schrieb:

    Im Mittel ca. 10000 Einträge.

    Dann solltest Du die Möglichkeit mit CopyMemory nutzen, IMO.


Anmelden zum Antworten