Tutorial 12 : Gestion de la Mémoire
Et des Fichiers d'entrée-sortie

Nous allons étudier de façon rudimentaire la gestion de la mémoire et les opérations les fichiers i/o (i/o=input/output=entrée sortie) dans ce Tutorial. En plus nous utiliserons des boîtes de dialogue communes en tant que dispositifs d'entrée-sortie.

Downloadez l'exemple ici.

Théorie:

La gestion de la mémoire sous Win32 du point de vue de l'application est très simple et directe. Chaque processus possède 4 GB d'espace d'adresse mémoire. Le modèle de mémoire employé, est appelé le modèle de mémoire 'FLAT'(plat). Dans ce modèle, tous les registres de segment pointent sur la même adresse de départ et l'offset est sur 32 bits. Ainsi, une application peut accéder à n'importe quel endroit de la mémoire dans son propre espace d'adresse sans avoir besoin de changer la valeur des registres. Ça simplifie beaucoup la gestion de la mémoire. Il n'y a plus de pointers ou registres "Near" ou "FAR" désormais (pointers proches ou lointains).
Sous Win16, il y a deux principales sortes de fonctions API de mémoire : Global et Local. Le type d'API Global peut utiliser des Call qui vont vers des parties de mémoire se trouvant dans d'autres segments. Ainsi ce type d'API Global sont des fonctions de mémoires "lointaines". Le type d'API Local utilise des Call qui traitent uniquement avec la partie locale où le processus en est . Donc ce sont des fonctions de mémoire "proches". Sous Win32, ces deux types sont identiques. Si vous appelez GlobalAlloc ou LocalAlloc, vous obtenez exactement le même résultat.
Voici les étapes pour allouer et utiliser la mémoire :
  1. Réservez un bloc de mémoire en appelant GlobalAlloc. Cette fonction renvoie l'handle du bloc de mémoire demandé.
  2. "Lockez(Fermez)" le bloc de mémoire en appelant GlobalLock. Cette fonction reçoit l'handle du bloc de mémoire et renvoie le pointer du bloc de mémoire.
  3. Vous pouvez employer le pointer pour lire ou écrire dans cette mémoire.
  4. "Ouvrez" le bloc de mémoire en appelant GlobalUnlock. Cette fonction rend nul et sans effet le pointer du bloc de mémoire.
  5. Libérez le bloc de mémoire en appelant GlobalFree. Cette fonction reçoit l'handle du bloc de mémoire.
Vous pouvez aussi remplacer "Global" par "Local" comme LocalAlloc, LocalLock, etc.
Cette méthode sera davantage simplifiée plus loin en employant le flag 'GMEM_FIXED', dans l'appel de GlobalAlloc. Si vous utilisez ce flag, la valeur de retour de Global/LocalAlloc sera le pointer du bloc de mémoire alloué, et non pas l'Handle du bloque de mémoire. Vous ne devez pas appeler Global/LocalLock et vous pouvez passer le pointer à Global/LocalFree sans appeler Global/LocalUnlock d'abord. Mais dans ce Tutorial, j'emploierai l'approche "traditionnelle".

Les fichiers d'entrée-sortie sous Win32 ressemblent étonnement à ceux sous DOS. Les étapes nécessaires sont les mêmes. Vous avez seulement besoin de changer les appels aux API et c'est fait. Les étapes exigées sont les suivantes :
 

  1. Ouvrez ou créez le fichier en appelant la fonction CreateFile. Cette fonction est bourrée de ressources : en plus des fichiers, elle est capable d'ouvrir des ports de communication, des lecteurs de disques ou le clavier. En cas de succès (quand tout ce passe bien), il renvoie l'handle du fichier ou celui du dispositif. Vous pouvez alors employer cet handle pour exécuter des opérations sur le fichier ou le dispositif.

  2. Déplacez le fichier pointer(sélectionné) à l'emplacement désiré en appelant SetFilePointer.
  3. Exécutez, lisez ou écrivez l'opération en appelant ReadFile ou WriteFile. Ces fonctions transfèrent des données d'un bloc de mémoire un fichier. Donc vous devez allouer(réserver) un bloc de mémoire assez grande pour qu'il puisse contenir les données.
  4. Fermez le fichier en appelant CloseHandle. Cette fonction reçoit l'Handle du fichier.

Contenu:

