[c/WINAPI] GetProcAddress vollkommen dynamisch

cwriter

Erfahrenes Mitglied
Hallo Welt

Um eine Funktion dynamisch aus einer dll zu laden, empfiehlt MSDN die Funktion GetProcAddress(). Nun ist mir das noch nicht dynamisch genug, denn man braucht dafür typedefs und casts, die man ja nicht erst zur Runtime angeben kann.
Ich habe mir überlegt, dass man einfach einen typedef haben kann, der alles auf void hat, um danach einfach dynamisch zu casten. Gibt es dafür irgendeinen Workaround oder eine Möglichkeit, dynamisch zu casten/zu "typedefen"? (Ich weiss, dass C++ 11(?) sowas bietet, würde aber gerne einen anderen Weg finden, da ich kein Fan von templates bin :) )

Oder ist GetProcAddress() der falsche Weg dafür?
Ich möchte ein Programm durch die dlls möglichst flexibel machen, also alles laden können, wozu ich gerade Lust habe. Im Notfall könnte ich auch einfach einen Funktionsnamen in der dll vorschreiben (z.B. int func(void*)) und dann diese Funktion aufrufen, damit diese dann den Rest macht, aber wie gesagt: Es wäre mir lieber, wäre es ein bisschen dynamischer.

Gruss
cwriter
 
Hi

vorausgesetzt, du weißt zur Laufzeit, welche (Art/Anzahl von) Parameter(n) benötigt sind,
könnte man sich den Aufruf selbst zusammenbasteln.
siehe Aufrufkonventionen wie stdcall und cdecl, thiscall;
die Verwendung von Inline-Asm und das Realisieren von Funktionsaufrufen damit.

edit: Start hier: http://en.wikipedia.org/wiki/X86_calling_conventions

Über Arrays/Listen etc. von Paraminfos (die ja zur Laufzeit beliebig erweiterbar sind)
kann man ja dann später drüberschleifen, keine Bindung an Casts etc. der Compilezeit.
 
die Verwendung von Inline-Asm und das Realisieren von Funktionsaufrufen damit.
Bei den DLLs ist ja stdcall praktisch vorgeschrieben, da ich gelesen habe, dass Delphi z.B. nichts mit einer cdecl-Funktion anfangen kann. Vielleicht ist das aber nur Beifang aus dem Internet, der nicht stimmt.
Aber falls schon: stdcall räumt ja in der aufgerufenen Funktion auf und gibt per naming convention nach dem '@' die Anzahl der Bytes an. Wozu bräuchte ich dann ASM? Mit void* oder char* als Argument habe ich ja meine garantierten 4 bzw. 8 Bytes (k.A. wie gross ein std::vector ist).
Die Frage bliebe nur nach dem Rückgabewert.
Oder wie meintest du
Über Arrays/Listen etc. von Paraminfos (die ja zur Laufzeit beliebig erweiterbar sind)
kann man ja dann später drüberschleifen, keine Bindung an Casts etc. der Compilezeit.
?

Gruss und Danke für die schnelle Antwort
cwriter
 
Mit Delphi-Interna kenn ich mich nicht aus, zum Rest:

Calling Convention != Name mangling

Die eventuell vorhandenen Zusätze mit @ etc. zum Funktionsnamen sind eine eigene Sache.
Prinzipiell muss man für die nichts tun, man muss aber den korrekten Funktionsnamen kennen,
um eine Funktion aufrufen zu können.

Was eine CC unter Anderem ausmacht:
*Wenn eine Funktion aufgerufen wird: Welche Registerspeicher darf sie für sich verwenden
(einfach nach Bedarf überschreiben), und welche eben nicht (die dürfen dann schon verwendet
werden, solange der ursprüngliche Wert am Funktionsende wieder drin steht.
Die Werte können zB. in der Zwischenzeit am RAM gespeichert werden, später wieder ausgelesen)
*Wo sind die Parameterwerte (Register, Stack, ...),
ihre Reihenfolge (verglichen zum Code richtig oder verkehrt)
*Wo kommt der Returnwert hin
*Was muss vom Funktionsinneren und was von der aufrufenden Codestelle
wieder in den Ursprungszustand (vor Aufrufsbeginn) gesetzt werden
(zB. die am Stack gespeicherten Parameter wieder entfernen)
...

