Una libreria DLL contiene il codice oggetto per le funzioni che fanno parte della libreria insieme ad alcune informazioni aggiuntive per consentire alla DLL di essere utilizzabile.
Tuttavia, una DLL della libreria non contiene le informazioni sul tipo necessarie per determinare l'elenco di argomenti e i tipi specifici per le funzioni contenute nella DLL della libreria. Le informazioni principali in una libreria DLL sono: (1) un elenco delle funzioni che la DLL esporta insieme alle informazioni sull'indirizzo che collegherà una chiamata di una funzione al codice binario della funzione effettiva e (2) un elenco di qualsiasi DLL richiesta che le funzioni nella libreria DLL utilizzano.
È possibile aprire una DLL di libreria in un editor di testo, ne suggerisco una piccola e scansionare attraverso i simboli arcani del codice binario fino a raggiungere la sezione che contiene l'elenco di funzioni nella DLL della libreria e altre DLL richieste.
Quindi una libreria DLL contiene le informazioni minime necessarie per (1) trovare una funzione particolare nella libreria DLL in modo che possa essere invocata e (2) una lista di altre DLL necessarie che dipendono dalle funzioni nella libreria DLL sopra.
Questo è diverso da un oggetto COM che normalmente dispone di informazioni sul tipo per supportare la capacità di fare ciò che è fondamentalmente la riflessione ed esplorare i servizi dell'oggetto COM e il modo in cui tali servizi sono accessibili. È possibile farlo con Visual Studio e altri IDE che generano un elenco di oggetti COM installati e consentono di caricare un oggetto COM ed esplorarlo. Visual Studio ha anche uno strumento che genererà i file del codice sorgente che forniscono gli stub e includono il file per accedere ai servizi e ai metodi di un oggetto COM.
Tuttavia una libreria DLL è diversa da un oggetto COM e tutte le informazioni aggiuntive fornite con un oggetto COM non sono disponibili da una libreria DLL. Invece un pacchetto di librerie DLL è normalmente costituito da (1) la libreria DLL stessa, (2) un file .lib che contiene le informazioni di collegamento per la libreria DLL insieme agli stub e alle funzionalità per soddisfare il linker quando si costruisce l'applicazione che utilizza la libreria DLL e (3) un file include con i prototipi di funzione delle funzioni nella libreria DLL.
Così si crea l'applicazione chiamando le funzioni che risiedono nella libreria DLL ma utilizzando le informazioni sul tipo dal file di inclusione e il collegamento con gli stub del file .lib associato. Questa procedura consente a Visual Studio di automatizzare gran parte del lavoro richiesto per utilizzare una DLL di libreria.
Oppure è possibile digitare il codice LoadLibrary()
e la creazione di una tabella delle funzioni nella libreria DLL utilizzando GetProcAddress()
. Facendo la codifica manuale tutto ciò di cui hai veramente bisogno sono i prototipi di funzioni delle funzioni nella DLL della libreria che puoi quindi inserire in te stesso e nella DLL della libreria stessa. In effetti stai facendo il lavoro a mano che il compilatore di Visual Studio fa per te se stai usando gli stub della libreria .lib e includi il file.
Se si conosce il nome della funzione reale e la funzione prototipo di una funzione in una DLL library allora che cosa si potrebbe fare è di avere la vostra linea di comando richiedono le seguenti informazioni:
- il nome della funzione di essere chiamato come una stringa di testo sulla riga di comando
- l'elenco degli argomenti da utilizzare come una serie di stringhe di testo sulla riga di comando
- un ulteriore parametro che descrive il prototipo di funzione
Questo è simile a come funzionano le funzioni nel runtime C e C++ che accettano elenchi di argomenti variabili con tipi di parametri sconosciuti. Ad esempio la funzione printf()
che stampa un elenco di valori di argomento ha una stringa di formato seguita dagli argomenti da stampare. La funzione printf()
utilizza la stringa di formato per determinare i tipi dei vari argomenti, il numero di argomenti da prevedere e i tipi di trasformazioni di valori da eseguire.
Così, se il programma di utilità aveva una riga di comando simile al seguente:
dofunc "%s,%d,%s" func1 "name of " 3 " things"
e la DLL library aveva una funzione il cui prototipo sembrava:
void func1 (char *s1, int i, int j);
quindi l'utilità sarebbe generare dinamicamente il chiamata di funzione trasformando le stringhe di caratteri della riga di comando nei tipi effettivi necessari per la chiamata alla funzione.
Questo potrebbe funzionare per semplici funzioni che accettano tipi Plain Old dati tipi tuttavia più complessi come ad esempio struct
tipo di argomento richiederebbe più lavoro, come si avrebbe bisogno di un qualche tipo di una descrizione del struct
insieme ad una sorta di descrizione dell'argomento forse simile a JSON.
Appendice I: Un semplice esempio
Il seguente è il codice sorgente di un'applicazione di Visual Studio console di Windows che mi sono imbattuto nel debugger. Gli argomenti del comando nelle Proprietà erano pif.dll PifLogAbort
che causava il caricamento di una DLL di libreria da un altro progetto, pif.dll e quindi la funzione PifLogAbort()
in quella libreria da richiamare.
Nota come l'elenco di argomenti della funzione PifLogAbort()
viene creato come una struttura che contiene un array. I valori dell'argomento vengono inseriti nell'array di una variabile di struct
e quindi la funzione viene chiamata passando l'intero struct
in base al valore. Ciò che fa è di spingere una copia della matrice di parametri nello stack e quindi chiama la funzione. La funzione PifLogAbort()
vede lo stack in base al suo elenco di argomenti ed elabora gli elementi dell'array come singoli argomenti o parametri.
// dllfunctest.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
typedef struct {
UCHAR *myList[4];
} sarglist;
typedef void ((*libfunc) (sarglist q));
/*
* do a load library to a DLL and then execute a function in it.
*
* dll name.dll "funcname"
*/
int _tmain(int argc, _TCHAR* argv[])
{
HMODULE dll = LoadLibrary(argv[1]);
if (dll == NULL) return 1;
// convert the command line argument for the function name, argv[2] from
// a TCHAR to a standard CHAR string which is what GetProcAddress() requires.
char funcname[256] = {0};
for (int i = 0; i < 255 && argv[2][i]; i++) {
funcname[i] = argv[2][i];
}
libfunc generic_function = (libfunc) GetProcAddress(dll, funcname);
if (generic_function == NULL) return 2;
// build the argument list for the function and then call the function.
// function prototype for PifLogAbort() function exported from the library DLL
// is as follows:
// VOID PIFENTRY PifLogAbort(UCHAR *lpCondition, UCHAR *lpFilename, UCHAR *lpFunctionname, ULONG ulLineNo);
sarglist xx = {{(UCHAR *)"xx1", (UCHAR *)"xx2", (UCHAR *)"xx3", (UCHAR *)1245}};
generic_function(xx);
return 0;
}
Questo semplice esempio illustra alcuni ostacoli tecnici che devono essere superati. Dovrai sapere come tradurre i vari tipi di parametri nell'allineamento corretto in un'area di memoria che viene poi inserita nello stack.
L'interfaccia per questa funzione di esempio è notevolmente omogenea in quanto la maggior parte degli argomenti sono i puntatori unsigned char
con l'eccezione dell'ultimo che è un int
. Con un eseguibile a 32 bit, tutti e quattro questi tipi di variabili hanno la stessa lunghezza in byte. Con un elenco più vario di tipi nella lista degli argomenti è necessario avere una comprensione di come il compilatore allinea i parametri quando sta spingendo gli argomenti sullo stack prima di effettuare la chiamata.
Appendice II: Estendere la semplice esempio
Un'altra possibilità è quella di avere un set di funzioni di supporto insieme ad una versione differente del struct
. struct
fornisce un'area di memoria per creare una copia dello stack necessario e le funzioni di guida vengono utilizzate per creare la copia.
Quindi il struct
e le sue funzioni di supporto possono apparire come le seguenti.
typedef struct {
UCHAR myList[128];
} sarglist2;
typedef struct {
int i;
sarglist2 arglist;
} sarglistlist;
typedef void ((*libfunc2) (sarglist2 q));
void pushInt (sarglistlist *p, int iVal)
{
*(int *)(p->arglist.myList + p->i) = iVal;
p->i += sizeof(int);
}
void pushChar (sarglistlist *p, unsigned char cVal)
{
*(unsigned char *)(p->arglist.myList + p->i) = cVal;
p->i += sizeof(unsigned char);
}
void pushVoidPtr (sarglistlist *p, void * pVal)
{
*(void * *)(p->arglist.myList + p->i) = pVal;
p->i += sizeof(void *);
}
E poi le struct
e helper funzioni sarebbero stati utilizzati per costruire la lista di argomenti come la seguente, dopo il quale la funzione dalla DLL library viene richiamato con la copia della pila fornita:
sarglistlist xx2 = {0};
pushVoidPtr (&xx2, "xx1");
pushVoidPtr (&xx2, "xx2");
pushVoidPtr (&xx2, "xx3");
pushInt (&xx2, 12345);
libfunc2 generic_function2 = (libfunc2) GetProcAddress(dll, funcname);
generic_function2(xx2.arglist);
Calling la convenzione potrebbe alla fine darti dolore. Le aspettative della funzione reale su come la chiami sono un'ipotesi ovvia. E "... con argomenti passati", notando la * pluralità * di questa affermazione, è interessante. Stai passando * uno *. Se hai bisogno di qualcosa di più (cioè ti stai aspettando che invii magicamente 'argv [3] ... argv [argc-1] 'come argomenti di funzione), questo non lo farà, e farlo correttamente diventa complicato rapidamente . – WhozCraig
Non è chiaro cosa intendi con 'se potrei passare un elenco di puntatori void al puntatore di funzione'. Se la funzione che stai chiamando è definita come __...MyFunction (void *) __, quindi sì, puoi chiamarlo così, altrimenti non saresti in grado di farlo. Inoltre, assicurati che sia etichettato come 'declspec (stdcall)'. –
Ho appena cercato un modo per ottenere uno strumento da riga di comando da chiamare, ad es. kernel32.dll e qualsiasi sua funzione e passare argomenti ad esso. Speravo - come ha detto WhozCraig - per un modo magico di trovare l'elenco degli argomenti da una funzione importata – h4x0r