Écrire un périphérique personnalisé d'E/SDate de publication : 22/02/2009. Date de mise à jour : 01/08/2011.
Par
Morten Sørvig CUVELIER Thibaut (traducteur) Qt Quarterly
Dans cet article, nous allons développer un dérivé de
QIODevice,
qui chiffre et déchiffre à la volée un flux. La classe agit comme un wrapper pour une classe d'E/S, comme
QFile ou QSocket. Elle peut même
être combinée avec QTextStream ou
QDataStream.
Finalement, nous verrons quelles améliorations
QIODevice peut nous apporter.
I. L'article original
II. Nos buts, en guise d'introduction
III. Le périphérique personnalisé
IV. Le code source
V. QIODevices dans Qt4
VI. Divers
I. L'article original
Qt Quarterly est une revue trimestrielle électronique proposée par Nokia à destination des développeurs et
utilisateurs de Qt. Vous pouvez trouver les
versions originales.
Nokia, Qt, Qt Quarterly et leurs logos sont des marques déposées de Nokia Corporation en Finlande
et/ou dans les autres pays. Les autres marques déposées sont détenues par leurs propriétaires respectifs.
Cet article est une traduction d'un des tutoriels écrits par Nokia Corporation and/or its subsidiary(-ies)
inclus dans la documentation de Qt, en anglais. Les éventuels problèmes résultant d'une mauvaise traduction ne sont
pas imputables à Nokia.
II. Nos buts, en guise d'introduction
Le petit bout de code ci-après montre la manière d'utiliser notre périphérique d'E/S pour chiffrer des données
et stocker le résultat dans un fichier.
QFile file(" output.dat " );
CryptDevice cryptDevice(& file)
QTextStream out(& cryptDevice);
cryptDevice.open(QIODevice :: WriteOnly);
out < < " Hello World " ;
|
Les données écrites dans le
QTextStream
passent par le CryptDevice avant d'atteindre
QFile.
De la même manière, quand QTextStream
essaye de lire des données, CryptDevice s'interpose entre
elle et QFile.
L'avantage d'utiliser une classe de flux d'E/S, à l'opposé de réaliser le chiffrement dans une passe séparée,
est que le fichier ne doit pas être entièrement chargé en mémoire, ce qui améliore le temps de chiffrement.
Dans cet exemple, nous allons un chiffrement trivial à base de XOR. Ceci ne pourra pas garantir la moindre
sécurité, cet exemple ne peut donc pas être utilisé dans une application réelle. Le but de cet article
n'est pas tant le déchiffrement que la dérivation de
QIODevice.
III. Le périphérique personnalisé
Écrire une classe QIODevice
personnalisée dans Qt3 implique l'héritage à
QIODevice
et la réimplémentation d'une série de fonctions virtuelles
Un problème, qui apparaît, lors de l'écriture d'un wrapper, autour d'un
QIODevice existant, est que
le périphérique peut être, soit synchrone, soit asynchrone.
Les périphériques synchrones écrivent et lisent les données immédiatement ; les asynchrones peuvent prendre
jusqu'à quelques secondes pour délivrer les données.
Les QIODevices
sont aussi divisés entre périphériques à accès direct et à accès séquentiel.
Les périphériques à accès séquentiel (comme, par exemple, QSocket) offrent uniquement un
streaming
de données, pendant que ceux à accès direct offrent un accès aléatoire
(comme QFile).
Notre classe CryptDevice va être un périphérique à accès séquentiel. Que la classe soit synchrone ou
non ne dépend que du QIODevice
qui l'utilise.
IV. Le code source
La définition de la classe ressemble à ceci.
class CryptDevice : public QIODevice
{
public :
CryptDevice(QIODevice * underlyingDevice);
bool open(int mode);
void close();
void flush();
Offset at() const ;
bool at(int pos) const ;
Offset size() const ;
Q_LONG readBlock(char * data, Q_ULONG maxSize);
Q_LONG writeBlock(const char * data, Q_ULONG size);
int getch();
int putch(int ch);
int ungetch(int ch);
private :
QIODevice * underlyingDevice;
QValueStack< char > ungetchBuffer;
} ;
|
Les fonctions publiques sont toutes réimplémentées à partir de
QIODevice.
CryptDevice:: CryptDevice(QIODevice * underlyingDevice)
: underlyingDevice(underlyingDevice)
{
setType(IO_Sequential);
}
|
La définition du constructeur est très rapide :
Nous prenons un pointeur au QIODevice
comme argument, et utilisons le drapeau IO_Sequential
pour indiquer que le périphérique est séquentiel (par opposition aux accès aléatoires).
bool CryptDevice:: open(int mode)
{
bool underlyingOk;
if (underlyingDevice- > isOpen())
underlyingOk = (underlyingDevice- > mode() ! = mode);
else
underlyingOk = underlyingDevice- > open(mode);
if (underlyingOk) {
setState(IO_Open);
return true ;
}
return false ;
}
|
Dans la fonction open(), nous ouvrons le
périphérique à la base, s'il ne l'est pas déjà, et nous mettons son état à IO_Open.
void CryptDevice:: close()
{
underlyingDevice- > close();
setState(0 );
}
void CryptDevice:: flush()
{
underlyingDevice- > flush();
}
|
La fermeture et le vidage sont très simples.
int CryptDevice:: getch()
{
char ch;
if (readBlock(& ch, 1 ) = = 1 )
return (uchar )ch;
else
return - 1 ;
}
int CryptDevice:: putch(int ch)
{
char data = (char )ch;
if (writeBlock(& data, 1 ) = = 1 )
return ch;
else
return - 1 ;
}
int CryptDevice:: ungetch(int ch)
{
ungetchBuffer.push((char )ch);
return ch;
}
|
La fonction ungetch() met un caractère sur
le périphérique, en annulant le dernier getch(). En théorie, nous aurions pu, tout simplement, appeler
ungetch() sur le périphérique sous-jacent,
parce que notre chiffrement est très basique (un caractère sur le périphérique correspond à un caractère
dans le CryptDevice. Cependant, pour montrer comment implémenter un
buffer
avec ungetch(),
nous implémentons différemment cette fonction.
Q_LONG CryptDevice:: readBlock(char * data, Q_ULONG maxSize)
{
Q_ULONG ungetchRead = 0 ;
while (! ungetchBuffer.isEmpty()
& & ungetchRead < maxSize)
data[ungetchRead+ + ] = ungetchBuffer.pop();
Q_LONG deviceRead = underlyingDevice- > readBlock(data +
ungetchRead, maxSize - ungetchRead);
if (deviceRead = = - 1 )
return - 1 ;
for (Q_LONG i = 0 ; i < deviceRead; + + i)
data[i] = data[ungetchRead + i] ^ 0x5E ;
return ungetchRead + deviceRead;
}
|
À la lecture d'un bloc, nous commençons par lire tous les caractères non obtenus. Ensuite, nous appelons
readBlock() sur le périphérique sous-jacent.
À la fin, nous XORons chaque bit lu sur le périphérique avec la constante magique 0x5E.
Q_LONG CryptDevice:: writeBlock(const char * data, Q_ULONG size)
{
QByteArray buffer(size);
for (Q_ULONG i = 0 ; i < size; + + i)
buffer[i] = data[i] ^ 0x5E ;
return underlyingDevice- > writeBlock(buffer.data(),
size);
}
|
À l'écriture d'un bloc, nous créons un buffer temporaire, rempli avec les données XORées.
Une implémentation plus efficace aurait utilisé un buffer de 4069 bits sur le tas, et appelé
writeBlock de nombreuses fois si la taille
était plus grande que le buffer.
QIODevice :: Offset CryptDevice:: at() const
{
return 0 ;
}
|
La fonction at() retourne la position actuelle du
périphérique. Pour des périphériques séquentiels, elle devrait toujours retourner 0.
bool CryptDevice:: at(Offset )
{
return false ;
}
|
QIODevice a une surcharge de
at() qui impose la position du périphérique.
Pour les séquentiels, ceci n'a aucun sens.
QIODevice :: Offset CryptDevice:: size() const
{
return underlyingDevice- > size();
}
|
Dans la fonction size(), nous retournons la taille
du périphérique sous-jacent. Ceci est possible dans ce simple exemple, parce que l'algorithme de chiffrement est trivial.
Si la taille est inconnue, nous pouvons retourner 0.
V. QIODevices dans Qt4
La classe QIODevice de Qt4 diffère
en de nombreux aspects avec celle de Qt3. La principale est qu'elle hérite de
QObject, et fournit des signaux et des
slots pour notifier les autres applications des données arrivant. Ceci signifie qu'il est plus facile d'implémenter des
périphériques qui supportent les opérations asynchrones. En plus,
l' API a été nettoyée. Ainsi, la dérivation de
QIODevice requiert uniquement deux
fonctions : readData() et
writeData().
VI. Divers
Un tout grand merci à matrix788 pour sa relecture !
Au nom de toute l'équipe Qt, j'aimerais adresser le plus grand remerciement à Nokia pour nous avoir
autorisé la traduction de cet article !
Copyright ©2004 Morten Sørvig.
Aucune reproduction, même partielle, ne peut être faite
de ce site et de l'ensemble de son contenu : textes, documents, images, etc
sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts. Cette page est déposée à la SACD.
|