Bei einem Funktionsaufruf muss der Compiler einige Vor/Nacharbeit dazugenerieren,
und genau das muss man jetzt selbst übernehmen.
Wenn die Funktion immer den selben Returntyp, selbe CC, selbe Anzahl und Typen der Parameter
hat kann man dm Compiler die Arbeit überlassen, in dem man eben ein typedef oÄ. macht.
Bei Unterschieden (vor allem unterschiedliche Paramanzahl) kann man das dann nicht mehr
in C-Syntax ausdrücken, daher auch kein Compiler, der einem das abnimmt
->Ein kleiner Teil Asm ins C-Programm, und dort die Arbeit selbst machen.
Wenn man Infos über die Parameter etc. in C-Arrays oÄ. hat
kann man das dort dann in einer Schleife durchgehen und auf den Stack schieben usw.

zB. cdecl ist im Wikipedialink ausführlich beschrieben.
Wenn man die Sonderfälle Klassen, Kommazahlen und abweichende Compiler weglässt
und das Ganze von der Aufrufenden Seite betrachtet:
*Dass die Register am Asm-Beginn frei sind macht der Compiler
*Parameter auf den Stack pushen, letzten zuerst
*Wenn du für das Pushen die Register EAX, ECX und/oder EDX gebraucht hast
und die Werte für die unteren Schritte noch brauchst, die auch pushen.
*Funktion Aufrufen.
*Wenn die Funktion nicht void ist: Den Returnwert in EAX in den Ram speichern
(da hin, wo die Ziel-Variable in C ist)
*Ggf. Register poppen
*Parameter poppen (genau so viel, wie man gepusht hat natürlich).
 
Hi.

Im Grunde hat Microsoft ja mit P/Invoke im .NET Framework eine Methodik eingeführt wie man beliebige Funktionen von DLLs aufrufen kann.

Man muss eben nur eine Beschreibung / Syntax für die Deklaration der Funktionen entwerfen.

Ob diese nun zur Kompilierzeit definiert und umgesetzt werden wie bei MS oder zur Laufzeit ist dann Geschmackssache. Es ist allerdings kein ganz so einfaches Unterfangen (siehe sheels Ausführungen).

Eine weitere Idee wäre die LLVM einzusetzen oder libffi zu benutzen.
 
Hallo und danke für die vielen Antworten

Da ich mich in Assembler überhaupt nicht auskannte (mal vom "Aha, 'mov' gibt's" abgesehen), musste ich mich erst etwas einarbeiten.
Und ich musste feststellen, dass ich ein klein wenig von C verwöhnt bin.

Nach einigen Fehlermeldungen bin ich dann aber auf einen so minimalistischen Code gekommen, dass ich mich fast fragen muss, warum es dafür keine vordefinierte Funktion gibt...
C:
        HMODULE handle = LoadLibraryA("DLL.dll");
	if(handle == NULL) printf("Error: DLL did not load.\n");
	void* func = (void*)GetProcAddress(handle,"fnDLL");
	if(func == NULL) printf("ERROR: %d\n",GetLastError());

	int retval = 0;

	_asm
	{
		call func
		mov retval, eax
	}

	printf("retval=%d\n",retval);

	FreeLibrary(handle);
	system("PAUSE");
	return 0;
Gut, fairerweise muss ich anfügen, dass es hier keine Parameter gibt und beständig von einem int als Rückgabewert ausgegangen wird ;-)

Habe ich beim Assembler-Code noch irgendwelches Aufräumen vergessen oder geht das in Ordnung so? Oder muss ich nach einem call (da ich ja der caller bin) das wieder aufräumen? Oder ist nur das ASM-Subset der Caller und macht das selbst?
Ach ja, ich habe doch die _cdecl-Variante (extern "C") genommen, da _stdcall ja eigentlich _[Funktion]@[Parametergrösse in Bytes] produzieren sollte, es aber interessanterweise nicht tut. Wahrscheinlich ist es standardmässig gar kein _stdcall.
(Ja, ich könnte einen Decompiler oder sonst was in der Art einsetzen, aber so geht's auch.)

Zu
Wenn man Infos über die Parameter etc. in C-Arrays oÄ. hat
kann man das dort dann in einer Schleife durchgehen und auf den Stack schieben usw.
Es besteht nicht zufällig die Möglichkeit, das per Winapi in irgendeiner Form aus der DLL zu lesen? (Ich habe da aber schon einen üblen Verdacht...)

Es ist allerdings kein ganz so einfaches Unterfangen (siehe sheels Ausführungen).
Ja, das ist mir durchaus bewusst. Assembler war für mich bis jetzt sowas wie der Heilige Gral, der einfach tabu ist. Aber irgendwie möchte ich auch Fortschritte machen. Ich entschuldige mich schon mal im Voraus für all die dummen Fragen, die von meiner Seite kommen werden :)