Le programme inscrit ci-dessous, affiche une boîte de dialogue qui sert à ouvrir des fichiers. Il laisse l'utilisateur choisir un fichier texte quelconque pour l'ouvrir puis affiche le contenu de ce fichier dans une fenêtre d'édition à l'intérieur de son secteur client. L'utilisateur a la possibilité de modifier le texte dans la zone d'édition à sa guise puis ensuite il peut sauvegarder le contenu dans un fichier.

.386
.model flat,stdcall
option casemap:none
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
MEMSIZE equ 65535

EditID equ 1                            ; n° ID de la zone d'édition.

.data
ClassName db "Win32ASMEditClass",0
AppName db "Win32 ASM Edit",0
EditClass db "edit",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)

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwndEdit HWND ?                               ; L'Handle de la zone d'édition.
hFile HANDLE ?                                   ; Handle du fichier.
hMemory HANDLE ?                            ;Handle du bloc de mémoire réservé.
pMemory DWORD ?                            ;Pointer du bloc de mémoire réservé
SizeReadWrite DWORD ?                   ; Nombre d'octets lu ou écrit réalité. (dans le bloque mémoire on peut très bien ne pas tout utiliser).

.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:SDWORD
    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 uses ebx hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_CREATE
        invoke CreateWindowEx,NULL,ADDR EditClass,NULL,\
                   WS_VISIBLE or WS_CHILD or ES_LEFT or ES_MULTILINE or\
                   ES_AUTOHSCROLL or ES_AUTOVSCROLL,0,\
                   0,0,0,hWnd,EditID,\
                   hInstance,NULL
        mov hwndEdit,eax
        invoke SetFocus,hwndEdit
;====================================================
;        Initialisation des members de la structure OPENFILENAME
;====================================================
        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_SIZE
        mov eax,lParam
        mov edx,eax
        shr edx,16
        and eax,0ffffh
        invoke MoveWindow,hwndEdit,0,0,eax,edx,TRUE
    .ELSEIF uMsg==WM_DESTROY
        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 or GENERIC_WRITE ,\
                                FILE_SHARE_READ or FILE_SHARE_WRITE,\
                                NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
                                NULL
                    mov hFile,eax
                    invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE
                    mov  hMemory,eax
                    invoke GlobalLock,hMemory
                    mov  pMemory,eax
                    invoke ReadFile,hFile,pMemory,MEMSIZE-1,ADDR SizeReadWrite,NULL
                    invoke SendMessage,hwndEdit,WM_SETTEXT,NULL,pMemory
                    invoke CloseHandle,hFile
                    invoke GlobalUnlock,pMemory
                    invoke GlobalFree,hMemory
                .endif
                invoke SetFocus,hwndEdit
            .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 hFile,eax
                        invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE
                        mov  hMemory,eax
                        invoke GlobalLock,hMemory
                        mov  pMemory,eax
                        invoke SendMessage,hwndEdit,WM_GETTEXT,MEMSIZE-1,pMemory
                        invoke WriteFile,hFile,pMemory,eax,ADDR SizeReadWrite,NULL
                        invoke CloseHandle,hFile
                        invoke GlobalUnlock,pMemory
                        invoke GlobalFree,hMemory
                    .endif
                    invoke SetFocus,hwndEdit
                .else
                    invoke DestroyWindow, hWnd
                .endif
            .endif
        .ELSE
            invoke DefWindowProc,hWnd,uMsg,wParam,lParam
            ret
.ENDIF
xor    eax,eax
ret
WndProc endp
end start


Analyse:

        invoke CreateWindowEx,NULL,ADDR EditClass,NULL,\
                   WS_VISIBLE or WS_CHILD or ES_LEFT or ES_MULTILINE or\
                   ES_AUTOHSCROLL or ES_AUTOVSCROLL,0,\
                   0,0,0,hWnd,EditID,\
                   hInstance,NULL
        mov hwndEdit,eax

Dans la section WM_CREATE, nous créons un contrôle d'édition (ou une zone d'édition, de saisie). Notez que les paramètres x, y, width, height qui sont les dimensions du contrôle sont tous mis à zéros puisque nous redéfinirons leurs tailles plus tard pour que son secteur client soit contenu en entier dans la fenêtre parente.
Remarquez que dans ce cas, nous ne devons pas appeler ShowWindow pour faire apparaître la zone d'édition à l'écran, parce que nous incluons le style WS_VISIBLE. Vous pouvez aussi utiliser ce tour de passe passe pour la fenêtre parente.

