Tutorial 13 : Fichiers à Mémoire Configurée

Je vais vous montrer ce que sont les fichiers à mémoires configurées et comment les employer à votre avantage. L'utilisation d'un fichier à mémoire configurée est très facile comme vous le verrez dans ce Tutorial.

Downloadez l'exemple ici.

Théorie:

Si vous examinez l'exemple du Tutorial précédent, vous constaterez qu'il a un défaut sérieux : que se passe-t-il si la taille du fichier vous voulez lire est plus grande que le bloc de mémoire allouée ? Ou que la chaîne de caractère que vous voulez chercher est coupée en deux à la fin du bloc de mémoire ? La réponse traditionnelle pour la première question c'est que vous devez à plusieurs reprises lire dans les données du fichier avant que l'on ne rencontre la fin du fichier. La réponse à la deuxième question c'est que vous devez vous préparer à ce cas spécial de fin de bloc de mémoire. ça s'appelle un problème de valeur aux frontières. C'est un vrai casse tête pour les programmeurs car ça engendre des bogues (des défauts) innombrables.
il n'y aurait plus aucun problème si nous pouvions réserver un très grand bloc de mémoire, assez grand pour stocker le fichier en entier. Cependant notre programme s'accaparerait trop de ressources. Ce sont les (Files Mapping) configurations de fichiers qui vont nous sauver. Pour utiliser la configuration de fichier, vous pouvez vous imaginer que c'est un fichier entier, étant déjà chargé en mémoire et vous pouvez employer un pointer de mémoire pour lire ou écrire des données dans ce fichier. C'est aussi simple que cela. Nul besoin d'employer des fonctions API de mémoire et pas besoin de faire de différence entre les fonctions API de fichiers d'entrée et de sortie non plus, ce sont les mêmes dans la configuration de fichiers (Files Mapping). La configuration de fichier est aussi employée comme moyen de partage des données entre plusieurs processus (entre plusieurs programmes). En employant la configuration de fichiers de cette façon, il n'y aucun fichier réellement impliqué. C'est plutôt dans le bloc de mémoire réservé que chaque processus peut *lire ou écrire*. Mais le partage de ces données entre plusieurs processus est un point délicat, ça ne doit pas être traité à la légère. Vous devez exécuter ces processus par ordre (les uns après les autres) en les synchronisant, sinon vos applications planteront très vite.
Dans ce Tutorial, nous ne verrons pas le sujet de 'fichier configuré' (File Mapping) en tant que moyen pour créer une zone de mémoire partagée. Nous allons plutôt voir comment utiliser le 'File Mapping' comme moyen de "Mapper" (d'organiser à la carte) un fichier dans la mémoire. En fait, le 'PE Loader' emploie la configuration de fichier (File Mapping) pour charger des fichiers exécutables dans la mémoire. C'est très commode uniquement pour les parties du fichier nécessaires, qui doivent être sélectivement lues sur le disque. Sous Win32, vous devez employer le File Mapping autant que possible.
Il y a quelques limitations à la configuration de fichier, quoique. Une fois que vous créez un fichier à mémoire configurée, sa taille ne peut pas être changé pendant cette session. Donc le 'File Mapping' est important pour les fichiers 'Read-Only' (une des propriété d'un fichier) ou pour des opérations sur des fichiers qui ne doivent en aucun cas affecter la taille du fichier. Ça ne signifie pas que vous ne pouvez pas employer le File Mapping lorsque vous voulez augmenter la taille du fichier. Vous pouvez estimer sa nouvelle taille et prendre comme mémoire cette taille pour votre fichier configuré, ainsi le fichier s'agrandira à cette taille. C'est par vraiment pratique, c'est tous.
Assez pour l'explication. On va plonger dans l'exécution de configuration de fichier (File Mapping). Pour employer le (File Mapping), on doit suivre ces étapes :
  1. Appelez CreateFile pour ouvrir le fichier que vous voulez Mapper.
  2. Appelez CreateFileMapping avec l'Handle du fichier renvoyé par CreateFile comme un de ses paramètres. Cette fonction crée un objet 'File Mappinf' du fichier ouvert par CreateFile.
  3. Appelez MapViewOfFile pour Mapper une région du fichier choisie ou bien du fichier entier en mémoire. Cette fonction renvoie le pointer du premier octet de la zone où commence le fichier configuré (Mappé).
  4. Employez le pointer pour lire ou écrire dans le fichier.
  5. Appelez UnmapViewOfFile pour Unmapper le fichier.
  6. Appelez CloseHandle avec l'Handle du fichier configuré (l'Handle du File Mapping) comme paramètre pour fermer le fichier configuré.
  7. Appelez CloseHandle une fois de plus, mais cette fois-ci avec l'Handle renvoyé par CreateFile pour fermer le fichier réel.

Exemple:

Le programme ci-dessous vous laisse ouvrir un fichier grâce à une DialogBox. Il ouvre le fichier en employant le principe du 'File Mapping', si le fichier est ouvert avec succès, le titre de fenêtre principale est changé par nom du fichier qui vient d'être ouvert. Vous pouvez ensuite sauvegarder le fichier sous un autre nom par le biais du sous-menu File/save. Le programme recopiera le contenu total du fichier ouvert, vers un nouveau fichier. Notez que vous ne devez pas appeler GlobalAlloc pour réserver un bloc de mémoire dans ce programme.

.386
.model flat,stdcall
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib

.const
IDM_OPEN equ 1
IDM_SAVE equ 2
IDM_EXIT equ 3
MAXSIZE equ 260

.data
ClassName db "Win32ASMFileMappingClass",0
AppName  db "Win32 ASM File Mapping Example",0
MenuName db "FirstMenu",0
ofn   OPENFILENAME <>
FilterString db "All Files",0,"*.*",0
             db "Text Files",0,"*.txt",0,0
buffer db MAXSIZE dup(0)
hMapFile HANDLE 0                                    ; Handle de la 'memory mapped file', doit être
                                                                    ; initialisé avec un 0 parce qu'on doit aussi l'utiliser comme
                                                                    ; un flag dans la section WM_DESTROY

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hFileRead HANDLE ?                               ; Handle du fichier source (fichier réel)
hFileWrite HANDLE ?                                ; Handle de la 'sortie' du fichier.
hMenu HANDLE ?
pMemory DWORD ?                                 ; Pointer sur les données dans le fichier source.
SizeWritten DWORD ?                             ; Nombre d'octes actuellement écrit par WriteFile. (écrit dans la Box
                                                                        ; où on peut mettre du texte)