Assembler ist irgendwie cool. Ich hoffe, ich habe mich jetzt nicht verliebt :-/

Gruss
cwriter
 
Nach einigen Fehlermeldungen bin ich dann aber auf einen so minimalistischen Code gekommen, dass ich mich fast fragen muss, warum es dafür keine vordefinierte Funktion gibt...
...
Gut, fairerweise muss ich anfügen, dass es hier keine Parameter gibt und beständig von einem int als Rückgabewert ausgegangen wird ;-)
Genau bei solchen Fix-Fällen kann man ja gleich einen Funktionspointer/typedef in C machen :p

Habe ich beim Assembler-Code noch irgendwelches Aufräumen vergessen oder geht das in Ordnung so? Oder muss ich nach einem call (da ich ja der caller bin) das wieder aufräumen? Oder ist nur das ASM-Subset der Caller und macht das selbst?
Ohne Parameterzeug, ohne verwendete Register
(die vom C-Code verwendeten übernimmt der Compiler),
für cdecl, schaut nicht schlecht aus...wenns funktioniert
(auch mit mehreren Aufrufen hintereinander etc.): Ok

Oder muss ich nach einem call (da ich ja der caller bin) das wieder aufräumen?
Oder ist nur das ASM-Subset der Caller und macht das selbst?
Erster Satz: Ja, du/der Caller muss. Aber wie du siehst ist das in dem Fall nicht viel.
Den zweiten Satz hab ich nicht verstanden.

Caller ist die Codestelle, die die Funktion aufrufen will.
Callee ist die Funktion bzw. ihr Inneres.
Je nach CC müssen bestimmte Aktionen vor/nach Aufruf
bzw. am Anfang/Ende der Funktion passieren usw.

Ach ja, ich habe doch die _cdecl-Variante (extern "C") genommen, da _stdcall ja eigentlich _[Funktion]@[Parametergrösse in Bytes] produzieren sollte, es aber interessanterweise nicht tut. Wahrscheinlich ist es standardmässig gar kein _stdcall.
(Ja, ich könnte einen Decompiler oder sonst was in der Art einsetzen, aber so geht's auch.)
Decompiler ist Overkill, sowas reicht: http://www.dependencywalker.com/
bzw. ist bei VS wahrscheinlich sowieso dabei.
Sonst: Beim kompilieren der DLL kann man sich auch so eine Datei erzeugen lassen (denk ich)

Es besteht nicht zufällig die Möglichkeit, das per Winapi in irgendeiner Form aus der DLL zu lesen? (Ich habe da aber schon einen üblen Verdacht...)
Die gemangelten Names:
http://stackoverflow.com/questions/1128150/win32-api-to-enumerate-dll-export-functions
Die komplette Parameterinfo etc.:
Dein Verdacht bestätigt sich.
Bei beliebigen DLLs, die nicht extra vorbereitet wurde,
damit sie mit deinem System zusammenarbeiten, wird das nichts.

Ja, das ist mir durchaus bewusst. Assembler war für mich bis jetzt sowas wie der Heilige Gral, der einfach tabu ist. Aber irgendwie möchte ich auch Fortschritte machen.
http://en.wikipedia.org/wiki/Grails_(framework) :p

Ich entschuldige mich schon mal im Voraus für all die dummen Fragen, die von meiner Seite kommen werden :)
Du weißt doch, dass es keine dumme Fragen gibt :)
Und dass ein Forum nur von Fragen leben kann...

Assembler ist irgendwie cool. Ich hoffe, ich habe mich jetzt nicht verliebt :-/
Willkommen im H... äh Club :D
 
