Newsgroup: it.comp.lang.c++
Titolo: Passaggio parametri dinamico

Salve a tutti,
ho la necessita' di chiamare una funzione passandogli i parametri in modo dinamico , senza sapere a livello di compilazione quanti e quali parametri dovro' passare .
Porto un esempio concreto : 
devo chiamare la funzione "MessageBox" dato il suo puntatore ( ritornato dopo un loadlibrary ecc...) e specificato che questa ha 4 parametri da 4 byte ciascuno ed i relativi valori .
Chiaramente queste informazioni vengono passate run-time e quello che mi serve e' un modo dinamico per chiamare questa funzione passandogli i suoi parametri.
Formalmente in C questo si ottiene utilizzando le funzionalita va_arg ecc... che pero' richiedono una definizione delle funzioni del tipo 
void fu(...) 
quindi richiedono che le funzioni su cui si va ad agire non siano compilate.
L' unica alternativa che conosco al momento per risolvere il problema e' implementare il tutto in asm seguendo le specifiche del passaggio parametri mi sembra strano pero' che non sia mai stato implementato niente per risolvere il problema .
Esiste qualche alternatica in C/C++ evitandomi l' asm ?
Grazie,
Ciao 

 quindi richiedono che le funzioni su cui si va ad agire non siano compilate.
Funzioni non compilate? Che cosa intendi?
Comunque mi pare che ne' in C ne' in C++ si possono chiamare funzioni i 
cui tipi dei parametri sono totalmente decisi a run-time, a meno di 
usare i varargs, ossia i tre punti, ma in tal caso la funzione deve 
essere definita in tal modo, o di usare l'assembly.
Pero' se puoi determinare staticamente tutti i possibili insiemi di 
parametri di cui puoi aver bisogno, puoi definire un typedef per ognuno 
di essi, e fare il cast a quello che ti serve.
Per esempio:
typedef void F();
typedef void Fd(double);
typedef void Fi(int);
typedef void Fii(int,int);
typedef void Fiii(int,int,int);
typedef void Fiiii(int,int,int,int);
[...]
// Inizializzazione del puntatore a funzione.
F* MessageBox = loadlibrary();
// Chiamata di funzione con il tipo appropriato.
((Fiiii*)MessageBox)(i1,i2,i3,i4);
-- 
Carlo Milanesi
http://carlomilanesi.wordpress.com/

Grazie della risposta Carlo,
dall' esempio di soluzione hai capito perfettamente qual e' il problema.
Il problema della soluzione che mi hai proposto e' che se si cominciano a dichiarare tutte le possibilita' si ha una crescita esponenziale delle dichiarazioni ed una implementazione in assembler diventa un' alternativa valida.
Forse con qualche trucchetto , definendo su define il tipo del parametro ( alla fine sono ad 1 ,2 ,4, 8 bytes ) e mettendo piu' include allo stesso file  contenente i typedef con valori di define diversi ...
Lavorandoci un po' penso che possa portare ad una buona soluzione , rimane il fatto strano che il problema non sia mai stato affrontato dai compilatori C++.
Ciao,
Denis.

 in assembler diventa un' alternativa valida.
Puoi usare un wrapper ed usare la "Template argument deduction" per 
evitare di scrivere tutte le possibilità a mano.
Una cosa del genere:
<code>
struct DynamicFunction
{
     typedef void (*F)(void);
     DynamicFunction(F function): function_(function) {}
     void operator()()
     {
     	function_();
     }
     template <class T0> void operator()(T0 arg0)
     {
         typedef void (*Func)(T0);
         reinterpret_cast<Func>(function_)(arg0);
     }
     template <class T0, class T1> void operator()(T0 arg0, T1 arg1)
     {
         typedef void (*Func)(T0, T1);
         reinterpret_cast<Func>(function_)(arg0, arg1);
     }
     ....
private:
     F function_;
};
int main()
{
     DynamicFunction df(loadlibrary(...));
     df(1, 2, 3.4, "pippo");
}
</code>
Il codice sopra non è testato, ci sono aggiustamenti da fare, anche 
perché non penso sia la loadlibrary a restituire il puntatore a 
funzione, ma direi che è un buon esempio dal quale partire.
Ciao.
-- 
http://www.econet-project.eu

 Denis.

