Zeiger aus C-DLL zurückgeben
-
Hallo zusammen,
ich hoffe, dass ich in diesem Forum richtig bin.
Ich darf zum ersten Mal in meinem Leben eine DLL programmieren (in C), an die die folgenden Anforderungen gestellt sind:
1. Alle Operationen sollen als Funktionen und Prozeduren vorhanden sein.
2. Alle Operationen haben einen Eingabewert und einen Rückgabewert, beides sind Strings (also bei mir char *).
3. Der benötigte Speicher für den Rückgabestring soll in der DLL ermittelt werden, ohne dass der Aufrufer diesen Wert kennt (also auch keine Funktion, welche die benötigte Länge zurückgeben kann).Sind diese Anforderungen denn überhaupt erfüllbar?
Hab hier mal ein paar Codeausschnitte (wie gesagt, nur Ausschnitte - daher wenn möglich keine Fragen nach Funktionen, die zwar gerufen werden, aber hier nicht abgebildet sind!):
Header der DLL:
#ifndef _DLL_H_ #define _DLL_H_ #if BUILDING_DLL # define DLLIMPORT __declspec (dllexport) #else /* Not BUILDING_DLL */ # define DLLIMPORT __declspec (dllimport) #endif /* Not BUILDING_DLL */ // Function DLLIMPORT char *function (char *); // Procedure DLLIMPORT void procedure (char *, char *); #endif /* _DLL_H_ */
Die Funktion soll den String per Return zurückgeben, die Prozedur soll ihn in den zweiten Aufrufparameter kopieren. Der Speicher dafür soll in der DLL angelegt und vom Aufrufer freigegeben werden (ich weiss, das klingt ziemlich übel, aber geht so was mit den Heap-Funktionen: http://msdn.microsoft.com/en-us/library/aa366711(VS.85).aspx?).
Wie kann ich denn den Speicher für:
1. Die Returnwerte der Funktionen
2. Die Rückgabeparameter der Prozedurenin der DLL selbst anlegen, ohne den Aufrufer damit zu belasten?
C-Source der DLL:
// Function DLLIMPORT char *function (char *inputString) { // doing ... return "Function Return Value"; // later a pointer to an character array in memory shall be returned } // Procedure DLLIMPORT void moveToFrontProcedure (char *inputString, char *outputString) { // ...doing => Hier soll der Speicher für den OutputString reserviert und der Wert des Strings in den Aufrufparameter eingetragen werden - aber wie? => Meine Idee wäre die HeapFunctions zu benutzen. }
Bei den Zeilen mit Pfeil fehlt mir bisher jede Idee (mit Ausnahme der HeapFunctions).
C-Source of Caller:
// function pointer for signature of DLL functions typedef char *(*importFunction)(char *); // function pointer for signature of DLL procedures typedef void (*importProcedure)(char *, char *); // ... int callDLLFunction(HINSTANCE hinstLib, char *functionName, char *input) { // Get function pointer for the given function name importFunction functionDLL = (importFunction)GetProcAddress(hinstLib, functionName); if (functionDLL == NULL) { printf("ERROR: unable to find DLL function: %d\n", GetLastError()); cleanUpDLL(hinstLib); return 1; } // Call function => char *returnValue = functionDLL(input); printf("Return value of function %s: %s", functionName, returnValue); return 0; } int callDLLProcedure(HINSTANCE hinstLib, char *procedureName, char *input) { // Get function pointer for the given procedure name importProcedure procdureDLL = (importProcedure)GetProcAddress(hinstLib, procedureName); if (procdureDLL == NULL) { printf("ERROR: unable to find DLL function: %d\n", GetLastError()); cleanUpDLL(hinstLib); return 1; } // Call procedure procdureDLL(input, ""); return 0; }
Die Zuweisung und Ausgabe des Rückgabewertes (Zeile mit Pfeil) funktioniert in diesem Falle (also bei Rückgabe einer konstanten Zeichenkette). Aber tut das auch noch bei der Rückgabe einer Zeichenkette, die dynamisch in der DLL-Funktion im Speicher erstellt wird?
Vielen Dank schon einmal für eure Hilfen!
Ciao
-
Ich denke, daß das auch funktioniert, wenn Du in der DLL den Speicher mit malloc anforderst und der Aufrufer den Speicher mit free wieder freigibt.
Mal abgesehen davon, daß ich so eine Verfahrensweise für sehr unglücklich halte, aber offensichtlich hast Du da ja keinen Einfluss drauf.
Dein Prozeduraufruf darf dann natürlich nicht so aussehen:
procdureDLL(input, "");
sondern so:
char *ptr;
[...]
procdureDLL(input, ptr);
-
Vielen Dank für die Antwort.
Das hab ich auch schon probiert, aber es funktioniert nicht.
Auch das Allozieren von Speicher im Aufrufer und ein realloc in der DLL hat nach Rücksprung aus der DLL den Nichtempfang der Zeichenkette im Aufrufer zur Folge.
Zudem habe ich probiert, dem übergebenen Zeiger den Wert des Zeigers des reservierten Speicherbereiches aus der DLL zu geben (mit HeapAlloc) bzw. den String mit strncpy zu kopieren. Beides erfolglos.
Kann auch gern nochmal Code dazu posten.
Und wie sieht das Ganze aus, wenn DLL und Aufrufer in unterschiedlichen Programmiersprachen erstellt werden?
Ciao
-
Lass den caller den Speicher reservieren und übergihn mit als Paramter der Funktion. Deine Funktion gibt die Länge der Zeichenkette zurück:
Aufruf ohne Speicherreservierung: Funktion schlägt fehl und gibt zurück wie viel Speicher sie benötigt (Anzahl Zeichen oder was auch immer).
Aufrufer reserviert den benötigten Speicher und ruft die Funktion zum zweiten mal mit genügend Speicher auf.
Macht Windows auch nicht anders.
-
Ich bin jetzt schon im Wochenende - aber am Montag kann ich das gerne selbst mal ausprobieren.
Zum Thema Aufrufer in anderen Programmiersprachen:
Ich habe vor kurzem eine DLL bearbeiten müssen, die von einer Anwendung in UNIPHASE - keine Ahnung was das ist, oder ob es überhaupt richtig geschrieben ist - aufgerufen wird. Diese DLL gibt einen Zeiger auf ein global definiertes Char-Array zurück, und das funktioniert vom Allerfeinsten - ist natürlich nicht threadsicher. Das Problem hierbei war, daß der Aufrufer eine mir unbekannte Fremdanwendung ist und ich deshalb nicht verlangen durfte, daß mir Speicher zur Verfügung gestellt wird, oder wieder freigegeben wird.
Deshalb war (und bin ich eigentlich auch noch immer ;-)) ich der Meinung, daß das auch gehen muß, wenn der Speicher in der DLL dynamisch allokiert wird.
Im Mom. muß ich eine DLL schreiben, die von einem Delphi-Programm aufgerufen werden soll, da laß ich mir jedoch Speicher und Speichergröße anliefern.
Solange ich irgendwelchen Einfluß auf den Aufrufer habe, würde ich auch immer versuchen, den Speicher von diesem bereitstellen zu lassen.
-
Okay denn ... bei mir funktioniert folgendes einwandfrei:
#ifndef DLL_H #define DLL_H #ifdef __cplusplus extern "C" { #endif #include <windows.h> #ifdef BUILD_DLL #define EXPORT __declspec (dllexport) #else #define EXPORT __declspec (dllimport) #endif int WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved); EXPORT char * TestEins(const char * in); EXPORT void TestZwei(const char * in, char ** out); #ifdef __cplusplus } #endif #endif
#include <stdio.h> #include <stdlib.h> #define BUILD_DLL #include "testdll.h" int WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved) { return true; } char * Reverse(const char *text) { int len = strlen(text); int i; char * res = (char*) malloc(len + 1); for(i = 0; i < len; ++i) res[i] = text[len - i - 1]; res[len] = 0; return res; } EXPORT char * TestEins(const char * in) { char *tmp; printf("*** TestEins Eingabe: %s\n", in); tmp = Reverse(in); printf("*** TestEins Ausgabe: %s\n", tmp); return tmp; } EXPORT void TestZwei(const char * in, char ** out) { printf("*** TestZwei Eingabe: %s\n", in); *out = Reverse(in); printf("*** TestZwei Ausgabe: %s\n", *out); }
#include <stdio.h> #include "testdll.h" int main () { char test[] = "ABCDEFGH"; char *res1; char *res2; //Reihenfolge umkehren res1 = TestEins(test); printf("\nres1 = %s\n\n", res1); //und wieder zurück TestZwei(res1, &res2); printf("\nres2 = %s\n", res2); free(res1); free(res2); }
-
1. Man kann über Modulgrenzen hinweg Speicher allokieren und freigeben mit malloc/free, wenn die gleiche CRT-DLL verwendet wird. (Statisches linken der CRT schließt sich aus)
2. Es steht einem auch frei die WinAPI Heap Funktionen zu verwenden.
3. Das ganze kann man strukturell umgehen indem man entsprechende Factory Methoden für Allokation und Deallokation in der DLL schafft.Wenn Speicher von einem Moudl EXE/DLL aus allokiert wird, dann sollte dieser auch wieder von dem Modul freigegeben werden.
Ich rate von 1+2 ab...
-
Vielen Dank für eure Antworten!
Eigentlich bin ich auch der Ansicht, dass Reservieren und Freigeben von Speicherbereichen in der selben Instanz erfolgen soll, habe aber im aktuellen Fall wenig Einfluss drauf!
Hm, so ähnlich hatte ich es auch schon probiert:
malloc() in der DLL und free() im Aufrufer.
Habe allerdings immer nur einen Zeiger übergeben und nicht die Adresse des Zeigers, daher wird es wohl nicht geklappt haben!
Warum muss ich hier die Adresse des Zeigers (für den Rückgabeparameter im Prozedurfall) übergeben? Kann mir das jemand vielleicht noch etwas näher erläutern?
Hatte bisher den Zeiger der DLL übergeben, diese hatte Speicher reserviert und ihm diesen Zeiger zugewiesen (also zieger = malloc(...)). Danach wurden die Zeichen in diesen Speicherbereich geschrieben und zum Aufrufer zurückgekehrt.
Dieser konnte die Zeichen aber nicht ausgeben!Habe hier also irgendwo noch nen Verständnisfehler, das zu Zeiger auf Zeiger führt. Gibt es da keine andere Möglichkeit?
Hatte noch an Variablen gedacht, die in der DLL verfügbar sind (während der gesamten Instanzlaufzeit) und für die dann Speicher reserviert wird, der beim Entladen wieder freigegeben wird.
Die entsprechenden Adressen werden dann dem reingegebenen Zeiger mitgeteilt.
Oder brauche ich hier auch Zeiger auf Zeiger (also Adresse des Zeigers)?Ciao
-
implementier doch einfach eine free methode in der dll die nur free() aufruft
-
Reth schrieb:
Warum muss ich hier die Adresse des Zeigers (für den Rückgabeparameter im Prozedurfall) übergeben? Kann mir das jemand vielleicht noch etwas näher erläutern?
Weil Du den Wert des Zeigers beim Aufrufer verändern willst. Wenn Du nur den Zeiger übergibst (per value), dann kannst Du ihn in einer aufgerufenen Funktion (unabhängig davon, ob das in einer DLL, oder in Deinem eigenen Modul ist) nur lokal überschreiben.
Angenommen, Du übergibst einen int-wert an eine Funktion und änderst diesen dort, ist die Änderung beim Aufrufer nicht sichtbar - erst wenn Du die Adresse des ints übergibst und via Dereferenzierung den Wert änderst.
Genau so ist es beim Zeiger auch - Du willst ja nicht den Speicher beschreiben, auf den Dein Zeiger beim Aufrufer zeigt (erstmal zeigt der ja nirgendwohin), sondern den Zeigerwert selbst ändern, deshalb mußt Du die Adresse des Zeigers übergeben, und via Dereferenzierung den Zeigerwert auf den von malloc gelieferten Bereich setzen.
-
Vielen Dank. Verstanden.
Gibt es denn eine Möglichkeit, die mit einem einfachen Zeiger auskommt oder mit den Heap-Funktionen?
Ciao
-
Nur, wenn Du Deinen char-Pointer in einer Struktur versteckst:
[...] struct MyPtr { char * ptr; }; [...] EXPORT void TestZwei(const char * in, struct MyPtr * out); [...]
[...] EXPORT void TestZwei(const char * in, struct MyPtr * out) { printf("*** TestZwei Eingabe: %s\n", in); out->ptr = Reverse(in); printf("*** TestZwei Ausgabe: %s\n", out->ptr); }
char test[] = "ABCDEFGH"; char *res1; struct MyPtr res2; [...] //und wieder zurück TestZwei(res1, &res2); printf("\nres2 = %s\n", res2.ptr); free(res1); free(res2.ptr);
Allerdings denke ich, daß diese Verfahrensweise die Gefahr birgt, daß man nicht mehr weiß, was denn nun via free freizugeben ist.
-
HeapAlloc/HeapFree
oder BSTR!
-
Martin Richter schrieb:
HeapAlloc/HeapFree
oder BSTR!BSTR hab ich mir noch nicht angesehen.
HeapAlloc und HeapFree hatte ich mal probiert, hat aber noch nicht geklappt.
Gibt es da einen Link zu einem funktionierenden Beispiel?
Funktioniert das auch, wenn das aufrufenden Programm in einer eher exotischen Programmiersprache gehalten ist (in meinem Fall KCML)?
Ciao
-
Was geht nicht? Zeig mal bitte etwas Code.