2013-08-06 13 views
18

Sto cercando di rinviare una chiamata di funzione (usando una funzione wrapper), salvando i suoi argomenti in un elenco di puntatori void:come di rinviare una chiamata di funzione in C

void *args[] 
int argt[] 

L'ARGT viene utilizzato in modo per ricordare il tipo di dati memorizzati nella posizione * vuota.

Più tardi, ho bisogno di chiamare la funzione rinviata:

function(args[0], args[1]) 

ma il problema è che devo specificare il tipo correttamente.

Io uso una macro, come questo:

#define ARGTYPE(arg, type) type == CHARP ? (char *) arg : (type == LONGLONG ? *((long long *) arg) : NULL) 

e la chiamata di funzione diventa:

function(ARGTYPE(args[0], argt[0]), ARGTYPE(args[1], argt[1])) 

ho due problemi:

1) avvertimento: puntatore/integer tipo disadattamento nell'espressione condizionale, generata dalla definizione di macro (si noti che posso conviverci, vedere 2))

2) il vero problema: l'argomento long long non viene passato correttamente (ottengo 0 ogni volta)

Mi manca chiaramente qualcosa, quindi qualcuno può spiegare (in dettaglio) perché la macro non funziona correttamente, o suggerire un altro approccio?

EDIT: aggiungo qui la parte argomenti stoccaggio (i dettagli rilevanti, ho analizzano un va_list), diventa il loro tipo sulla base di una di formato:

while (*format) 
{ 
    switch(*format) 
    { 
     case 's': 
      saved_arguments[i] = strdup(arg); 
      break; 
     case 'l': 
      saved_arguments[i] = malloc(sizeof(long long)); 
      *((long long *) saved_arguments[i]) = arg; 
      break; 
    } 
    i++; 
    format++; 
} 
+1

Come si 'function' dichiarato? Se dipende dagli argomenti, come decidi quale funzione chiamare? – jxh

+1

Sembra che libffi sia * vicino * a quello che vuoi, almeno per la spedizione dinamica. Non aiuta esattamente con la parte di ritardo, ma è probabile che sia specifico per la piattaforma che stai prendendo di mira. Forniscici alcune informazioni sulla tua piattaforma (Linux, Windows, Mac OSX, ecc.) E possiamo aiutarti di più. –

+0

@jxh, 'function' è come printf, può assumere argomenti di diverso tipo. Tutto ciò di cui ho bisogno è di passare alla funzione argomenti correttamente memorizzati (memorizzati in precedenza). – kaspersky

risposta

-1

Perché non si utilizza g_timeout_add_seconds()

Imposta una funzione da chiamare a intervalli regolari con la priorità predefinita, G_PRIORITY_DEFAULT. La funzione viene richiamata ripetutamente finché non restituisce FALSE, a quel punto il timeout viene automaticamente distrutto e la funzione non viene richiamata.

+1

