' Rapid-Q by William Yu (c)1999-2000 . ' ================================================================================ ' Upload_il_tuo_script_su_Rapidq.it ' Capitolo_9__SUBI__FUNCTIONI__e_DLLs ****** 9. SUBI, FUNCTIONI e DLL ****** In questo capitolo parleremo della creazione di SUB/FUNCTION con un numero variabile di parametri. Se conoscete un po' di C, conoscerete probabilmente il comando più famoso, printf. Bene, in Rapid-Q è possibile implementare il vostro printf personalizzato! 9.1 SUB/FUNCTION con un numero variabile di parametri Le SUB e FUNCTION tradizionali hanno un numero di parametri prefissato, ma le SUBI e FUNCTIONI possono accettare un numero infinito (255) di parametri variabili. Vi starete probabilmente chiedendo come è possibile recuperare questi parametri. In realtà sono memorizzati in uno stack interno. Quindi, è necessario ricercare il parametro corretto nello stack. Ecco i comandi utilizzati: ParamStr$() - Matrice di parametri stringa ParamVal() - Matrice di parametri numerici ParamValCount - Numero di parametri numerici passati ParamStrCount - Numero di parametri stringa passati Per fare un semplice esempio, verifichiamo come funziona: SUBI TestSUBI (...) PRINT "Parametri stringa: "; ParamStrCount FOR I = 1 TO ParamStrCount PRINT I;" "; ParamStr$(I) NEXT I PRINT "Parametri numerici: "; ParamValCount FOR I = 1 TO ParamValCount PRINT I;" "; ParamVal(I) NEXT I END SUBI TestSUBI "Hello", 1234, "Hmmm", "Yeah...", 9876, 1*2*3*4, UCASE$("Ultimo") Noterete probabilmente che le matrici ParamStr$() e ParamVal() non partono da zero (cioè il loro primo valore è 1). Questo può sembrare strano, dal momento che in Rapid- Q tutte le numerazioni cominciano con zero. A parte questo, è altrettanto strano l'utilizzo di (...) in sostituzione dei nomi dei parametri. Non è rilevante cosa inserite nelle parentesi, purché sia un dato valido (una parola). Provate a creare una FUNCTIONI; non è diverso dal creare una funzione standard, a parte il fatto che si sostituiscono i nomi dei parametri con (...). 9.2 Approfondimento sulle FUNCTIONI Per maggiore chiarezza, facciamo alcuni esempi. In questo caso troveremo il numero massimo fra i parametri forniti. FUNCTIONI FindMax (...) AS DOUBLE DIM Largest AS DOUBLE DIM I AS BYTE Largest = -999999999999 FOR I = 1 TO ParamValCount IF ParamVal(I) > Largest THEN Largest = ParamVal(I) END IF NEXT FindMax = Largest ' oppure Result = Largest END FUNCTIONI La funzione analizza l'intera lista di parametri numerici e verifica quale si sia il maggiore. I parametri stringa vengono ignorati. Proviamolo: PRINT "Il numero maggiore è: "; FindMax(523, 12.4, 602, 45, -1200) PRINT "Il numero maggiore è: "; FindMax(523, 12.4, FindMax(602, 45, -1200)) Potete tranquillamente incassare le vostre FUNCTIONI. E estremamente semplice. Dovete solo ricordare le parole chiave che vi serviranno per accedere allo stack interno, ed è fatta! 9.3 Introduzione alle DLL Che cos'è una DLL? È una Dynamic Link Library (libreria con un collegamento dinamico), che contiene funzioni esportate che possono essere utilizzate da qualunque linguaggio di programmazione che le supporta. È possibile scrivere delle DLL in Delphi o C++ ed utilizzare le funzioni in Rapid-Q. Le DLL vengono caricate nello stesso spazio dedicato al vostro programma, così avete accesso alle loro funzioni esportate. Le DLL in Rapid- Q possono essere collegate dinamicamente solo in esecuzione (run-time). 9.4 Come chiamare una DLL C'è un esempio chiamato DLL.BAS incluso nella distribuzione di Rapid-Q. Inoltre c'è una DLL chiamata PASCAL.DLL. Suggerisco di darvi un'occhiata. La chiamata di DLL in Rapid- Q è quasi identica a quella di PowerBASIC o VisualBASIC. Può sembrare orribile, ma ho preferito adeguarmi allo standard... Va notato che le maiuscole sono rilevanti! È così, se sbagliate una maiuscola nella vostra funzione, addio! Non è per confondervi ancora di più, ma in realtà ci sono due funzioni. Una è quella che richiamerete nel vostro programma, l'altra è quella contenuta nella DLL. Queste due funzioni/subroutine devono avere gli stessi parametri! DECLARE SUB Test LIB "TEST.DLL" ALIAS "MyFunc" (S AS STRING) Vi starete chiedendo in quale caso le maiuscole siano rilevanti, Test o MyFunc. Ebbene, Test è quella che chiamerete nel vostro programma, perciò sapete che non può essere; MyFunc è quella contenuta nella DLL, perciò assicuratevi di averla digitata correttamente! La SUB precedente punterà all'indirizzo di memoria della vostra DLL (quando caricata), ed eseguirà il codice in essa contenuto. Come potrete notare, c'è solo un parametro per la nostra routine MyFunc. Avete visto solo due nuovi comandi, LIB e ALIAS. Dopo LIB dovrebbe esserci una stringa corrispondente al percorso della vostra DLL. Se la vostra DLL risiede nel vostro parametro $PATH non è necessario specificare il percorso (per esempio se TEST.DLLsi trova in C:\WINDOWS\SYSTEM). Il secondo comando ALIAS serve a specificare la funzione/subroutine che si può eseguire in quella DLL. Assicuratevi di aver digitato correttamente le maiuscole, è molto importante. Se non siete sicuri, potete utilizzare un visualizzatore di DLL (come Quick View, ne parleremo più avanti). Che tipi di dati possiamo validamente passare? SHORT/WORD, INTEGER/LONG/DWORD, DOUBLE, STRING, QRECT, QNOTIFYICONDATA, e qualunque UDT voi creiate. A differenza di alcune implementazioni che obbligano a passare le stringhe come puntatori, Rapid-Q lo farà per voi, non dovrete preoccuparvene. Quando incontrerete il parametro LPZSTR, sapete che significa Long Pointer Zero-Terminated String (puntatore stringa con terminazione zero di tipo Long). Potete tranquillamente passare qualsiasi variabile stringa. Avrete notato che alcuni linguaggi Basic (come VB) ignorano ALIAS quando la funzione corrisponde alla funzione dichiarata. Per esempio: DECLARE SUB MyFunc LIB "TEST.DLL" Se la vostra funzione corrisponde a quella della DLL, potete evitare di usare ALIAS. Tuttavia, questo non accade in Rapid-Q; non è possibile, quindi non provateci nemmeno. La chiamata di una funzione di una DLL o di una vostra funzione è esattamente uguale, non c'è bisogno di sapere nient'altro: MyFunc("Hello") 9.5 Utilizzo di Quick View Come accennato prima, se siete incerti sull'uso delle maiuscole della vostra funzione, è opportuno controllare utilizzando Quick View. [http://www.cajino.50megs.com/rapidq/chap9d1.gif] Quick View è compreso in Windows (non c'è bisogno di scaricarlo). Per utilizzarlo, aprite "esplora risorse" (o risorse del computer), quindi localizzate un file DLL; cliccate su di esso col tasto destro e selezionate Quick View dal menù. Cercate Esporta Tabella, dove troverete tutte le funzioni disponibili. Probabilmente esistono dei visualizzatori di DLL migliori, ma non intendo pubblicizzarli. Quick View è più che sufficiente. 9.6 Scrivere delle DLLs Al momento, non è possibile scrivere DLL in Rapid-Q. Molti linguaggi permettono di creare DLL, l'unico problema è dato dalle stringhe, soprattutto se si utilizza VisualBasic o PowerBasic. È conveniente utilizzare puntatori per le stringhe nei vostri parametri per evitare l'utilizzo di OLE per allocare le stringhe stesse. Rapid-Q non utilizza OLE per allocare stringhe, dal momento che suppongo non ci saranno molte DLL che non usano puntatori. 9.7 Utilizzo di tipi non supportati nelle chiamate di DLL QUESTA SEZIONE E' NULLA CON L'INTRODUZIONE DI STRUCT, MA PUO' ESSERE UTILIZZATA A PIACERE.. Rapid-Q usa dei meccanismi per memorizzare tipi personalizzati che rendono impossibile effettuare chiamate su DLL che richiedono parametri di tipo particolare. Tuttavia, non tutto è perduto. Esiste una scappatoia efficace, che però richiede un po' di pratica. È necessario passare alle funzioni DLL che richiedono parametri di tipo particolare un puntatore a detto tipo. In che modo? Vediamo un esempio di una funzione DLL che richiede un parametro di tipo particolare: DECLARE FUNCTION GetWindowRect LIB "USER32" ALIAS "GetWindowRect" _ (hWnd AS LONG, lpRect AS RECT) AS LONG RECT è una struttura così composta: TYPE RECT Left AS LONG Top AS LONG Right AS LONG Bottom AS LONG END TYPE Ovviamente, questo dovrebbe funzionare: DIM R AS RECT GetWindowRect(MainForm.Handle, R) SI', ORA FUNZIONA. Tuttavia, Rapid-Q memorizza i tipi personalizzati in modo non contiguo, cioè non in un blocco unico come richiesto dalla chiamata della DLL, così questo chiamata non è valida. Come fare? È necessario utilizzare un semplice trucco, con l'uso del sempre utile componente QMEMORYSTREAM: '' Notate il cambiamento in lpRect DECLARE FUNCTION GetWindowRect LIB "USER32" ALIAS "GetWindowRect" _ (hWnd AS LONG, lpRect AS LONG) AS LONG DIM R AS RECT DIM M AS QMEMORYSTREAM M.WriteUDT(R) GetWindowRect(MainForm.Handle, M.Pointer) M.Position M.ReadUDT(R) La prima cosa da fare è cambiare i parametri della funzione DLL. Abbiamo cambiato il Tipo di lpRect in LONG. Questo perché intendiamo passare un puntatore (cioè un numero). Utilizziamo QMEMORYSTREAM per memorizzare il Tipo personalizzato (usando M.WriteUDT) in un blocco contiguo per la nostra chiamata alla DLL. Dobbiamo semplicemente passare il puntatore a questo blocco di memoria e la DLL leggerà da questo indirizzo di memoria. Dopo aver chiamato la funzione dobbiamo recuperare i dati restituiti (se necessario, in questo caso GetWindowRect restituisce la struttura rect di Window). Questo è piuttosto semplice, ma cosa succede se ci sono dei Tipi all'interno di un Tipo? TYPE Struct1 A AS INTEGER END TYPE TYPE Struct2 S AS Struct1 I AS INTEGER END TYPE Se una DLL richiede un Tipo, che può contenere altri Tipi, è possibile effettuare lo stesso genere di conversione: TYPE Struct1 A AS INTEGER END TYPE TYPE Struct2 S AS LONG I AS INTEGER END TYPE DIM S1 AS Struct1 DIM S2 AS Struct2 DIM M1 AS QMEMORYSTREAM DIM M2 AS QMEMORYSTREAM M1.WriteUDT(S1) S2.S = M1.Pointer M2.WriteUDT(S2) '' Chiamata alla funzione DLL... L'ultima considerazione riguarda le stringhe all'interno dei Tipi. Non è più possibile dichiarare una stringa, dobbiamo dichiarare un puntatore, ad esempio: TYPE TStruct ST AS STRING Other AS INTEGER END TYPE Questo non sarebbe corretto in quanto ST verrebbe passato per valore (nel quale caso potrebbe utilizzare una quantità indefinita di memoria). Tuttavia, la maggior parte delle chiamate a DLL richiede che le stringhe siano passate per riferimento (cioè vogliono il puntatore alla stringa). E' necessaria una conversione, usando VARPTR: TYPE TStruct ST AS LONG Other AS INTEGER END TYPE DIM Struct AS TStruct DIM S AS STRING S = SPACE$(100) '' Allocare dello spazio per la stringa Struct.ST = VARPTR(S) '' Per recuperare la stringa utilizzare VARPTR$ S = VARPTR$(Struct.ST) Notate che questa conversione è necessaria solo per Tipi e non per normali chiamate di DLL. Per normali chiamate di DLL che hanno stringhe per parametri è sufficiente passare le stringhe e non i puntatori, dal momento che Rapid-Q tradurrà il tutto automaticamente. Potete ovviamente effettuare la conversione, ricordatevi solo di cambiare il parametro stringa in qualcosa come Long, e passate VARPTR$ invece della stringa vera e propria S$. 9.8 Tabella di conversione API Molti hanno richiesto una guida API per convertire le loro dichiarazioni WinAPI in C o VB per l'utilizzo con Rapid-Q, così ecco un riassunto con qualche breve spiegazione. Grazie a Mayuresh S. Kadu per questa guida API WIN32 in VB. ___________________________________________________________________________ |Tipo_dati_in_C_________|Pascal_________|VB______________|Rapid-Q___________| |ATOM___________________|SHORT__________|byval_as_INTEGER|SHORT_____________| |BOOL___________________|BOOLEAN________|byval_as_LONG___|LONG______________| |BYTE___________________|BYTE___________|byval_as_BYTE___|BYTE______________| |CHAR___________________|CHAR___________|byval_as_BYTE___|BYTE______________| |CHAR[20] |STRING[20] |as STRING |Non applicabile in| |_______________________|_______________|________________|dichiarazioni_API_| |COLORREF_______________|LONGINT________|byval_as_LONG___|LONG______________| |DWORD__________________|DWORD__________|byval_as_LONG___|DWORD_____________| |Windows_handles_ie._HDC|Windows_Handles|byval_as_LONG___|LONG______________| |INT,_UINT______________|INTEGER,_DWORD_|byval_as_LONG___|INTEGER,_DWORD____| |LONG___________________|LONGINT________|byval_as_LONG___|LONG______________| |LPARAM_________________|LONGINT________|byval_as_LONG___|LONG______________| |LPDWORD________________|^DWORD_________|as_LONG_________|BYREF_as_DWORD____| |LPINT,_LPUINT__________|^INTEGER_______|as_LONG_________|BYREF_as_LONG_____| |LPRECT_________________|^TRECT_________|as_ANY__________|QRECT_____________| |LPSTR,_LPCSTR__________|PCHAR__________|byval_as_STRING_|BYREF_as_STRING___| |LPVOID_________________|LONGINT________|as_ANY__________|LONG______________| |LPWORD_________________|^WORD__________|as_INTEGER______|BYREF_as_WORD_____| |LRESULT________________|LONGINT________|byval_as_LONG___|LONG______________| |NULL___________________|NIL____________|byval_as_LONG___|LONG______________| |SHORT__________________|SHORT__________|byval_as_INTEGER|SHORT_____________| |WORD___________________|WORD___________|byval_as_INTEGER|WORD______________| |WPARAM_________________|LONGINT________|byval_as_LONG___|LONG______________| I tipi di dati in rosso sono puntatori alla variabile. In Rapid-Q, ciò comporta l'uso di VARPTR quando si passa l'indirizzo della variabile, e non il valore della variabile. Ecco alcuni esempi di conversione: In VB: DECLARE SUB Test LIB "USER32" ALIAS "What" _ (byval L AS LONG, byval S AS STRING) Test (1230, "Hello world!") In Rapid-Q: DECLARE SUB Test LIB "USER32" ALIAS "What" _ (byval L AS LONG, byval S AS STRING) Test (1230, "Hello world!") Non c'è alcuna differenza, ma notate che Rapid-Q non usa BYVAL, che può essere omesso. Nell'esempio precedente, la stringa è passata per riferimento, cioè viene passato l'indirizzo della stringa, non la stringa stessa. Ecco un esempio in cui viene passata l'intera stringa (non l'indirizzo, così richiede un OLE per allocare spazio per la stringa); questo funziona in VB ma non in Rapid-Q: In VB: DECLARE SUB Test LIB "USER32" ALIAS "What" _ (byval L AS LONG, S AS STRING) Test (1230, "Hello world!") In Rapid-Q: Non è possibile in quanto non utilizza OLE per allocare la stringa. Cosa succede con le stringhe NULL? Ecco un'altra situazione (notate che non uso molto VB, per cui potrebbe essere possibile dichiarare S cone STRING e passarlo come vbNullString; non ne sono certo, così vado sul sicuro): In VB: DECLARE SUB Test LIB "USER32" ALIAS "What" _ (byval L AS LONG, byval S AS LONG) Test (1230, 0&) In Rapid-Q: DECLARE SUB Test LIB "USER32" ALIAS "What" _ (byval L AS LONG, byval S AS LONG) Test (1230, 0&) Così se vogliamo passare una stringa anzichè una stringa NULL, dovremo usare VARPTR: In VB o Rapid-Q: DIM S AS STRING S = "Hello world!" Test (1230, VARPTR(S)) Una stringa NULL è praticamente un indirizzo pari a 0. VARPTR ritorna semplicemente l'indirizzo di una variabile. E per gli altri puntatori, come LPWORD? Un dato WORD è semplicemente un numero positivo di 16 bit, e VB supporta solo numeri di 16 bit positivi o negativi chiamati INTEGER, ma l'idea generale è di rimuovere il comando byval quando si passano variabili per riferimento: In VB (non supporta WORD, utilizziamo il tipo più simile): DECLARE SUB Test LIB "USER32" ALIAS "Another" _ (L AS INTEGER) DIM L AS INTEGER Test (L) In Rapid-Q: DECLARE SUB Test LIB "USER32" ALIAS "Another" _ (BYREF L AS WORD) DIM L AS WORD Test (L) In Rapid-Q, utilizzate BYREF per passare variabili per riferimento; in VB non è necessario usare BYREF, come dimostrato dall'esempio precedente. ' =============================================================================== ' 2003 Holyguard.net - 2007_Abruzzoweb