;=====================================================
;        Initialisation des membres de la structure OPENFILENAME.
;=====================================================
        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

Après la création du contrôle d'édition, prenons le temps d'initialiser les membres d'ofn. Parce que nous souhaitons aussi réutiliser ofn pour la boîte de dialogue de sauvegarde, nous remplissons uniquement les membres *communs* employés par GetOpenFileName et GetSaveFileName.
La section WM_CREATE est un formidable endroit pour pouvoir faire vite fait l'initialisation.

    .ELSEIF uMsg==WM_SIZE
        mov eax,lParam
        mov edx,eax
        shr edx,16
        and eax,0ffffh
        invoke MoveWindow,hwndEdit,0,0,eax,edx,TRUE

Nous recevons des messages WM_SIZE lorsque la taille du secteur client de notre fenêtre principale change. Nous recevons aussi ce message quand la fenêtre est créée en premier. Pour pouvoir recevoir ce message, les styles de classe de fenêtre doivent inclure les styles CS_VREDRAW et CS_HREDRAW. Nous profitons de cette occasion pour redimensionner notre contrôle d'édition à la taille du le secteur client de notre fenêtre parentale. D'abord nous devons connaître la largeur actuelle et la hauteur du secteur client de la fenêtre parente. Nous obtenons ces renseignements de lParam. Le mot de poids fort d'lParam contient la hauteur alors que le mot de poids faible d'lParam contient la largeur du secteur client. Nous employons alors ces deux informations pour redimentionner la zone d'édition en appelant la fonction MoveWindow. En plus du changement de la taille de la fenêtre, nous pouvons aussi changer sa position.

            .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

Quand l'utilisateur choisit le sous-menu File/Open, nous remplaçons les Flags de la structure ofn et appelons la fonction GetOpenFileName pour afficher la boîte de dialogue qui sert à ouvrir des fichiers.

                .if eax==TRUE
                    invoke CreateFile,ADDR buffer,\
                                GENERIC_READ or GENERIC_WRITE ,\
                                FILE_SHARE_READ or FILE_SHARE_WRITE,\
                                NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
                                NULL
                    mov hFile,eax

Après que l'utilisateur ait choisit un fichier pour l'ouvrir, nous appelons CreateFile pour ouvrir ce fichier. Nous indiquons à cette fonction qu'elle doit essayer d'ouvrir le fichier pour le lire ou bien écrire dedans. Après que le fichier soit ouvert, la fonction renvoie l'handle de ce fichier que nous stockons dans une variable Globale pour une future utilisation. Cette fonction a la syntaxe suivante :

CreateFile proto lpFileName:DWORD,\
                           dwDesiredAccess:DWORD,\
                           dwShareMode:DWORD,\
                           lpSecurityAttributes:DWORD,\
                           dwCreationDistribution:DWORD\,
                           dwFlagsAndAttributes:DWORD\,
                           hTemplateFile:DWORD

dwDesiredAccess spécifie quelle est l'opération que vous voulez exécuter sur le fichier.

dwShareMode indique quelle opération, sur le fichier ouvert, vous voulez permettre à d'autres processus d'effectuer. lpSecurityAttributes n'a aucune signification sous Windows 95.
dwCreationDistribution indique que l'action CreateFile s'exécutera, que le fichier indiqué dans lpFileName existe ou non. dwFlagsAndAttributes indique les attribus du fichier                     invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE
                    mov  hMemory,eax
                    invoke GlobalLock,hMemory
                    mov  pMemory,eax

Quand le fichier est ouvert, nous allouons(réservons) un bloc de mémoire pour l'utiliser par les fonctions WriteFile ou ReadFile. Nous spécifions le flag GMEM_MOVEABLE pour que ce soit Windows qui s'occupe de déplacer le bloc de mémoire dans le but de consolider la mémoire (défragmentation locale). Le flag GMEM_ZEROINIT dit à GlobalAlloc de remplir le bloc de mémoire nouvellement réservé avec des zéros.
Quand les retours de GlobalAlloc sont menés avec succès, eax contiennent l'handle du bloc de mémoire alloué. Nous passons cet handle à la fonction GlobalLock qui renvoie ensuite le pointer au bloc de mémoire.

                    invoke ReadFile,hFile,pMemory,MEMSIZE-1,ADDR SizeReadWrite,NULL
                    invoke SendMessage,hwndEdit,WM_SETTEXT,NULL,pMemory