Hallo Welt

Ich bin zu folgendem Lösungsweg gekommen:


C:
bool _cdecl FunctionFromDll(HMODULE mod, const char* name, void* ret, const char* format, ...)
Diese Funktion lädt als der DLL mod die Funktion name, der Rückgabewert wird in ret gespeichert und das Parameterformat wird mit format übergeben. Danach kommen die Parameter selbst in Form einer va_list (ja, nicht gerade das Schönste, aber IMHO die einfachste Möglichkeit, verschiedene Typen zu übergeben.

Um keine Kompetenzprobleme mit dem Stack zu bekommen (damit die for()-Schleife nicht die frisch gespeicherten Parameter überschreibt), wird zuerst alles in einen void**-array geladen. Das funktioniert soweit. (Lieber so als 200x zu pushen)
Nun zum inline asm:
Analog zu http://msdn.microsoft.com/de-de/library/y8b57x4b(v=vs.90).aspx, hier der Code direkt kopiert:
C:
// InlineAssembler_Calling_C_Functions_in_Inline_Assembly.cpp
// processor: x86
#include <stdio.h>

char format[] = "%s %s\n";
char hello[] = "Hello";
char world[] = "world";
int main( void )
{
   __asm
   {
      mov  eax, offset world
      push eax
      mov  eax, offset hello
      push eax
      mov  eax, offset format
      push eax
      call printf
      //clean up the stack so that main can exit cleanly
      //use the unused register ebx to do the cleanup
      pop  ebx
      pop  ebx
      pop  ebx
   }
}
Hier mein Code:
C:
//reqsize == strlen(format) == Anzahl Parameter
//pointers == void**
//func == Geladene Funktion
//retval == int
_asm
	{
		mov ecx, reqsize
read:
		mov eax, pointers
		push eax
		loop read

		call func
		mov retval, eax

		mov ecx, reqsize
unload:
		pop ebx
		loop unload

	}
	memcpy(ret,&retval,sizeof(int));
(Ja, ich weiss, dass das noch nicht besonders dynamisch ist, aber ich mache gerne kleine Schritte.)
Nun, dieser Code funktioniert nicht, und ich weiss sogar, warum (Fortschritt!): mov eax,pointers führt nur zu einer Kopie der Pointeradresse. Nun kann man ja nicht direkt Pointer vermischen (http://bytes.com/topic/net/answers/544632-dereference-pointer-inline-assembly)
Nun zur Frage: Woher kriege ich mehr Register, ohne die nicht-volatile zu überschreiben?
ecx ist bereits für die Schleife reserviert, und ebx hat möglicherweise schon einen anderen Wert gespeichert, den ich nicht überschreiben will)

Oder habe ich da was falsch verstanden?
Wenn ich das eax-Register pushe, erhöhe ich den Wert des esp, und damit wird der Wert von eax nun in ebx sein.

Gruss
cwriter

/EDIT
C:
_asm
	{
		mov ecx, reqsize
read:
		mov edx, pointers
		mov ebx, [edx+(ecx-1)]
		mov eax, [ebx]
		push eax
		loop read

		call func
		mov retval, eax

		mov ecx, reqsize
unload:
		pop ebx
		loop unload

	}
So funktioniert es nur für einen Parameter.

/EDIT2:
Irgendwie bin ich schon dumm. (Nicht als Anlass nehmen, mir das zu bestätigen, bitte :) )
C:
_asm
	{
		mov ecx, reqsize
read:
		mov edx, pointers
		mov eax, ecx
		dec eax
		shl eax, 2
		mov ebx, [edx+eax]
		mov eax, [ebx]
		push eax
		loop read

		call func
		mov retval, eax

		mov ecx, reqsize
unload:
		pop ebx
		loop unload

	}
Dieser Code funktioniert. Mein Fehler war, dass ich durch alle Umkehrungen (Befüllung des Arrays - cdecl Umkehrung - Befüllen von eax usw. den Überblick verloren habe und das Programm dann versuchte, einen String vom int-Wert 3 aus zu schreiben. Tut nicht gut...
Dennoch: Sieht gerade jemand einen Fehler in diesem Code?

Gruss
cwriter
 
Zuletzt bearbeitet:
Zurück