.code
start:
        invoke GetModuleHandle, NULL
        mov    hInstance,eax
        invoke GetCommandLine
        mov CommandLine,eax
        invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
        invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND
    mov   wc.cbSize,SIZEOF WNDCLASSEX
    mov   wc.style, CS_HREDRAW or CS_VREDRAW
    mov   wc.lpfnWndProc, OFFSET WndProc
    mov   wc.cbClsExtra,NULL
    mov   wc.cbWndExtra,NULL
    push  hInst
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,OFFSET MenuName
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,\
                ADDR AppName, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
               CW_USEDEFAULT,300,200,NULL,NULL,\
    hInst,NULL
    mov   hwnd,eax
    invoke ShowWindow, hwnd,SW_SHOWNORMAL
    invoke UpdateWindow, hwnd
    .WHILE TRUE
        invoke GetMessage, ADDR msg,NULL,0,0
        .BREAK .IF (!eax)
        invoke TranslateMessage, ADDR msg
        invoke DispatchMessage, ADDR msg
    .ENDW
    mov     eax,msg.wParam
    ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_CREATE
        invoke GetMenu,hWnd                       ; Obtient l'handle du menu
        mov  hMenu,eax
        mov ofn.lStructSize,SIZEOF ofn
        push hWnd
        pop  ofn.hWndOwner
        push hInstance
        pop  ofn.hInstance
        mov  ofn.lpstrFilter, OFFSET FilterString
        mov  ofn.lpstrFile, OFFSET buffer
        mov  ofn.nMaxFile,MAXSIZE
    .ELSEIF uMsg==WM_DESTROY
        .if hMapFile!=0
            call CloseMapFile
        .endif
        invoke PostQuitMessage,NULL
    .ELSEIF uMsg==WM_COMMAND
        mov eax,wParam
        .if lParam==0
            .if ax==IDM_OPEN
                mov  ofn.Flags, OFN_FILEMUSTEXIST or \
                                OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
                                OFN_EXPLORER or OFN_HIDEREADONLY
                                invoke GetOpenFileName, ADDR ofn
                .if eax==TRUE
                    invoke CreateFile,ADDR buffer,\
                                                GENERIC_READ ,\
                                                0,\
                                                NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
                                                NULL
                    mov hFileRead,eax
                    invoke CreateFileMapping,hFileRead,NULL,PAGE_READONLY,0,0,NULL
                    mov     hMapFile,eax
                    mov     eax,OFFSET buffer
                    movzx  edx,ofn.nFileOffset
                    add      eax,edx
                    invoke SetWindowText,hWnd,eax
                    invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED
                    invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED
                .endif
            .elseif ax==IDM_SAVE
                mov ofn.Flags,OFN_LONGNAMES or\
                                OFN_EXPLORER or OFN_HIDEREADONLY
                invoke GetSaveFileName, ADDR ofn
                .if eax==TRUE
                    invoke CreateFile,ADDR buffer,\
                                                GENERIC_READ or GENERIC_WRITE ,\
                                                FILE_SHARE_READ or FILE_SHARE_WRITE,\
                                                NULL,CREATE_NEW,FILE_ATTRIBUTE_ARCHIVE,\
                                                NULL
                    mov hFileWrite,eax
                    invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0
                    mov pMemory,eax
                    invoke GetFileSize,hFileRead,NULL
                    invoke WriteFile,hFileWrite,pMemory,eax,ADDR SizeWritten,NULL
                    invoke UnmapViewOfFile,pMemory
                    call   CloseMapFile
                    invoke CloseHandle,hFileWrite
                    invoke SetWindowText,hWnd,ADDR AppName
                    invoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLED
                    invoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED
                .endif
            .else
                invoke DestroyWindow, hWnd
            .endif
        .endif
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .ENDIF
    xor    eax,eax
    ret
