Copyrigtht © 1997 Università di Firenze. All rights reserved.
Free license available.
Sommario.
In questo capitolo vengono descritte le API di Win32 necessarie per aprire e configurare una porta seriale. Vengono inoltre descritte varie tecniche per leggere e scrivere dati su una porta.
TRASFERIMENTO DATI CON LE API DI COMUNICAZIONE WIN32
Per realizzare un trasferimento dati seriale in Windows 95, si utilizzano
le API DI COMUNICAZIONE Win32 o brevemente Win32c. Tuttavia Win32c non
si limita al solo trasferimento dati, ma è in grado di gestire completamente
la porta seriale.
Storicamente il modem e la porta seriale sono strettamente correlati: tutti
i trasferimenti di dati su linea telefonica coinvolgono la porta seriale.
Per comprendere una comunicazione seriale è essenziale capire la
comunicazione del modem in Windows.
Il modem tipicamente spedisce i dati in modo sequenziale sulla linea telefonica,
ma Win32c può anche spedire dati su un device parallelo come la
porta LPT.
E' difficile generalizzare quando si usa Win32. Esso, per esempio, non
si adatta bene alla comunicazione su LAN (Local Area Network). Le LAN spesso
serializzano i dati da spedire in modo simile al modem, ma dovranno tener
conto di tecniche di accesso al canale, routing, protocolli a correzione
d'errore che hanno poco in comune con i protocolli di una porta seriale.
La complessità dei protocolli richiesti per le LAN fanno si che
Win32c sia poco più che un'idea per questo tipo di applicazioni.
Un altro limite di Win32c è quello di non poter lavorare con comunicazioni
real-time, nelle quali la qualità della riproduzione è strettamente
legata al tempo. Per esempio, se vogliamo digitalizzare l'audio, la qualità
del suono dipende dal rate di riproduzione. Quando l'audio è registrato
(ad esempio su un CD ROM) viene digitalizzato con un particolare rate,
noto come sampling rate. Ovviamente il segnale deve poi essere riprodotto
tenendo conto della frequenza di campionamento utilizzata.
Anche i segnali video sono dipendenti dal tempo: la velocità di
riproduzione deve essere opportuna. Supponiamo di voler riprodurre un file
audio (usando le Windows Multimedia Extensions), contenente, ad esempio,
un messaggio registrato, direttamente sulla linea telefonica. Non si può
usare Win32c proprio perchè non è in grado di trasmettere
l'audio in tempo reale (questo anche se disponiamo di un modem voice).
Quello che si può certamente fare, però, è trasmettere
come file audio il messaggio e simultaneamente inviare un segnale di controllo
al computer locale collegato all'altra estremità invitandolo a riprodurre
tale file usando, ad esempio, Microsoft Sound Recorder (accessorio di Windows
95).
Quindi non sono i file di tipo suono o audio come tali che precludono l'utilizzo
di Win32c, ma è il fatto che richiedono una trasmissione in tempo
reale ad ostacolarne la realizzazione.
Escludendo LAN, TCP/IP e comunicazioni in real-time, ci sono comunque moltissime
altre applicazioni adatte a Win32c. Praticamente tutte le applicazioni
che richiedono una porta seriale o un modem sono delle possibili candidate;
includendo e-mail, fax, file transfer e applicazioni interattive come la
terminal emulation.
Applicazioni come quelle appena citate sono felicemente supportate dalle
MAPI (Messaging API).
Quindi si consiglia di usare Win32c per tutte le comunicazioni interattive,
non in real-time, che non richiedano molti livelli di protocollo. Attualmente
questo consiglio non è stato ancora ben recepito. Win32c non ha
ancora assunto un ruolo centrale nella trasmissione di e-mail, faxes e
di file, anche se, come abbiamo già detto, grazie alle MAPI queste
funzioni verrebbero svolte ottimamente.
Ci sono comunque delle applicazioni dove è quasi obbligatorio utilizzare
Win32c, come nel caso in cui si debba scrivere drivers MAPI per comunicazioni
su area geografica, nel caso dei terminali interattivi, dei servizi online
o dei multiplayer games.
2.1 CONOSCENZE DI BASE PER LA COMUNICAZIONE SERIALE
La seguente trattazione presuppone la conoscenza di alcuni concetti basilari quali il baud rate, bits di start e di stop, parità. Si presuppone inoltre una certa familiarità con le operazioni base e i segnali dell'interfaccia seriale RS-232 (EIA RS-232). Di seguito, comunque, riportiamo un breve richiamo dei segnali più importanti.
2.1.1 APRIRE UNA PORTA SERIALE
In Windows 16-bit, sono previste funzioni speciali per aprire,
chiudere, leggere e scrivere da una porta seriale. Queste funzioni sono
OpenComm, CloseComm, ReadComm, WriteComm rispettivamente.
In Windows 95, invece, le porte seriali e gli altri dispositivi di comunicazione
sono trattati allo stesso modo dei files. La porta seriale è aperta,
chiusa, letta, scritta con le stesse funzioni usate per i files.
Una sessione di comunicazione inizia con una chiamata alla funzione CreateFile.
Tale funzione "apre" una porta seriale per una lettura, per una
scrittura o per entrambe contemporaneamente. Come sempre in Windows, CreateFile
ritorna un gestore (handle) da usare nelle successive operazioni sulla
porta aperta. La funzione come si può vedere sotto è complessa,
questo anche perchè è general-purpose. Infatti si utilizza
tale funzione sia per aprire un file esistente, sia per crearne uno nuovo,
ma anche per aprire una periferica, che non è un file, come una
porta seriale o parallela o un modem.
CreateFile(szDevice, fdwAccess, fdwShareMode, lpsa, fdwCreate, fdwAttrsAndFlags, hTemplateFile);
Il primo parametro szDevic è il nome logico della porta seriale aperta come COM1 o COM2. Il secondo è fdwAccess il quale specifica il tipo di accesso alla porta. Come per un file, la porta può essere aperta in lettura, scrittura o per entrambe. Il flag GENERIC_READ apre la porta in lettura, GENERIC_WRITE apre la porta in scrittura. Per averle entrambe basta collegare con un OR entrambi i flag come sotto:
fdwAccess = GENERIC_READ | GENERIC_WRITE
Il terzo parametro, fdwShareMode, specifica gli attributi di
condivisione di una porta. Questo parametro è usato per i file che
possono essere condivisi da più applicazioni. Esso deve essere impostato
a 0 per la porta seriale, che non deve essere condivisa. Questa è
una delle principali differenze tra una porta seriale e un file. Se un'altra
applicazione ha già aperto la porta seriale quando la corrente applicazione
chiama CreateFile, la funzione ritorna un errore perchè due
applicazioni non possono condividere la porta seriale. Tuttavia, più
percorsi (threads) della stessa applicazione possono condividere
il gestore (handle) della porta ritornato dalla funzione CreateFile.
Il quarto parametro lpsa, punta ad una struttura di attributi sulla
sicurezza che definisce come il gestore della porta può essere utilizzato
dai "figli" dell'applicazione che ha aperto la porta.
Il quinto parametro fdwCreate, specifica cosa fare se la funzione
CreateFile richiama un file già esistente. Quando una porta
seriale è già attiva esso deve essere configurato con OPEN_EXISTING.
Questo flag indica a Windows di non creare una nuova porta, ma di aprirne
una già esistente.
Il sesto parametro, fdwAttrsAndFlags, descrive vari attributi della
porta. Per i files sono possibili molti attributi, ma per la porta seriale
solo uno è interessante FILE_FLAG_OVERLAPPED. Quando è specificato
la porta di I/O lavora in background.
L'ultimo parametro, hTemplateFile, specifica il gestore di un file
archiviato e non è usato per aprire una porta, quindi è impostato
a NULL.
Quando una porta è stata aperta, possono essere allocati un buffer
di trasmissione e ricezione e altre informazioni di inizializzazione, richiamando
la funzione SetupComm come mostrato sotto. Non è essenziale
richiamare tale funzione, in quanto Windows alloca il buffer in trasmissione
e in ricezione di default. Comunque è buona norma assicurarsi che
il buffer abbia le dimensioni opportune.
SetupComm(hComm, dwRxBufSize, dwTxBufSize)
hComm è il gestore della porta aperta rimandato da Createfile. Il secondo parametro è la dimensione del buffer in ricezione e il terzo di quello in trasmissione.
2.1.2 CHIUDERE UNA PORTA SERIALE
Chiudere una porta seriale è molto più semplice che aprirla. Basta semplicemente chiamare la funzione CloseHandle con il gestore rimandato da CreateFiles come unico parametro.
CloseHandle(hComm);
Ricordarsi sempre di chiudere la porta altrimenti le altre applicazioni non saranno in grado di usarla.
2.2. CONFIGURARE UNA PORTA SERIALE
Configurare una porta seriale è più complicato in
Windows 95 rispetto alle precedenti versioni di Windows. Tuttavia ciò
consente di avere maggiori potenzialità, permettendo una maggiore
integrazione tra porta seriale e modem.
La configurazione inizia determinando la capacità della porta. In
questo modo si evita che un'applicazione utilizzi una configurazione non
corretta. Le capacità di una porta sono referenziate nella struttura
COMMPROP.
2.2.1 LA STRUTTURA COMMPROP
COMMPROP è probabilmente la struttura più interessante della famiglia delle strutture dati di Windows 95 Comunication. Essa contiene le possibili configurazioni per la porta seriale; ad esempio i valori possibili per il baud rate, il numero bits dei dati, il metodo per stabilire la parità. Se la porta è connessa con un modem, essa contiene anche la configurazione del modem. Per avere tali informazioni la porta deve essere aperta in modo opportuno.
2.2.1.1 Come ottenere le proprietà della porta
La funzione GetCommProperties, mostrata sotto, insieme
alla COMMPROP consente di ottenere le informazioni desiderate sulla porta
seriale. Il loro utilizzo però non si limita alla porta seriale,
infatti, ad esempio, possono essere usate anche per ottenere informazioni
sulla porta parallela.
Il contenuto di COMMPROP è statico (nessuna applicazione può
modificare i dati di questa struttura), quindi non c'è nessuna funzione
che consenta di scrivere in tale struttura la configurazione della porta.
GetCommProperties (hComm,lpCommProp)
I due parametri della funzione sono il gestore della porta e il puntatore
alla struttura COMMPROP mostrata sotto:
typedef struct _COMMPROP {
WORD wPacketLength; // dimensioni della struttura in bytes
WORD wPacketVersion; // versione della struttura
DWORD dwServiceMask; // servizi implementati
DWORD dwReservedl; // riservati, non utilizzabili
DWORD dwMaxTxQueue; // massima dimensione del buffer in TX, in bytes
DWORD dwMaxRxQueue; // massima dimensione del buffer in RX, in bytes
DWORD dwMaxBaud; // massimo baud rate, in bps
DWORD dwProvSubType; // specifica il tipo del provider
DWORD dwProvCapabilities; // capacità supportata
DWORD dwSettableParam; // parametri modificabili
DWORD dwSettableBaud; // possibile baud rate
WORD wSettableData; // dimensioni del bytes
WORD wSettableStopParity; // bits di stop, parità stabilita
DWORD dwCurrentTxQueue; // dimensioni del buffer in trasmissione, in bytes
DWORD dwCurrentRxQueue; // dimensioni del buffer in ricezione, in bytes
DWORD dwProvSpec1; // dati dello specifico provider
DWORD dwProvSpec2; // dati dello specifico provider
WCHAR wcProvChar[1]; // dati dello specifico provider
} COMMPROP, *LPCOMMPROP;
Si può notare che l'ultimo membro della struttura, chiamato
wcProvChar è dichiarato come un array di caratteri, anche
se consiste di un unico carattere. Questa strana costruzione si può
trovare spesso in Windows, per esempio quando si programma Device Indipendent
Bitmaps (DIBs). Tale membro deve essere interpretato come un byte di
partenza per ulteriori dati che possono essere appesi alla struttura. In
alcuni casi esso è utilizzato dall'applicazione per allocare memoria
per dati extra. Con COMMPROP, l'applicazione può allocare memoria
per dati extra, ma è Windows, non l'applicazione, che stabilisce
le dimensioni effettive. Ciò può causare dei problemi perchè
l'applicazione deve allocare memoria per la struttura COMMPROP e per gli
eventuali dati extra, ma non conosce quanta memoria Windows destinerà
ai dati extra.
Ci sono due vie per risolvere questo problema: una via diretta e una "pigra".
La via "pigra" che spreca memoria, ma risulta rapida e semplice,
è quella di allocare più memoria di quanta ne potrebbe richiedere
Windows. COMMPROP di per sé richiede soltanto 64 bytes, quindi se,
per esempio, alloco 1000 bytes (come mostrato sotto) sono sicuro di garantire
una sufficente area di memoria per ogni plausibile dato extra che Windows
mi può rimandare.
LPCOMMPROP lpCommProp = (LPCOMMPROP)malloc (1000);
Fortunatamente c'è una via migliore. Essa consiste nel richiamare due volte la funzione GetCommProperties come mostrato sotto. La prima volta si richiama la funzione con il campo wPacketLength contenente il numero totale di bytes richiesti dalla struttura, compresi i dati extra. L'applicazione controlla questo valore per allocare la memoria opportunamente e chiama nuovamente GetCommProperties per ricevere i dati extra.
LPCOMMPROP lpCommProp = (LPCOMMPROP)malloc (sizeof(COMMPROP));
lpCommProp.wPacketLength = sizeof(COMMPROP);
GetCommProperties(hComm,lpCommProp);
dwSize=lpCommProp.wPacketLength;
free(lpCommProp);
lpCommProp = (LPCOMMPROP)malloc (dwsize);
lpCommprop.wPacketLength = dwSize;
GetCommProperties(hComm, lpCommProp);
Come si può vedere sopra è necessario, prima di chiamare la funzione la prima volta, impostare wPacketLength con la sizeof(COMMPROP): Questo perchè Windows assumerà proprio il numero di bytes stabiliti da wPacketLength come riferimento per allocare lo spazio di memoria destinato ai dati (se destinassi più memoria del necessario potrei incorrere in problemi di overwrite). Windows successivamente imposta wPacketLength col numero di bytes richiesti per la struttura COMMPROP, compresi i dati extra. Infine l'applicazione rialloca opportunamente lo spazio di memoria per la struttura e richiama nuovamente GetCommProperties.
2.2.1.2 Definizione delle propietà della porta
Finora abbiamo visto come richiamare la sruttura COMMPROP e abbiamo
descritto il primo e l'ultimo campo (wPacketLength e wcProvChar)
della struttura. Analizziamo adesso gli altri campi.
Il campo wPacketLength specifica la versione della struttura COMMPROP.
Versioni differenti di Windows e di altri sistemi operativi Microsoft utilizzano
versioni diverse della struttura COMMPROP con differenti interpretazioni
per i vari membri. E' quindi opportuno testare questo membro per dare una
corretta interpretazione alla struttura. In Windows 95, la versione è
configurata a 0xffff.
Il membro dwServiceMask definisce le funzionalità della porta.
Per la porta seriale è impostato a SP_SERIALCOMM.
dwMaxTxQueue e dwMaxRxQueue contengono rispettivamente le
dimensioni massime consentite (in bytes) per i buffer di trasmissione e
ricezione. Se questo membro risulta essere 0 significa che non ci sono
limitazioni sulle massime dimensioni.
dwMaxBaud è il massimo data rate che la porta può
supportare, in bits per secondo (bps). Questo membro può essere
fissato o programmato. Nel caso in cui sia programmabile il campo è
impostato a BAUD_USER. Altrimenti è impostato ad uno dei valori
mostrati sotto.
BAUD_075 : 75 bps
BAUD_110 : 110 bps
BAUD_134_5 : 134.5 bps
BAUD_150 :150 bps
BAUD_300 :300 bps
BAUD_600 : 600 bps
BAUD_1200 : 1200 bps
BAUD_1800 : 1800 bps
BAUD_2400 : 2400 bps
BAUD_4800 : 4800 bps
BAUD_7200 : 7200 bps
BAUD_9600 : 9600 bps
BAUD_14400 : 14400 bps
BAUD_19200 : 19200 bps
BAUD_38400 : 38400 bps
BAUD_56K : 56 Kbps
BAUD_115200 : 115200 bps
BAUD_128K : 128 kbps
dwProvSubType specifica il tipo di periferica di comunicazione in modo più dettagliato rispetto a dwServiceMask. Per la porta seriale questo campo è impostato a PST_RS232. Altre periferiche supportate sono riportate sotto.
PST_FAX fax
PST_MODEM modem
PST_NETWORK_BRIDGE non specificato network bridge
PST_PARALLELPORT porta parallela
PST_SCANNER scanner
PST_TCPIP_TELNET TCP/IP Telnet protocol
PST_X25 standard X.25
dwProvCapabilities descrive la capacità della porta. Vediamole in dettaglio:
PCF_16BITMODE è supportato uno speciale modo a 16 bits
PCF_DTRDSR DTR (Data Terminal Ready)/DSR (Data Set Ready) sono supportate
PCF_INTTIMEOUTS gli intervalli di timeouts sono supportati
PCF_PARITY_CHECK il controllo di parità è supportato
PCF_RLSD RLSD (receive-line-signal-detect) è supportato
PCF_RTSCTS RTS (Request To Send)/CTS (Clear To Send) sono supportati
PCF_SETXCHARS i settaggi XON/XOFF sono supportati
PCF_SPECIALCHARS i caratteri speciali sono supportati
PCF_TOTALTIMEOUTS i timeouts totali sono supportati
PCF_XONXOFF il controllo del flusso XON/XOFF è supportato
L'interfaccia seriale RS-232 e il driver della porta seriale di Windows
supportano la maggior parte delle possibilità specificate sopra
ad eccezione di PCF_16BITMODE e di PCF_SPECIALCHAR.
Il membro dwSettableParams specifica quali tipi di impostazione
per la porta sono configurabili.
I valori dei flags e le rispettive interpretazioni sono mostrate sotto.
SP_BAUD il baud rate è configurabile
SP_DATABITS il numero di bit dati è configurabile
SP_HANDSHAKING l'handshaking è configurabile
SP_PARITY il parity mode è configurabile
SP_PARITY_CHECK il parity checking abilitato/disabilitato è configurabile
SP_RLSD RLSD è configurabile
SP_STOPBITS il numero di bit di stop è configurabile
dwSettableParams è uno dei membri più importanti
da testare per un'applicazione. E' infatti buona norma testare la configurazione
esistente prima di apportarne qualunque modifica.
dwSettableBaud, wSettableData e wSettableStopParity
contengono le possibili impostazioni per il baud rate, il numero di bits
di stop, il parity mode.
dwCurrentTxQueue e dwCurrentRxQueue contengono le dimensioni
correnti dei buffer in trasmissione e in ricezione, rispettivamente.
I membri dwProvSpec1, dwProvSpec2 e wcProvChar non
sono usati con la porta seriale RS-232. I primi due membri non sono usati
neppure dal modem; invece wcProvChar con il modem assume un particolare
significato che vedremo più avanti.