decisamente interessante , si puo' risparmiare parecchia scrittura di codice.

 decisamente interessante , si puo' risparmiare parecchia scrittura di codice.
volendo si può scrivere anche un po' meno codice in questo modo:
//--------------------
#include <stdarg.h>
int fz1(int v1, char v2, char* v3)
{
   printf("[fz1] Valori: [1] = %d, [2] = %d, [3] = %s\r\n", v1, v2, v3);
   return 0;
}
int fz2(int v1, char* v2, char* v3, int v4)
{
   printf("[fz2] Valori: [1] = %d, [2] = %s, [3] = %s, [4] = %d\r\n", 
v1, v2, v3, v4);
   return 0;
}
typedef int (*fz_)(unsigned long v1, ...);
#define MAX_PARAM 10
int resolve(fz_ fz, int nParam, ...)
{
   va_list ap;
   unsigned long par[MAX_PARAM];
   int i;
   va_start( ap, nParam );
   for(i = 0; i < MAX_PARAM && i < nParam; ++i)
     par[i] = va_arg(ap, unsigned long);
   switch(i) {
     case 1:
       return fz(par[0]);
     case 2:
       return fz(par[0], par[1]);
     case 3:
       return fz(par[0], par[1], par[2]);
     case 4:
       return fz(par[0], par[1], par[2], par[3]);
     case 5:
       return fz(par[0], par[1], par[2], par[3], par[4]);
     case 6:
       return fz(par[0], par[1], par[2], par[3], par[4], par[5]);
     case 7:
       return fz(par[0], par[1], par[2], par[3], par[4], par[5], par[6]);
     case 8:
       return fz(par[0], par[1], par[2], par[3], par[4], par[5], par[6], 
par[7]);
     case 9:
       return fz(par[0], par[1], par[2], par[3], par[4], par[5], par[6], 
par[7], par[8]);
     case 10:
       return fz(par[0], par[1], par[2], par[3], par[4], par[5], par[6], 
par[7], par[8], par[9]);
     }
   return 0;
}
int main()
{
   resolve((fz_)fz1, 3, 10, 20, "pippo");
   resolve((fz_)fz2, 4, 10, "pippo","pluto", 40);
   return 0;
}
//-------------------
Naturalmente i parametri non possono superare la dimensione 
dell'unsigned long (o aggiustala come vuoi :-) )
Ciao

Lo definisco un fatto strano perche' e' inequivocabilmente una carenza del linguaggio. Il fatto di poterlo scrivere in ASM in realta non da' una bella soluzione perche' e' suscettibile alle variazioni delle specifiche di chiamata/ passaggio parametri . Inoltre a 64 bit ( e mi servira' anche a 64 bit ) mi sembra che non si possa piu' inserire assembly inline e cosa ancor peggiore le specifiche del passaggio dei parametri mi sembra siano cambiate e la situazione si complica parecchio . Una soluzione a livello di C++ risolverebbe tutti questi problemi di indipendenza del codice 64/32 bit ed indipendenza dalle specifiche di chiamata ( _stdcall _fastcall _cdecl , ... ) .
Infine non penso di essere il primo a dover affrontare questo problema , soprattutto a basso livello chi sviluppa compilatori linker ecc dovrebbe aver affrontato il problema ed una soluzione a livello di compilatore sarebbe stata a portata di mano.
Ciao,
Denis.