Quand le bloc de mémoire est prêt pour l'utilisation, nous appelons la fonction ReadFile pour lire les données du fichier. Quand un fichier est d'abord ouvert ou créé, le fichier n'est pas. Ainsi dans ce cas, nous commençons à lire le premier octet dans le fichier. Le premier paramètre de ReadFile est l'handle du fichier à lire, le deuxième est le pointer du bloc de mémoire qui content les données, ensuite c'est le nombre d'octets à lire dans le fichier, le quatrième paramètre est l'adresse de la variable (de taille DWORD) qui contiendra le nombre d'octets réellement lus dans le fichier.
Après que nous ayons remplis le bloc de mémoire des données, nous transvasons ces données vers le contrôle d'édition (la zone de saisie) en envoyant le message WM_SETTEXT au contrôle d'édition, avec lParam qui contient le pointer du bloc de mémoire. Après cet appel, le contrôle d'édition affiche les données dans son secteur client.

                    invoke CloseHandle,hFile
                    invoke GlobalUnlock,pMemory
                    invoke GlobalFree,hMemory
                .endif

À ce point, nous n'avons aucun besoin de maintenir le fichier ouvert plus longtemps puisque notre but est d'écrire dans un autre fichier (que le fichier original)les données visibles dans contrôle d'édition. Donc nous fermons le fichier en appelant CloseHandle avec l'Handle du fichier comme son paramètre. Ensuite nous ouvrons le bloc de mémoire et le libérons. En réalité vous ne devez pas libérer la mémoire dès maintenant, vous pourrez réutiliser plus tard le bloc de mémoire pendant l'opération de sauvegarde. Mais dans le but de notre démonstration, je tiens à la libérer ici.

                invoke SetFocus,hwndEdit

Quand la DialogBox (qui sert à ouvrir des fichiers) s'affiche à l'écran, le centre d'entrée passe de la fenêtre principale à la DialogBox en question pour que ce soit elle qui soit active. Ainsi quand on referme un fichier de la DialogBox (déjà ouvert), nous devons rendre le centre d'entrée à la zone de saisie (au contrôle d'édition), qui se trouve en arrière.
Ceci fini l'opération de lecture sur le fichier. À ce point, l'utilisateur peut écrire sur le contrôle d'édition. Et quand il veut sauvegarder les données dans un autre fichier, il doit choisir le sous-menu File/Save qui affiche une DialogBox pour les sauvegardes. La création de la boîte de dialogue de sauvegarde n'est pas très différente de la boîte de dialogue d'ouverture de fichiers. En fait, elles diffèrent seulement entre le nom des fonctions, GetOpenFileName et GetSaveFileName. Vous pouvez aussi réutiliser la plupart des membres de la structure ofn, sauf le membre Flag.

                mov ofn.Flags,OFN_LONGNAMES or\
                                OFN_EXPLORER or OFN_HIDEREADONLY

Dans notre cas, nous voulons créer un nouveau fichier, donc OFN_FILEMUSTEXIST et OFN_PATHMUSTEXIST doivent être omis sinon la boîte de dialogue ne nous laissera pas créer un fichier qui n'existe pas déjà.
Le paramètre dwCreationDistribution de la fonction CreateFile doit être changé en CREATE_NEW puisque nous voulons créer un nouveau fichier.
Le reste du code est identique à celui dans la partie 'fichiers à ouvrir' sauf la chose suivante :

                        invoke SendMessage,hwndEdit,WM_GETTEXT,MEMSIZE-1,pMemory
                        invoke WriteFile,hFile,pMemory,eax,ADDR SizeReadWrite,NULL

Nous envoyons le message WM_GETTEXT au contrôle d'édition pour celui-ci recopier les données (le texte) qu'il contient dans le bloc de mémoire que nous avions alloués, la valeur de retour dans eax est la longueur des données à l'intérieur du buffer. Après que les données soient dans le bloc de mémoire, nous les écrivons dans un nouveau fichier.


[Iczelion's Win32 Assembly HomePage]


Traduit par Morgatte