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.
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