Se vuoi scrivere ancora meno, con un piccolo overhead dovuto 
all'immissione di tutti i parametri sullo stack (sempre se la chiamata è 
a convenzione C) ma eliminando lo switch:
//----------------------
int resolve(fz_ fz, int nParam, ...)
{
   va_list ap;
   unsigned long par[MAX_PARAM];
   int i;
   va_start( ap, nParam );
   for(i = 0; i < MAX_PARAM && i < nParam; ++i)
     par[i] = va_arg(ap, unsigned long);
   va_end(ap);


In effetti fissato un limite come numero di parametri si puo' implementare qualcosa di fattibile ed interessante , alla fine optero' per una soluzione simile , un po' di define typedef e template .
Grazie,
Denis.

e ma questo potrebbe essere molto pericolo ... meglio evitarlo

 e ma questo potrebbe essere molto pericolo ... meglio evitarlo
Non ci sono problemi se la chiamata è __cdecl.
Tra l'altro mi sa che, se il tuo scopo è usarlo con le API di windows 
con chiamata __stdcall, non funziona perché l'ellisse vale solo per __cdecl.

 rimane il fatto strano che il problema non sia mai stato affrontato dai compilatori C++.
A me in tanti anni tale problema non e' mai capitato e prima d'ora non 
ne ho mai sentito parlare.
Forse la soluzione non c'e' perche' il tuo problema e' piu' unico che raro.
D'altra parte non vedo come si possa scrivere del codice che chiami una 
funzione senza sapere quanti parametri passarle.


 ho la necessita' di chiamare una funzione passandogli i parametri in modo dinamico , senza sapere a livello di compilazione quanti e quali parametri dovro' passare .
E definire qualcosa del tipo
struct pars {
   int    nPars
   size_t size;
   char params[];
};
int fnc(pars* pParsP);
a secondo del numero di parametri che ti servono, ti
dimensioni la struttura allocandola dimamicamente con
una dimensione sufficiente a contenere sia la descrizione
del tipo di parametri passati, sia i parametri stessi.
Alla fine il problema piu' grosso e' decidere come codificare
le coppie (tipo variabile, valore) ed il parser per tirar
fuori i parametri dalla struttura dinamica... Visto che
vuoi evitare questa fatica al compilatore, devi farlo tu. :-)
HTH
C:\>iao Angelo

 ho la necessita' di chiamare una funzione passandogli i parametri
 > in modo dinamico , senza sapere a livello di compilazione
 > quanti e quali parametri dovro' passare .
Potresti usare il seguente codice, che ovviamente non e' portabile, ma 
d'altra parte nemmeno l'assembly lo e'.
#include <iostream>
using namespace std;
void f(short s, int i, double d)
{
   cout << s << "; " << i << "; " << d << endl;
}
int main()
{
   char args[20];
   *(short*)args = 123;
   *(int*)(args + 4) = 987654321;
   *(double*)(args + 8) = 3.14;
   // Sintassi di chiamata numero 1.
   ((void (*)())f)();
   // Sintassi di chiamata numero 2.
   typedef void (*F)();
   reinterpret_cast<F>(f)();
}
La sintassi di chiamata numero 2 mi sembra piu' leggibile.
Nota che all'inizio dello stack metto uno short, che su mio computer e' 
lungo solo 2 byte, e poi metto un int saltando due byte.
Questo perche' la funzione f aggiunge un padding per caricare l'int da 
un indirizzo che e' multiplo di 4.


 reinterpret_cast<F>(f)();
ossia, stai aggirando la convenzione di chiamata normale 
dalla signature della funzione f(short, int, double) facendo 
credere al compilatore che non ha parametri, in modo che 
esso in automatico non copi nulla sullo stack.
Poi esegui la chiamata, e una volta dentro f() essa si 
aspetta lo stack pronto, che avevi preparato a mano.
Questo se ho capito qualcosa.
Dubbi : una chiamata del genere non deve prevedere altre 
dichiarazioni di dati tra args[] e la chiamata a f(), vero ?
E' prescritto che l'ultima operazione del compilatore fatta 
prima della chiamata attuale sia il pushing dei parametri ? 
E in ordine di dichiarazione o inverso ?
Non vengono salvati sullo stack altri registri ?
grazie dei chiarimenti
ciao
Soviet


 Non vengono salvati sullo stack altri registri ?
