Il
flusso delle funzioni verso la porta seriale |
Prima
di introdurre la seconda funzione di accesso alla porta seriale
è necessario aver una visione generale di come avviene
il flusso delle funzioni che tentano di operare sulla porta
stessa.
S'immagini infatti di avere due operazioni distinte ma contemporanee,
una di accesso alla porta in lettura e l'altra in scrittura.
Quale delle due verrà eseguita per prima?
E' possibile poi costruire un'applicazione che permetta a
più funzioni di operare contemporaneamente?
La risposta ai quesiti sta nel fatto che processi di comunicazione
distinti (ad esempio il procedimento di lettura e quello di
scrittura) possono essere nonoverlapped (ossia non
accavallati) nel senso che un'operazione viene effettuata
solamente quando l'altra è terminata.
Questo sistema risulta particolarmente utile nelle applicazioni
con più processi (ad esempio quando le operazioni sulla
porta seriale risultano essere molto lunghe oppure quando
le funzioni che vi accedono sono diverse).
Rappresenta un compito dell'applicazione gestire la sequenza
di funzioni che accedono alla porta in modalità overlapped.
Ad esempio se si richiamano le funzioni di scrittura e di
lettura, l'applicazione darà l'accesso soltanto alla
prima, bloccando la seconda finché l'operazione precedente
non ha terminato il proprio lavoro.
Questo flusso si rivela molto ordinato e di conseguenza permette
un utilizzo della memoria molto razionale. Proprio per questi
motivi è il sistema generalmente più diffuso.
La figura sottostante rappresenta in maniera astratta l'avvicinamento
contemporaneo di più funzioni verso una porta seriale.
L'
accavallamento delle funzioni nel percorso verso la porta
seriale ossia la modalità overlapped è
un procedimento non tanto diretto quanto il precedente ma
permette una maggiore flessibilità ed efficienza.
Quando la porta seriale è aperta alla comunicazione
infatti più funzioni possono eseguire allo stesso momento
operazioni di lettura e scrittura, anche se altri processi
risultano ancora pendenti.
Ciononostante
è necessario non abusare della possibilità di
mantenere attivi più processi in quanto l'efficienza
ne potrebbe risentire a causa dell'ammontare di memoria richiesta
che si accumula in base al numero di funzioni attive.
A questo sguardo generale sui processi si può aggiungere
che la modalità overlapped di accesso ad una porta
seriale si compone di due parti distinte:
1. la creazione dell'operazione
2. il controllo del suo effettivo successo
La creazione di un processo overlapped implica innanzitutto
la creazione di un evento di sincronizzazione tramite la funzione
CreateEvent. Il controllo invece avviene semplicemente con
l'arrivo del valore di ritorno della funzione. Se questo è
differente da -1 allora si può passare alla gestione
dei dati ricevuti dalla funzione, in caso contrario utilizzando
la funzione GetLastError si otterrà un tipo di errore
indicativo del problema incontrato (nel caso di comunicazione
overlapped si riceverà presumibilmente un errore del
tipo ERROR_IO_PENDING che indica che la funzione ha incontrato
un problema durante la sua esecuzione).
Prima
di cominciare, quale dei due metodi si può ritenere
il migliore? La risposta dipende naturalmente dal numero delle
funzioni che s'incontrano al cosiddetto "collo di bottiglia",
ossia a quello che nella prima figura è stato indicato
come "blocco delle funzioni gestito dall'applicazione":
più funzioni attendono e solamente una passa. Non soltanto
un processo overlapped richiede più lavoro alla memoria,
ma è soggetto anche a un maggior numero di errori:
se un'operazione nonoverlapped infatti fallisce, la funzione
ritorna l'errore (presumibilmente il valore -1). Se è
invece un'operazione overlapped a fallire, le possibili cause
di errore possono essere molteplici: errore nella creazione
dell'operazione, errore nel processo ancora pendente, time-out
dell'operazione, time-out nel controllo del successo dell'operazione.
Una volta scelto il tipo di flusso di funzioni che si desidera
utilizzare, è necessario svilupparlo.
Mentre per un processo nonoverlapped basterà utilizzare
il codice dell'esempio riportato nell'articolo precedente,
per effettuare un processo overlapped è necessario
fare alcune modifiche.
Siccome però nella continuazione di questa serie di
articoli si utilizzerà unicamente una comunicazione
nonoverlapped, dell'altra si analizzeranno solamente i tratti
principali e la struttura di fondo.
Una nota da sottolineare prima di cominciare è che
i processi accavallati o non (overlapped e nonoverlapped)
sono ben distinti dal concetto di comunicazione sincrona e
asincrona. Mentre questo indica infatti il processo di trasferimento
dei dati, quello rappresenta come può essere gestito
il traffico di funzioni verso la porta seriale.
Il punto da cui cominciare è la modifica della funzione
CreateFile, utilizzata come già visto, per accedere
alla porta seriale. Era stato usato in precedenza la seguente
linea di codice:
nPorta
= CreateFile("COM1", GENERIC_READ Or GENERIC_WRITE,
0, 0&, _
OPEN_EXISTING, 0, 0) |
indicando
quindi come penultimo parametro (dwFlagsAndAttributes) il
valore 0. Questo implica la creazione di un procedimento nonoverlapped:
la funzione attenderà il termine di eventuali processi
pendenti.
Se si vuole invece impostare un processo overlapped bisognerà
invece fare uso della costante FILE_FLAG_OVERLAPPED facente
riferimento alla struttura OVERLAPPED che tiene traccia
delle operazioni simultanee:
Type
OVERLAPPED
Internal As Long
InternalHigh As Long
offset As Long
OffsetHigh As Long
hEvent As Long
End Type |
A
questo punto dovremo creare un oggetto-evento indicativo per
ogni funzione di accesso alla porta, dandogli uno stato iniziale.
I possibili stati di questo oggetto sono due: signaled e non-signaled.
Quando l'oggetto che si andrà ad associare a ciascuna
funzione è nello stato signaled significa che l'operazione
è terminata.
A tale scopo si farà uso della funzione CreateEvent:
CreateEvent
Security, False, True,
"Evento-lettura" |
Dove
l'ultimo parametro, ossia "evento-lettura" è
un nome qualsiasi che si può scegliere per identificare
il tipo di operazione a cui è associato l'evento stesso.
Ricordarsi di inserire nel codice la struttura SECURITY_ATTRIBUTES
a cui punta il primo parametro della CreateEvent
La funzione ReadFile invece dovrà essere modificata
in questo modo:
vRitorno
= ReadFile(nPorta, Carattere(1), Lunghezza_Buffer, Dati_Ricevuti,
_ overlaps(0)) |
dove
overlaps(0) indica un'array di strutture OVERLAPPED ciascuna
contenente le informazioni su una diversa operazione pendente.
Ad esempio la prima struttura di overlaps() può essere
utilizzata per le operazioni di lettura in background, la
seconda per quelle di scrittura e la terza per monitorare
lo stato della porta alla ricerca di eventuali errori.
Associare una struttura ad una funzione è semplice:
si utilizza nuovamente l'evento creato appositamente. Ogni
struttura OVERLAPPED fa infatti riferimento ad un oggetto
Event che rappresenta il collegamento struttura - funzione.
E' quindi possibile utilizzare la funzione WaitForSingleObject
per determinare in ogni momento lo stato dell'oggetto-evento:
Declare
Function
WaitForSingleObject Lib "kernel32"
Alias "WaitForSingleObject"
_ (ByVal hHandle As
Long, ByVal dwMilliseconds
As Long) As
Long |
Dove
hHandle definisce l'oggetto del quale determinare lo stato
(quindi nello specifico l'oggetto evento della struttura OVERLAPPD)
e dwMillisecons è il tempo massimo di attesa. Se l'attesa
supera questo tempo massimo verrà ritornato il valore
WAIT_TIMEOUT, se una qualsiasi operazione sulla porta è
terminata WAIT_OBJECT_0, in caso contrario WAIT_ABANDONED.
Un esempio di processi overlapped può essere rappresentato
dal codice che segue. Prendiamo in esame solamente una funzione.
Su una casella di testo verrà visualizzato se la funzione
sia è esecuzione oppure se c'è un'altra funzione
in corso. Chiaramente avendo preso in considerazione una sola
funzione il risultato sarà obbligatoriamente il primo,
ma allargando il codice al caso di più funzioni (utilizzando
come già visto una matrice di strutture, una per ogni
funzione), si otterrà il risultato desiderato. Sono
state omesse le dichiarazioni delle funzioni già analizzate
nel corso degli articoli precedenti:
Private
Type
SECURITY_ATTRIBUTES
nLength As Long
lpSecurityDescriptor As Long
bInheritHandle As Long
End Type
Private Type OVERLAPPED
Internal As Long
InternalHigh As Long
offset As Long
OffsetHigh As Long
hEvent As Long
End Type
Private
Sub Form_Load()
Dim nPorta
As Long
nPorta = CreateFile("COM2", GENERIC_READ Or
GENERIC_WRITE, _
FILE_FLAG_OVERLAPPED, 0&, OPEN_EXISTING, 0, 0)
Dim Security As
SECURITY_ATTRIBUTES
CreateEvent Security, False,
True, "Evento"
End Sub
Private
Sub Timer1_Timer()
Const Lunghezza_Buffer =
10
Dim Dati_Ricevuti As
Long
Dim Carattere(1 To Lunghezza_Buffer)
As Byte
vritorno = ReadFile(nPorta, Carattere(1), Lunghezza_Buffer,
Dati_Ricevuti, _ Evento)
Select Case vritorno
Case 0
Text1.Text = "La funzione è in esecuzione"
Case Else 'presumibilimente
utilizzando la funzione GetLastError si otterrà
il 'valore
'ERROR_IO_PENDING, ma non è fondamentale conoscere
il tipo di problema
'incontrato dalla funzione
Text1.Text = "Errore nell'esecuzione"
End Select
End Sub |
|