-1, non standard, funziona solo quando si utilizza GTK (che l'OP non ha dichiarato che stava usando). –

9

Il tuo avviso è causato dagli operatori ternari con più tipi nei loro sotto-espressioni, vale a dire: -

cond ? expression of type 1 : expression of type 2 

le due espressioni hanno bisogno di valutare allo stesso tipo, che in realtà non vi aiuterà molto .

Per risolvere il tuo problema, posso pensare a due soluzioni, entrambe leggermente negative.

  1. Usa varargs/Funzione Variadica

    Definire la funzione utilizzando il '...' parametri e memorizzare i parametri da qualche parte utilizzando i macro dati e definiscono la funzione di destinazione come accettare un va_list. Si perde ogni tipo di sicurezza di tipo, il controllo del compilatore e richiedono metadati extra per le funzioni, oltre a riscrivere la funzione di destinazione per utilizzare una va_list.

  2. goccia di assemblatore e incidere la pila

Te l'avevo detto che era brutto. Data una funzione da chiamare: -

void FuncToCall (type1 arg1, type2 arg2); 

creare una funzione: -

void *FuncToCallDelayed (void (*fn) (type1 arg1, type2 arg2), type1 arg1, type2 arg2); 

che copia i parametri nello stack ad un blocco allocato dinamicamente di memoria che viene restituita. Poi, quando si desidera chiamare la funzione: -

void CallDelayedFunction (void *params); 

con il puntatore la chiamata a FuncToCallDelayed restituito. Ciò quindi spinge i parametri nello stack e chiama la funzione. I parametri e il puntatore della funzione si trovano nel parametro params.

Questo metodo consente di collegarsi a un tipo di processore specifico ma almeno mantiene una qualche forma di controllo dei tipi nell'elenco dei parametri.

Aggiornamento

Ecco una versione del metodo 2, costruita per Visual Studio 2012, IA32, in esecuzione su Win7: -

#include <iostream> 
using namespace std; 

__declspec (naked) void *CreateDelayedFunction() 
{ 
    __asm 
    { 
     mov esi,ebp 
     mov eax,[esi] 
     sub eax,esi 
     add eax,4 
     push eax 
     call malloc 
     pop ecx 
     or eax,eax 
     jz error 
     mov edi,eax 
     sub ecx,4 
     mov [edi],ecx 
     add edi,4 
     add esi,8 
     rep movsb 
     error: 
     ret 
    } 
} 

void CallDelayedFunction (void *params) 
{ 
    __asm 
    { 
     mov esi,params 
     lodsd 
     sub esp,eax 
     mov edi,esp 
     shr eax,2 
     mov ecx,eax 
     lodsd 
     rep movsd 
     call eax 
     mov esi,params 
     lodsd 
     add esp,eax 
    } 
} 

void __cdecl TestFunction1 (int a, long long b, char *c) 
{ 
    cout << "Test Function1: a = " << a << ", b = " << b << ", c = '" << c << "'" << endl; 
} 

void __cdecl TestFunction2 (char *a, float b) 
{ 
    cout << "Test Function2: a = '" << a << "', b = " << b << endl; 
} 

#pragma optimize ("", off) 

void *__cdecl TestFunction1Delayed (void (*fn) (int, long long, char *), int a, long long b, char *c) 
{ 
    return CreateDelayedFunction(); 
} 

void *__cdecl TestFunction2Delayed (void (*fn) (char *, float), char *a, float b) 
{ 
    return CreateDelayedFunction(); 
} 

#pragma optimize ("", on) 

int main() 
{ 
    cout << "Calling delayed function1" << endl; 
    void *params1 = TestFunction1Delayed (TestFunction1, 1, 2, "Hello"); 
    cout << "Calling delayed function2" << endl; 
    void *params2 = TestFunction2Delayed (TestFunction2, "World", 3.14192654f); 
    cout << "Delaying a bit..." << endl; 
    cout << "Doing Function2..." << endl; 
    CallDelayedFunction (params2); 
    cout << "Doing Function1..." << endl; 
    CallDelayedFunction (params1); 
    cout << "Done" << endl; 
} 

** Aggiornamento ** Un altro

C'è una terza opzione, come ho menzionato nei commenti, e cioè utilizzare un sistema di messaggistica. Invece di chiamare una funzione, creare un oggetto messaggio del modulo: -

struct MessageObject 
{ 
    int message_id; 
    int message_size; 
}; 

struct Function1Message 
{ 
    MessageObject base; 
    // additional data 
}; 

e quindi avere una ricerca tra i message_id e funzioni reali, con le funzioni e la ricerca definite come: -

void Function1 (Function1Object *message) 
{ 
} 

struct Lookup 
{ 
    int message_id; 
    void (*fn) (void *data); 
}; 

Lookup lookups [] = { {Message1ID, (void (*) (void *data)) Function1}, etc }; 
+0

Attualmente sto memorizzando le variabili da una va_list. Sarebbe così bello se potessi copiare un va_list per un uso successivo :(. Ho considerato la seconda variante, ma comunque, ho pensato che dovrebbe funzionare con la macro – kaspersky

+0

@ gg.kaspersky Ma, * puoi * copiare una va_list. .. [va_copy] (http://en.cppreference.com/w/cpp/utility/variadic/va_copy). –

+0

@ RichardJ.RossIII, la copia funziona solo nell'ambito della funzione corrente, in quanto è solitamente associata al stack. Il mio function wrapper (che fa i risparmi), uscirà, e il va_list copiato verrà interrotto. A un certo punto più avanti, chiamerò 'function', ma nessun va_list sarà disponibile. – kaspersky

3

Il tuo tentativo fallisce perché gli operandi risultato vero e falso dell'operatore ?: devono essere compatibili.

Il mio suggerimento originale di creare una funzione call wrapper macro che espande gli argomenti con ogni combinazione possibile non è in realtà una soluzione praticabile per te dato che in realtà vuoi supportare più di due tipi e due argomenti.

Mi è venuto in mente che è possibile utilizzare swapcontext() e setcontext() per rinviare la chiamata. Fondamentalmente, invece di mettere da parte gli argomenti in una struttura dati e tornare dalla funzione di stampa per una chiamata futura che decomprime gli argomenti nascosti, si usa swapcontext() per passare alla funzione che si desidera riprendere fino al momento in cui la stampa può riprendere . Se hai solo bisogno di capovolgere avanti e indietro, hai solo bisogno di due contesti.

struct execution_state { 
    /*...*/ 
    ucontext_t main_ctx_; 
    ucontext_t alt_ctx_; 
    char alt_stack_[32*1024]; 
} es; 

La funzione di stampa potrebbe essere simile a questa:

void deferred_print (const char *fmt, ...) { 
    va_list ap; 
    while (need_to_defer()) { 
     /*...*/ 
     swapcontext(&es.main_ctx_, &es.alt_ctx_); 
    } 
    va_start(ap, fmt); 
    vprintf(fmt, ap); 
    va_end(ap); 
} 

Dove alt_ctx_ è inizializzato a una funzione appuntamento che prende il sopravvento l'esecuzione fino a quando la stampa può riprendere.Quando la stampa può riprendere, il contesto di stampa viene ripristinata con:

setcontext(&es.main_ctx_); 

ho codificato un esempio giocattolo, è possibile vedere in azione here.

+0

Grazie per la risposta, passerei volentieri l'argomento alla funzione, ma la funzione esegue qualche elaborazione e alla fine chiama vprintf. Inoltre, ho 5+ argomenti, quindi la macro FUNCTION non è una soluzione ... – kaspersky

+0

@ gg.kaspersky: Sono curioso di sapere cosa pensi del mio nuovo suggerimento. – jxh

+0

ti farò sapere non appena l'avrò finito. – kaspersky

0

Suggerisco il seguente modo per risolvere questo problema. Prima di tutto cerchiamo di sbarazzarsi di tipo di argomento verifica durante la chiamata di funzione:

#include <stdio.h> 

int function(int a, long long b) 
{ 
    printf("a = %d\n", a); 
    printf("b = %lld\n", b); 
    return 0; 
} 

int function2(double c, char *d) 
{ 
    printf("c = %f\n", c); 
    printf("d = %s\n", d); 
    return 0; 
} 

typedef int (*ftype)(); // The type of function which can take undefined number of arguments and return 'int' 

int main(int argc, char *argv[]) 
{ 
    ftype f1, f2; 

    f1 = (ftype)function; 
    f2 = (ftype)function2; 
    f1(10, 100500); 
    f2(2.3, "some string"); 
    return 0; 
} 

Poi possiamo attuare il "dispatcher" che eseguirà la chiamata di funzione correttamente:

int dispatch(void **args, int call_type, ftype function) 
{ 
    int ret_val; 
    switch(call_type) 
    { 
     0: ret_val = function(*(int*)args[0], *(double*)args[1]); 
      break; 
     1: ret_val = function(*(long long*)args[0], *(int*)args[1]); 
      break; 

     etc etc... 
    } 
} 

Lo svantaggio principale di questo approccio è la necessità di implementare molti casi per il dispatcher. E ovviamente funzionerà solo se tutti questi casi sono definiti a priori.

Infine, dovrei dire che è un'implementazione estremamente pericolosa. Può facilmente diventare una fonte di errori strani e pericolosi.

+0

Se si desidera che 'ftype' assuma un numero qualsiasi di argomenti, il metodo standard consiste nel lasciare' '' invece di non specificare alcun argomento. – Shahbaz

+0

È necessario utilizzare 'va_list' all'interno di ogni funzione chiamata per ottenere argomenti. Qui presumo che non possiamo cambiare il codice di quelle funzioni. Le parentesi graffe vuote funzioneranno in questo caso, '...' no. – VirtualVDX

1

si può usare qualcosa di simile:

#include <stdio.h> 
#include <string.h> 
#include <stdarg.h> 

enum e_type { 
    CHAR = 0, 
    INT, 
    LONG, 
    CHARPTR 
}; 

struct type { 
    enum e_type type; 
    union { 
     char c; 
     int i; 
     long l; 
     char *s; 
    } value; 
}; 

#define convert(t) (t.type == CHAR ? t.value.c : (t.type == INT ? t.value.i : (t.type == LONG ? t.value.l : t.value.s))) 

void test_fun(int argc, ...) 
{ 
    va_list args; 
    int i = 0, curr = 0; 
    struct type t; 

    va_start(args, argc); 

    while (i++ < argc) 
    { 
     t = va_arg(args, struct type); 

     switch (t.type) { 
      case CHAR: 
       printf("%c, ", convert(t)); 
       break; 

      case INT: 
       printf("%d, ", convert(t)); 
       break; 

      case LONG: 
       printf("%ld, ", convert(t)); 
       break; 

      case CHARPTR: 
       printf("%s, ", convert(t)); 
       break; 
     } 
    } 
    printf("\n"); 
    va_end(args); 
} 

void test_fun2(char c, long l, char *s) 
{ 
    printf("%c, %ld, %s\n", c, l, s); 
} 

int main() 
{ 
    struct type t1, t2, t3; 
    t1.type = CHAR; 
    t1.value.c = 0x61; 

    t2.type = LONG; 
    t2.value.l = 0xFFFF; 

    t3.type = CHARPTR; 
    t3.value.s = "hello"; 

    test_fun(3, t1, t2, t3); 
    test_fun2(convert(t1), convert(t2), convert(t3)); 

    return 0; 
} 

Il segreto è quello di utilizzare l'unione.

Questo codice fornirà molti avvisi poiché il compilatore non è in grado di calcolare correttamente il tipo del valore restituito dalla macro.

Il codice qui sopra correttamente stampare:

a, 65535, hello, 
a, 65535, hello 

(Testato con gcc e clangore su linux)

2

Utilizzare il foreign function call library, si prende cura di tutti i dettagli grungy specifiche della piattaforma per voi. Ad esempio, ecco come si potrebbe rinviare una chiamata di funzione a una funzione di prendere un int, void*, e long long parametri e tornare int:

#include <avcall.h> 

int my_function(int a, void *b, long long c) 
{ 
    // Do stuff 
} 

... 

av_list alist; // Stores the argument list 
int return_value; // Receives the return value 

// Initialize the argument list 
av_start_int(alist, &my_function, &return_value); 

// Push the arguments onto the list 
av_int(alist, 42);     // First arg 'a' 
av_ptr(alist, &something);   // Second arg 'b' 
av_longlong(alist, 5000000000LL); // Third arg 'c' 

// We're done -- stash away alist and return_value until we want to call the 
// function later. If the calling function needs to return, then you'll need 
// to allocate those variables on the heap instead of on the stack 

... 

// Now we're ready to call the stashed function: 
av_call(alist); 
// Return value from the function is now stored in our return_value variable 
Problemi correlati