Non e' previsto un bel niente. Quello che ho scritto lo ho provato con 
un compilatore e ho visto che funzionava, ma tale comportamento non e' 
definito dal linguaggio (nessuna versione) e potrebbe non funzionare 
piu' (anzi, sicuramente non funziona piu' in certi casi) cambiando 
compilatore, o architettura di processore, o anche opzioni di compilazione.
L'unico aspetto "teorico" e' il seguente.
Avrai visto che ci sono varie convenzioni di chiamata di funzione.
Io conosco almeno le seguenti:
* Tipica del C e del C++: I parametri vengono messi sullo stack 
dall'ultimo al primo, e solitamente, per maggiore efficienza, vengono 
anche valutati dall'ultimo al primo. Ha il vantaggio che consente un 
numero variabile di parametri, come la funzione "printf". Ha lo 
svantaggio che il chiamante deve ripristinare lo stack pointer al 
termine della funzione; se una funzione è chiamata mille volte, il 
codice contiene mille istruzioni di ripristino dello stack pointer.
* Tipica del Pascal: I parametri vengono messi sullo stack dal primo 
all'ultimo. Ha lo svantaggio che non consente un numero variabile di 
parametri; pertanto, per esempio, la funzione Pascal Write, che ammette 
un numero variabile di parametri, non e' una funzione di libreria ma una 
funzione incorporata del linguaggio. Ha il vantaggio che la funzione 
chiamata deve ripristinare lo stack pointer al termine della funzione; 
se una funzione è chiamata mille volte, il codice contiene una sola 
istruzione di ripristino dello stack pointer.
* Tipica del FORTRAN 77 (e precedenti): Non usa lo stack, in quanto gli 
argomenti sono messi in un'area statica. Ha lo svantaggio che non 
consente chiamate ricorsive.
* Parametri nei registri: Non usa lo stack, ma i registri del 
processore. Ha il vantaggio che è molto veloce, ma gli svantaggi che i 
registri potrebbero non bastare, non consente chiamate ricorsive. 
Suppongo che per gestire tali casi viene comunque generata anche una 
versione della funzione che usa una convezione di chiamata che usa lo stack.


 parametri dovro' passare .
Giusto come nota generale:
quando occorrono funzionalità come queste credo sia meglio non spingere 
ai limiti il linguaggio che si sta utilizzando.
Se ti occorre usare un alto livello di dinamismo, considera anche 
l'opzione di utilizzare un linguaggio dinamico come Python o Common Lisp.
I metodi di integrazione sono molteplici:
1) Programma principale scritto in C in cui embeddi il runtime del
  linguaggio
2) Programma principale scritto nel linguaggio di alto livello, con parti
  ottimizzate scritte in C
Io ricordo anni, fa quando utilizzavo C++, di aver perso un sacco di 
tempo a pensare come superare i limiti del linguaggio per fare quello che 
volevo, alla fine ho imparato Python.
Ciao   Manlio

 ripristinato ad ogni ritorno ma solo una volta ?
