Salve, questo articolo si ricollega a quello che ho scritto un pò di tempo fà: Callback tra newLisp e Lazarus Pascal/Free Pascal/Delphi (prima parte).
Nel precedente articolo abbiamo visto come poter chiamare creare delle callback tra newLisp e Delphi/Free Pascal. Quello che però mancava era un sistema efficiente per scambiare dati tra i due linguaggi. In questo scambio dati dobbiamo anche tener presente che alcune volte le informazioni da scambiare potrebbero essere particolarmente voluminose (una immagine, un suono, etc…).
Visto che ho da poco ufficializzato lo sviluppo di EagleNet (progetto open source sviluppato con newLisp e Pure Basic), in questa seconda parte userò proprio Pure Basic (http://www.purebasic.com); non vi preoccupate però, in quanto i programmi di esempio presentati sono molto brevi, inoltre i concetti illustrati sono facilmente implementabili in quasi qualunque linguaggio.
Un pò di teoria
Per scambiare dati tra un programma newLisp e Pure Basic, in cui il software creato in Pure Basic usa newLisp come dll (oppure so nel caso di Linux), possiamo procedere in vari modi:
- La funzione newLisp chiama la callback e passa a PureBasic i dati sotto forma di argomenti della funzione chiamata.
- Creazione di un’area di memoria condivisa, accessibile sia da newLisp che da PureBasic.
Nel secondo caso, anche se dovremo “formattare” l’area di memoria (dobbiamo definire di volta in volta un formato dei dati che andremo a leggere/scrivere), avremo diversi vantaggi, tra cui:
- I dati non verranno mai copiati, guadagnando in velocità (soprattutto nello scambio di molti dati).
- Potremo scambiare agevolmente sia dati testuali che binari, senza dover ricorrere a stratagemmi particolari.
- Una shared memory è una memoria che possiamo “formattare” a nostro piacimento, così da non essere costretti a priori a definire il tipo di dato da scambiare (datatype primitivi oppure complessi).
Allocare la memoria in PureBasic
Prima di tutto dobbiamo creare, in Pure Basic, un’area di memoria, che useremo come shared memory.
Sarebbe opportuno allocare tale memoria nello heap-space e non nello stack (quando il linguaggio di programmazione che utilizzate vi permette di fare questa scelta!).
Per allocare memoria, si possono usare istruzioni come malloc() del C, oppure AllocateMemory() in PureBasic:
Global *sharedMemoryEagleNet = AllocateMemory(5000)
If Not *sharedMemoryEagleNet
MessageRequester("Error", "Cannot allocate shared memory. EagleNet cannot continue.")
End -100
EndIf
In questo esempio, ho allocato 5000 bytes. Se l’allocazione avverrà con successo, allora sharedMemoryEagleNet conterrà un puntatore alla prima locazione di memoria allocata. Alcuni linguaggi inizializzano la memoria allocata, altri no. Leggete quindi con attenzione il manuale delle API usate nel vostro linguaggio. Nel nostro caso la memoria è initizializzata con valori NULL – char(0) – nil.
Condividere la memoria con newLisp
Lo step successivo sarà quello di comunicare a newLisp, in qualche modo, dove si trova l’area di memoria da condividere. Per falro, potete usare il seguente codice newLisp:
(set 'gloSharedMem __gloSharedMem__) (cpymem (pack "ld" <DO_SHARED_MEMORY> ) (+ (first (dump gloSharedMem)) 12) 4)
Il tag <DO_SHARED_MEMORY> và sostituito con un numero rappresentante la locazione di memoria inziale dell’area condivisa. Per usare quest’area dovrete successivamente fare riferimento alla variabile gloSharedMem.
Potete usare il codice seguente per sostituire a tale tag la locazione di memoria allocata:
myFile = ReplaceString(myFile, "<DO_SHARED_MEMORY>", Str(*sharedMemory))
Dove myFile contiene il codice newLisp mostrato in precedenza.
Come fà newLisp a scrievere in quest’area di memoria?
newLisp potrà scrivere in questa shared memory in modo molto semplice:
(cpymem (pack "ld" (length tmpData)) (address gloSharedMem) 4 ) (cpymem tmpData (address gloSharedMem) (length tmpData) )
Il codice newLisp appena presentato non farà altro che scrivere nella shared memory tutto ciò che è contenuto nella variabile tmpData. In pratica viene usata la funzione cpymem per copiare il contenuto di tmpData nella memoria in questione.
La funzione (address gloSharedMem) fornisce la prima locazione di memoria della shared memory.
FATE ATTENZIONE! Nel precedente codice non c’è nessun controllo sul numero di bytes scritti nella shared memory!! Fate i dovuti controlli o diventerete ben presto “prede” dei crackers!!!
Leggere la shared memory da PureBasic
Bene, ora newLisp ha messo dei dati nella shared memory, ma come posso rileggerli da Pure Basic?
Una difficoltà che potrete incontrare è “formattare” i dati della memoria condivisa, ovvero non leggere le informazioni in essa contenute come semplici sequenze di bytes, ma come blocchi di dati con un vero significato. Per esempio, allocando 100 bytes, io potrei utilizzarli per inviare una stringa da newLisp a PureBasic in questo modo:
Locazioni: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 H E L L O N E W L I S P ! Ma potrei anche decidere di trasmettere due stringhe diverse in un sol colpo: Locazioni: 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 T E S T O _ 1 T E S T O _ 2
Nel secondo esempio, dalla locazione (offset rispetto l’indirizzo base allocato) inizia il secondo testo.
Per leggere il testo nel modo corretto, posso usare le strutture (structure, oppure i record del Pascal):
Structure TStructTesto
nome1.s{7}
nome2.s{7}
EndStructure
In questo modo ho definito una struttura che “formatta” dei dati: infatti i primi sette caratteri apparterranno al primo testo (nome1), mentre i sette caratteri successivi apparterranno al nome2.
Come usare tale struttura?
*memoria.TStructTesto = *sharedMemoryEagleNet
MessageRequester("newLisp", "Nome_1 -> " + *memoria\nome1)
MessageRequester("newLisp", "Nome_2 -> " + *memoria\nome2)
In pratica ho creato un puntatore che punta alla shared memory (prima riga dell’esempio). Tale puntatore accetta un datatype definito dalla mia structure.
Il codice *memoria\nome1 mi permette quindi di accedere al primo blocco di dati formattato (nome1). Per chi non conoscesse PureBasic, vi dico che la notazione *memoria\nome1 è analoga al “classico” *memoria->nome1 oppure memoria.nome1.
Con questo metodo potrete agevolmente leggere/scrivere dati nella shared memory, passando dati molto rapidamente da un linguaggio all’altro.
Questo è il modo che sto utilizzando io nel progetto EagleNet
A presto!!!
Translate To English!
Commenti Recenti