Comme illustré précédemment, la gestion séquentielle de données de type complexe en mémoire de masse utilise des fichiers binaires. Ils sont également utilisés pour assurer la persistance des objets. L’exemple précédent est suffisamment illustratif pour ce qui concerne l’enregistrement de structures. Mais voici un exemple tout différent qui rappellera peut être à certains, d’anciens utilitaires de visualisations et de modifications d’exécutables.
Le but du programme suivant est d’afficher, en hexadécimal et en ASCII, le contenu d’un programme exécutable et de permettre le remplacement d’une chaîne par une autre de même longueur. Il est donné à titre d’exemple et sans aucune prétention de performance.
Le programme cible de l’expérimentation est un programme en langage C qui est compilé et qui affiche Bonjour en session Dos. Le fichier exécutable est Bonjour.Exe et il a une taille de ±19 Ko. Voici son code source en C :
#include <stdio.h> main() { printf("\nBonjour"); getc(stdin); return 0; }
L’interface présente deux ListBox (LBHexa et LBAscii) pour l’affichage du contenu du fichier, deux TextBox
(TCherche et TRemplace) pour l’acquisition de la chaîne cherchée et de celle de remplacement, et un jeu de boutons qui permet le pilotage du programme :
BCharger provoque la lecture du fichier et son affichage dans les ListBox BRemplacer remplace la chaîne dans les ListBox
Voici le code complet du programme.
Imports System.IO
Imports System.Text.RegularExpressions ' Outils de manipulation des chaînes
Public Class FBase
Private Sub BCharger_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Handles BCharger.Click Dim ValeurLue(2000) As Byte ' Un buffer de lecture de 2000 octets
Dim UneLigneHex As String = String.Empty ' La ligne à placer dans LBHexa
Dim UneLigneAsc As String = String.Empty ' La ligne à placer dans LBAscii
Dim i As Integer
Dim FichierEntree As String = "X:\Tmp\Bonjour.Exe" Dim FEntree As BinaryReader
FEntree = New BinaryReader(File.Open(FichierEntree, FileMode.Open, FileAccess.Read)) LBHexa.Items.Clear()
LBAscii.Items.Clear() ' Effectuer la lecture jusqu’à EOF par lots
While FEntree.PeekChar() <> -1 ' de maximum 2000 octets et constituer les
ValeurLue = FEntree.ReadBytes(2000) ' lignes des ListBox en les limitant à 16
For i = 1 To ValeurLue.Length ' octets chacune pour faciliter la lecture
UneLigneHex = UneLigneHex & String.Format("{0:X2}", ValeurLue(i - 1)) UneLigneAsc = UneLigneAsc & Char.ConvertFromUtf32(ValeurLue(i - 1)) If i Mod 16 = 0 Then
LBHexa.Items.Add(UneLigneHex)
UneLigneHex = String.Empty ' Vider la ligne sauvée
LBAscii.Items.Add(UneLigneAsc) UneLigneAsc = String.Empty End If Next End While FEntree.Close() End Sub
Private Sub BRemplacer_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Handles BRemplacer.Click Dim RechAsc, RempAsc As String
Dim RechHex As String = String.Empty Dim RempHex As String = String.Empty Dim Ligne As String
Dim N As Integer Dim SRech As Regex
RechAsc = TCherche.Text ' Acquisition des chaînes en ASCII
RempAsc = TRemplace.Text
For N = 0 To RechAsc.Length – 1 ' Création des chaînes en hexadécimal
RechHex = RechHex & String.Format("{0:X2}", RechAsc.Substring(N, 1)) RempHex = RempHex & String.Format("{0:X2}", RechAsc.Substring(N, 1)) Next
SRech = New Regex(RechAsc) ' La recherche s’effectue par la chaîne ASCII
For N = 0 To LBAscii.Items.Count – 1 ' pour chaque ligne de la liste LBAscii, et
Ligne = LBAscii.Items.Item(N) ' comme les 2 listes présentent les mêmes
If SRech.IsMatch(Ligne) Then ' caractères aux mêmes endroits, exception Ligne = Ligne.Replace(RechAsc, RempAsc) ' faite du format, si une ligne de LBAscii
LBAscii.Items.RemoveAt(N) ' contient la chaîne cherchée, la ligne
LBAscii.Items.Insert(N, Ligne) ' correspondante de LBHexa la contient aussi.
LBAscii.SelectedIndex = N ' La chaîne trouvée dans une ligne d’une
Ligne = LBHexa.Items.Item(N) ' liste est donc remplacée dans deux lignes.
Ligne = Ligne.Replace(RechHex, RempHex) ' Chacune remplace ensuite sa ligne initiale
LBHexa.Items.RemoveAt(N) ' dans sa liste d’origine.
LBHexa.Items.Insert(N, Ligne) LBHexa.SelectedIndex = N
Exit For ' On termine si on a trouvé. Le programme ne
End If ' remplace donc que la première occurrence
Next ' éventuellement trouvée.
Private Sub BEnregistrer_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles BEnregistrer.Click Dim UnByte As Byte
Dim NrLigne As Integer Dim i As Byte
Dim FichierSortie As String = "X:\Tmp\Bonjour.Exe" Dim FSortie As BinaryWriter
FSortie = New BinaryWriter(File.Open(FichierSortie, FileMode.Open, FileAccess.Write)) For NrLigne = 0 To LBAscii.Items.Count - 1
For i = 0 To LBAscii.Items.Item(NrLigne).Length - 1
UnByte = Char.ConvertToUtf32(LBAscii.Items.Item(NrLigne).Substring(i, 1), 0) FSortie.Write(UnByte) ' Chaque caractère est converti en sa valeur
Next ' numérique Ascii et enregistré
Next
FSortie.Close() End Sub
End Class
L’enregistrement octet par octet est très peu performant. Il serait plus rentable de traiter un important nombre d’octets en mémoire et d’en commander l’enregistrement par lots, à l’instar de ce qui est fait pour la lecture du fichier.
Et ce n’est pas la seule lacune de ce programme. En effet, la recherche de la chaîne à remplacer s’effectue à l’intérieur de chaque ligne de seize caractères de la ListBox et par conséquent, si cette chaîne ne se trouve pas entièrement sur une même ligne, elle n’est pas trouvée. Enfin, seule la première occurrence trouvée est remplacée. Le but n’était pas de fournir un réel utilitaire de bidouillage, mais seulement un autre exemple de traitement binaire d’un fichier.
A propos des outils utilisés
ConvertFromUtf32 La méthode ConvertFromUtf32 de la classe Char donne le caractère dont la valeur Ascii lui est passée en paramètre. La méthode ConvertToUtf32(Chaîne, Index_du_caractère)
réalise la convertion inverse.
Format La méthode Format de la classe String sert, comme son nom l’indique, à la mise en forme de diverses valeurs. Elle est utilisée dans le programme précédent pour représenter les valeurs Ascii des caractères sous forme de paires de chiffres hexadécimaux :
String.Format("{0:X2}", RechAsc.Substring(N, 1)). Les possibilités de formatage sont nombreuses. Plusieurs sont étudiées dans une autre partie de ce cours. En attendant, le lecteur peut une fois de plus consulter l’aide de son VisualStudio.Net et le site de MicroSoft déjà référencé.
Substring Cette autre méthode de la classe String permet l’extraction d’un ou plusieurs caractères d’une chaîne à partir d’une position donnée de 0 à Length – 1. Dans la phrase de code ci-dessus,
Substring extrait 1 caractère de RechAsc à la Nième position.
Length Cette méthode, toujours de la classe String, fournit le nombre de caractères d’une chaîne. Elle a déjà été utilisée précédemment sur des chaînes contenues dans les composants, comme dans l’expression LBAscii.Items.Item(NrLigne).Length de la fonction
BEnregistrer_Click. Dans ce programme elle est aussi utilisée pour connaître le nombre de caractères d’une variable dans l’expression RechAsc.Length – 1 de la fonction
BRemplacer_Click.
IsMatch Cette méthode de la classe System.Text.RegularExpressions effectue la recherche d’une chaîne donnée à l’intérieur d’une autre et retourne True en cas de succès. Elle est utilisée dans le programme pour vérifier l’existence de la chaîne recherchée à l’intérieur d’une ligne de LBAscii, dont les lignes sont présentées une par une à la méthode jusqu’à réussite de la recherche, ou jusqu’à la fin de la liste. Les expressions régulières sont étudiées davantage dans la partie suivante du cours.