Questo vantaggio non e' altro che lo svantaggio citato prima per la 
convenzione di chiamata tipica del C/C++.
Non ho scritto che "lo stack non va ripristinato ad ogni ritorno ma solo 
una volta", ma che "il codice contiene una sola istruzione di ripristino 
dello stack pointer", cioe' il codice di ripristino dello stack, essendo 
contenuto nella funzione stessa, e' contenuto una sola volta nel codice. 
Se invece tale codice fosse contenuto nella routine chiamante, dovrebbe 
essere contenuto una volta per ogni chiamata.
Per saperne di più:
http://en.wikipedia.org/wiki/X86_calling_conventions
(che tratta solo l'architettura x86)
o 
http://en.wikipedia.org/wiki/Calling_convention
 (che tratta varie 
architetture).


Si, è chiaro che in qualche modo l' informazione del numero di parametri deve essere passata

Grzie dell' esempio e del test che ha fatto , trucco interessante ma incompatibile con differenti convenzioni di chiamata soprattutto in 64 bit cosa accade ?
Qui il link della nuova convenzione di chiamata in 64 bit
http://msdn.microsoft.com/en-us/library/ms235286.aspx

Non so se il Python possa essere un ' alternativa valida .
Il tutto nasce dall' esigenza di effettuare porting in 64 bit di grandi quantità di codice .
Attualmente non è possibile comunicare ( chiamare funzioni ) fra processi 32 bit e 64 bit ( su windows ) e viene consigliata l' implementazione di un rpc (!) .
La mia idea e' quindi quella di implementare un servera 32 bit ed uno a 64 bit che forniscano questa funzionalità di chiamare funzioni a 32 o 64 bit senza convertire le dll e senza creare interfacce per ogni dll .
Qualche suggerimento a riguardo?
Ciao,
Denis.

 [...]
Ciao  Manlio


ciao
Soviet

 Ciao  Manlio

Già con le dll hanno fatto un bel buco nell' acqua ma mai quanto quello degli activex addirittura ammesso direttamente!
Quale sarà il prossimo?

Si intendo proprio quello ma la cosa che non mi và giu' non è tanto quella della comunicazione fra processi ma quella di non poter chiamare nello stesso processo a 64 codice a 32 e che cavolo ci voleva tanto mettere un' api che consentisse questa compatibilità ? Si faceva un ventennio fa in dos fra modalità reale / modalità protetta !
Con un rpc e tutto il giro che si deve implementare mi sembra di andare a caccia di moscerini con un bazzuca

 Quale sarà il prossimo?
sotto certi punti di vista (solo questi citati) direi che 
già NET sia un buco dello stesso genere.
Ho un certo numero di PICCOLI software freeware (roba di 
pochi mega) che, ciascuno, si appoggia ad una versione 
differente del framework .NET (che occupa talvolta decine 
talaltra centinaia di MB). Pur avendo tolto la 1.0, devo 
tenere installate nel sistema dalla 2.0 fino alla 4.0, 
passando per i numeri non interi.
Complessivamente (ho timore di guardarci, e cmq sono 
convinto che anche cercando troverei solo una parte di files 
e non quelli annidati troppo segretamente nel sistema) temo 
di avere almeno un giga, forse di più, di framework.
Questo per supportare programmi per magari complessivi 100 MB.
Ora se permettessero di linkare STATICAMENTE solo quel cazzo 
che è strettamente essenziale del framework stesso nel 
compilato, ed esso fosse standalone, risparmierei una 
fucilata di posto, punto primo, e installare / disinstallare 
diventerebbe pure quello uno scherzo.
Invece così, quando disinstallo, non mi fido neanche di 
togliere il framework, non sia mai che un programma ancora 
presente referenzi un paio di funzioni sulle migliaia 
disponibili.
Boh ... è la filosofia che mi sfugge. Il framework in sé 
stesso è eccellente. Ma non va la filosofia. E' utopia il 
code-sharing. Ogni programma pensa a sé. Se fanno un upgrade 
di qualche pezzo di interfaccia, ovviamente fanno l'upgrade 
del F.W. sottostante.
ciao
Soviet

 del F.W. sottostante.
Contestualizziamo:
stiamo parlando di software per PC?
dico proprio quei PC che usano dischi fissi che si misurano
in centinaia di GB?
E tu ti stai a preoccupare di qualche centinaio di MB che
peraltro viene condiviso tra più programmi?
Boh ... è la filosofia che mi sfugge :)
Posso citare Knuth?
"L'ottimizzazione preventiva è l'origine di ogni male"
p.s.
Guarda che un porgramma in .net che usa solo la libreria
occupa tipicamente qualche MB al massimo, spesso
molto meno, e non serve proprio alcuna installazione.
Lo lanci e parte.
-- 
Gerolamo 

 Lo lanci e parte.
Si, se hai già tutto installato parte. Se no non gira proprio.
Ciao
Soviet

 Si, se hai già tutto installato parte. Se no non gira proprio.
E certo.
Se lanci un programma in Java e non hai installato java che succede?
Se lanci un programma in Python e non hai installato python che succede?
Se lanci un programma in C++ su linux che richiede libssl
e non ce l'hai installato che succede?
-- 
Gerolamo 

 Lo lanci e parte.
Sarà, ma quanti sono i programmi in .NET che usano solo la libreria? Io
finora non ne ho visti. Il punto è che uno non usa .NET per scriversi
tutto a mano un'altra volta... se usi .NET, lo fai proprio per il
framework che ti mette a disposizione. Quindi non prendiamoci in giro,
please.

 in giro, please.
Che stai dicendo? Mi prendi in giro o sei un troll?
-- 
il culo di Gerolamo 

Volevo segnalare degli sviluppi sull' argomento , intanto utilizzando i variadic template si risolverebbe il problema in modo elegante ma il visual c++ ancora non li supporta a quanto sembra , detto questo il meta programming delle boost potrebbe risolvere definitivamente il problema , indago piu' approfonditamente poi riferiro' nel thread eventuali novita'
Ciao,
Denis.

 Denis.