WndProc endp

CloseMapFile PROC
        invoke CloseHandle,hMapFile
        mov    hMapFile,0
        invoke CloseHandle,hFileRead
        ret
CloseMapFile endp

end start
 

Analyse:

                    invoke CreateFile,ADDR buffer,\
                                                GENERIC_READ ,\
                                                0,\
                                                NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
                                                NULL

Quand l'utilisateur choisit un fichier dans la DialogBox qui sert à ouvrir des fichiers, nous appelons CreateFile pour l'ouvrir. Notez que nous spécifions GENERIC_READ pour ouvrir ce fichier avec comme propriété 'Read-Only'. 'dwShareMode' est mis à zéro parce que nous ne voulons pas que d'autres processus n'aient accès au fichier pendant notre opération.

                    invoke CreateFileMapping,hFileRead,NULL,PAGE_READONLY,0,0,NULL

Alors nous appelons CreateFileMapping pour créer une 'Memory Mapped File' du fichier ouvert. La syntaxe de CreateFileMapping est la suivante :

CreateFileMapping proto hFile:DWORD,\
                                         lpFileMappingAttributes:DWORD,\
                                         flProtect:DWORD,\
                                         dwMaximumSizeHigh:DWORD,\
                                         dwMaximumSizeLow:DWORD,\
                                         lpName:DWORD

Sachez déjà, que CreateFileMapping ne doit pas Mapper la totalité de la mémoire du fichier. Vous pouvez utiliser cette fonction pour Mapper seulement une partie de la mémoire du fichier réel. Vous spécifiez la taille de la mémoire du fichier à Mapper dans les paramètres dwMaximumSizeHigh et dwMaximumSizeLow. Si vous indiquez une taille plus grande que le fichier réel, le fichier réel sera agrandit à la nouvelle taille. Si vous voulez que la taille de la mémoire du 'File Mapping' soit la même que celle du fichier réel, mettez ces deux derniers paramètres à zéro.
Vous pouvez employer un NUL dans le paramètre lpFileMappingAttributes pour que ce soit Windows qui crée une 'memory mapped file' avec des attributs de sécurité par défaut.
FlProtect définit la protection désirée pour la 'memory mapped file'. Dans notre exemple, nous employons PAGE_READONLY pour permettre uniquement l'opération de lecture sur la 'memory mapped file'. Notez que cet attribut ne doit pas contredire l'attribut employé dans CreateFile sinon CreateFileMapping échouera.
LpName pointe sur le nom de la 'memory mapped file'. Si vous voulez partager ce fichier avec d'autre processus, vous devez lui donner un nom. Mais dans notre exemple, notre processus est le seul qui emploie ce fichier donc nous ignorons ce paramètre.

                    mov     eax,OFFSET buffer
                    movzx  edx,ofn.nFileOffset
                    add      eax,edx
                    invoke SetWindowText,hWnd,eax

