2015-03-11 18 views
6

Il problema che ho è che voglio creare un'applicazione generica da riga di comando che può essere utilizzata per caricare una libreria DLL e quindi chiamare una funzione nel libreria DLL. Il nome della funzione è specificato sulla riga di comando con gli argomenti forniti anche nella riga di comando dell'utilità.C Passare gli argomenti come void-pointer-list alla funzione importata da LoadLibrary()

Posso accedere alla funzione esterna da una DLL caricata in modo dinamico utilizzando la funzione LoadLibrary(). Una volta caricata la libreria, posso ottenere un puntatore alla funzione usando GetProcAddress() Voglio chiamare la funzione con gli argomenti specificati sulla riga di comando.

Posso passare un elenco di puntatori void al puntatore di funzione che è stato restituito dalla funzione LoadLibrary() simile all'esempio seguente?

Per mantenere il codice di esempio semplice, ho eliminato il controllo degli errori. C'è un modo per ottenere qualcosa di simile a questo lavoro:

 
    //Somewhere in another dll 
    int DoStuff(int a, int b) 
    { 
     return a + b; 
    } 
 
    int main(int argc, char **argv) 
    { 
     void *retval; 
     void *list = argv[3]; 
     HMODULE dll; 
     void* (*generic_function)(void*); 

     dll = LoadLibraryA(argv[1]); 

     //argv[2] = "DoStuff" 
     generic_function = GetProcAddress(dll, argv[2]); 

     //argv[3] = 4, argv[4] = 7, argv[5] = NULL 
     retval = generic_function(list); 
    } 

Se ho dimenticato di menzionare le informazioni necessarie, per favore fatemelo sapere. Grazie in anticipo

+0

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

+0

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)'. –

+0

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

risposta

4

È necessario eseguire il cast del puntatore di funzione restituito da LoadLibrary in uno con i tipi di argomenti corretti prima di chiamarlo. Un modo per gestirlo è quello di avere un numero di funzioni call-adattatore che fanno la cosa giusta per ogni possibile tipo di funzione che si potrebbe desiderare di chiamare:

void Call_II(void (*fn_)(), char **args) { 
    void (*fn)(int, int) = (void (*)(int, int))fn_; 
    fn(atoi(args[0]), atoi(args[1])); 
} 
void Call_IS(void (*fn_)(), char **args) { 
    void (*fn)(int, char *) = (void (*)(int, char *))fn_; 
    fn(atoi(args[0]), args[1]); 
} 
...various more functions 

Poi si prende il puntatore che hai da GetProcAddress e gli argomenti aggiuntivi e passarli alla funzione corretta Call_X:

void* (*generic_function)(); 

dll = LoadLibraryA(argv[1]); 

//argv[2] = "DoStuff" 
generic_function = GetProcAddress(dll, argv[2]); 

//argv[3] = 4, argv[4] = 7, argv[5] = NULL 

Call_II(generic_function, &argv[3]); 

il problema è che è necessario sapere qual è il tipo di funzione che stai ricevendo il puntatore per è e chiamare la funzione adattatore appropriato. Che in genere significa creare una tabella di nomi di funzione/adattatori e fare una ricerca al suo interno.

Il problema correlato è che non esiste alcuna funzione analoga a GetProcAddress che indicherà i tipi di argomento per una funzione nella libreria: tali informazioni semplicemente non sono memorizzate in nessun punto accessibile nella DLL.

1

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); 
+0

Approccio molto interessante! Non pensavo di interfacciare la procedura di chiamata/elenco-passaggio. Approfondirò decisamente questo aspetto. Grazie per la tua spiegazione/risposta sofisticata e ben dettagliata. Buon lavoro! – h4x0r

Problemi correlati