Si CreateFileMapping s'effectue avec succès, le nom du fichier ouvert prend la place du titre de la fenêtre. Le nom du fichier ainsi que son chemin complète sont stocké dans le buffer, nous voulons afficher uniquement le nom de fichier dans le titre donc nous devons ajouter la valeur du membre nFileOffset de la structure OPENFILENAME à l'adresse de ce buffer. (vous avez compris pourquoi ! ! !)

                    invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED
                    invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED

Comme précaution, nous ne voulons pas que l'utilisateur puisse ouvrir plusieurs fichiers les uns après les autres, donc nous devons griser le sous-menu 'Open' et dégriser le sous-menu 'Save'. EnableMenuItem est justement employé pour changer les attributs du menu..
Après ceci, nous attendons que l'utilisateur clique sur le sous-menu File/Save pour fermer notre programme. Au moment où l'utilisateur ferme le programme, nous devons en fait fermer la 'memory mapped file' ainsi que le fichier réel. Voici comment faire :

    .ELSEIF uMsg==WM_DESTROY
        .if hMapFile!=0
            call CloseMapFile
        .endif
        invoke PostQuitMessage,NULL

Dans le susdit petit bout de code, quand la procédure de fenêtre reçoit le message WM_DESTROY, il vérifie d'abord la valeur d'hMapFile pour voir si c'est un zéro ou non. Si ce n'est pas le zéro, il appelle la fonction CloseMapFile qui contient le code suivant :

CloseMapFile PROC
        invoke CloseHandle,hMapFile
        mov    hMapFile,0
        invoke CloseHandle,hFileRead
        ret
CloseMapFile endp

CloseMapFile ferme la 'memory mapped file' et le fichier réel, pour empêcher qu'il n'y ait une fuite de ressources lorsqu'on sort de notre programme Win32.
Si l'utilisateur veut le sauvegarder les données dans un autre fichier, le programme l'affiche dans une DialogBox spéciale 'Sauvegarde'. Après qu'il ait tapé le nom du nouveau fichier, le fichier est créé par la fonction CreateFile.

                    invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0
                    mov pMemory,eax

Immédiatement après que le fichier ait été créé, nous appelons MapViewOfFile pour Mapper la partie désirée de la 'memory mapped file' dans la mémoire. Cette fonction a la syntaxe suivante :

MapViewOfFile proto hFileMappingObject:DWORD,\
                                   dwDesiredAccess:DWORD,\
                                   dwFileOffsetHigh:DWORD,\
                                   dwFileOffsetLow:DWORD,\
                                   dwNumberOfBytesToMap:DWORD

dwDesiredAccess indique quelle opération nous souhaitons faire sur le fichier. Dans notre exemple, nous voulons seulement lire les données, donc nous employons FILE_MAP_READ.
dwFileOffsetHigh et dwFileOffsetLow indiquent l'offset de départ où commence le fichier que vous voulez Mapper en mémoire. Ici, nous voulons lire dans la totalité du fichier donc nous commençons le Mapping à l'offset 0.
dwNumberOfBytesToMap indique le nombre d'octets à Mapper dans la mémoire. Si vous voulez Mapper le fichier en entier (Spécifié par CreateFileMapping), donnez la valeur 0 à MapViewOfFile.
Après l'appel à MapViewOfFile, la partie désirée est chargée dans la mémoire. On vous renverra le pointer du bloc de mémoire qui contient les données du fichier.

                    invoke GetFileSize,hFileRead,NULL

Regarde de quelle taille est le fichier. La taille du fichier est renvoyée dans eax. Si le fichier est plus grand que 4 GB, le mot de poids fort de la taille du fichier est stocké dans FileSizeHighWord. Puisque nous ne nous attendons pas à manipuler un si grand fichier, nous pouvons l'ignorer.

                    invoke WriteFile,hFileWrite,pMemory,eax,ADDR SizeWritten,NULL

Écrit les données qui sont configurées(Mappées) dans la mémoire du fichier.

                    invoke UnmapViewOfFile,pMemory

Quand tout est près avec le fichier d'entrée, on l''Unmap' en mémoire.

                    call   CloseMapFile
                    invoke CloseHandle,hFileWrite

et on referme tous les fichiers.

                    invoke SetWindowText,hWnd,ADDR AppName

Restore le titre original de la fenêtre principale.

                    invoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLED
                    invoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED

Réactive le sous-menu 'Open' et désactive le sous-menu 'Save' en le grisant.


[Iczelion's Win32 Assembly HomePage]


Traduit par Morgatte