Уроки Iczelion'а

       

Память и файлы


Мы выучим основы менеджмента памяти и файловых операций ввода/вывода в этом Урок е. Также мы используем обычные диалоговые окна как устройства ввода/вывода.

Скачайте пример здесь.

Теория:

Менеджмент памяти под Win32 с точки зрения приложения достаточно прост и прямолинеен. Используемая модель памяти называется плоской моделью памяти. В этой модели все сегментные регистры (или селекторы) указывают на один и тот же стартовый адрес и смещение 32-битное, так что приложение может обратиться к любой точке памяти своего адресного пространства без необходимости изменять значения селекторов. Это очень упрощает управление памятью. Больше нет "дальних" и "ближних" указателей.

Под Win16 существует две основные категории функций API памяти: глобальные и локальные. Функции глобального типа взаимодействуют с памятью в других сегментах, поэтому они функции "дальней" памяти. Функции локального типа взаимодействуют с локальной кучей процессов, поэтому они функции "ближней" памяти.

Под Win32 оба этих типа идентичны. Используете ли вы GlobalAlloc или LocalAlloc, вы получите одинаковый результат.

  1. Выделите блок памяти с помощью вызова GlobalAlloc. Эта функция возвращает хэндл на запрошенный блок памяти.
  2. "Закройте" блок памяти, вызвав GlobalLock. Эта функция принимает хэндл на блок памяти и возвращает указатель на блок памяти.
  3. Вы можете использовать указатель, чтобы читать или писать в память.
  4. "Откройте" блок памяти с помощью вызова GlobalUnlock. Эта функция возвращает указатель на блок памяти.
  5. Освободите блок памяти с помощью GlobalFree. Эта функции принимает хэндл на блок памяти.

Вы также можете заменить "Global" на "Local", т.е. LocalAlloc, LocalLock и т.д.

Вышеуказанный метод может быть упрощен использованием флага GMEM_FIXED при вызове GlobalAlloc. Если вы используете этот флаг, возвращаемое значение от Global/LocalAlloc будет указателем на зарезервированный блок памяти, а не хэндл этого блока. Вам не надо будет вызывать Global/LocakLock вы сможете передать указатель Global/LocalFree без предварительного вызова Global/LocalUnlock. Hо в этом туториале я использую "традиционный" подход, так как вы можете столкнуться с ним при изучении исходников других программ.




Файловый ввод/ вывод по Win32 имеет значительное сходство с тем, как это делалось под DOS. Все требуемые шаги точно такие же. Вам только нужно изменить прерывания на вызовы API функций.

  1. Откройте или создайте файл функцией CreateFile. Эта функция очень универсальна: не считая файла, она может открывать компорты, пайпы, дисковые приводы и консоли. В случае успеха она возвращает хэндл файла или устройства. Затем вы можете использовать этот хэндл, чтобы выполнить определенные действия над файлом или устройством.

  2. Переместите файловый указатель в желаемое местоположение функцией SetFilePointer.

  3. Проведите операцию чтения или записи с помощью вызова ReadFile или WriteFile. Перед этим вы должны зарезервировать достаточно большой блок памяти для данных.

  4. Закройте файл с помощью CloseHandle. Эта функции принимает хэндл файла.

Содержание:
Приведенная ниже программа отображает открытый файловое диалоговое окно. Оно позволяет пользователю использовать текстовый файл, чтобы открыть и показать содержимое файла в клиентской области edit control'а. Пользователь может изменять текст в edit control'е по своему усмотрению, а затем может сохранить содержимое в файл.
.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 ; ID of the edit control
.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 ? ; Handle to the edit control
hFile HANDLE ? ; File handle hMemory HANDLE ? ; handle to the allocated memory block
pMemory DWORD ? ;pointer to the allocated memory block SizeReadWrite DWORD ? ; number of bytes actually read or write
.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
;============================================== ; Initialize the members of OPENFILENAME structure ;============================================== 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
Анализ:
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
В секции WM_CREATE мы создаем edit control. Отметьте, что параметры, которые определяют x, y, windth, height контрола равны нулю, поскольку мы изменим размер контрола позже, чтобы покрыть всю клиентскую область pодительского окна.


Заметьте, что в этом случае мы не должны вызывать ShowWindow, чтобы заставить появиться контрол на экране, так как мы указали стиль WS_VISIBLE. Вы можете использовать этот трюк и для родительского окна.
;============================================== ; Инициализируем структуру ;============================================== 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
После создания edit control'а edit control'а, мы используем это время, чтобы проинициализировать члены ofn. Так как мы хотим использовать ofn повторно в диалоговом окне, мы заполняем только общие члены, которые используются и GetOрenFileName и GetSaveFileName. Секция WM_CREATE - это прекрасное место для одноразовой инициализации.
.ELSEIF uMsg==WM_SIZE mov eax,lParam mov edx,eax shr edx,16 and eax,0ffffh invoke MoveWindow,hwndEdit,0,0,eax,edx,TRUE
Мы получаем сообщения WM_SIZE, когда pазмеp клиентской области нашего основного окна изменяется. Мы также получаем его, когда окно создается. Для того, чтобы получать это сообщение, стили класса окна должны включать CS_REDRAW и CS_HREDRAW. Мы используем эту возможность для того, чтобы сделать pазмеp нашего edit control'а pавным клиентской области окна. Для начала мы должны узнать текущую ширину и высоту клиентской области родительского окна. Мы получаем эту информацию из lParam. Верхнее слово lParam содержит высоту, а нижнее слово - ширину клиентской области. Затем мы используем эту информацию для того, чтобы изменить размер edit control'а с помощью вызова функции MoveWindow, которая может изменять позицию и размер окна на экране.
.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
Когда пользователь выбирает пункт меню File/Oрen, мы заполняем в структуре параметр Flags и вызываем функцию GetOрenFileName, чтобы отобразить окно открытия файла.


.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
После того, как пользователь выберет файл для открытия, мы вызываем CreateFile, чтобы открыть файл. Мы указываем, что функция должна попробовать открыть файл для чтения и записи. После того, как файл открыт, функция возвращает хэндл на открытый файл, который мы сохраняем в глобальной переменной для будущего использования. Эта функция имеет следующий синтаксис:
CreateFile proto lpFileName:DWORD,\ dwDesiredAccess:DWORD,\ dwShareMode:DWORD,\ lpSecurityAttributes:DWORD,\ dwCreationDistribution:DWORD\, dwFlagsAndAttributes:DWORD\, hTemplateFile:DWORD
  • dwDesireAccess указывает, какую операцию вы хотите выполнить над файлом.

    • Открыть файл для проверки его атрибутов. Вы можете писать и читать из файла.
    • GENERIC_READ Открыть файл для чтения.
    • GENERIC_WRITE Открыть файл для записи.

  • dwShareMode указывает, какие операции вы хотите позволить выполнять вашим процессам над открытыми файлами.

    • 0 Hе разделять файл с другими процессами.

    • FILE_SHARE_READ позволяет другим процессам прочитать информацию из файла, который был открыт

    • FILE_SHARE_WRITE позволяет другим процессам записывать информацию в открытый файл.

    • lpSecurityAttributes не имеет значения под Windows 95.

    • dwCreationDistribution указывает действие, которое будет выполнено над файлом при его открытии.

      • CREATE_NEW Создание нового файла, если файла не существует.

      • CREATE_ALWAYS Создание нового файла. Функция перезаписывает файл, если он существует.

      • OPEN_EXISTING Окрытие существующего файла.

      • OPEN_ALWAYS Открытие файла, если он существует, в противном случае, функция создает новый файл.

      • TRUNCATE_EXISTING Открытие файла и обрезание его до нуля байтов. Вызывающий функцию процесс должен открывать файл, по крайней мере, с доступом GENERIC_WRITE. Если файл не существует, функция не срабатывает.

      • dwFlagsAndAttributes указывает атрибуты файла

        • FILE_ATTRIBUTE_ARCHIVE Файл является архивным файлом. Приложения используют этот атрибут для бэкапа или удаления.



        • FILE_ATTRIBUTE_COMPRESSED Файл или директория сжаты. Для файла это означает, что вся информация в файле заархивирована. Для директории это означает, что сжатие подразумевается по умолчанию для создаваемых вновь файлов и поддиректорий.

        • FILE_ATTRIBUTE_NORMAL У файла нет других атрибутов. Этот атрибут действителен, только если исопльзуется один.

        • FILE_ATTRIBUTE_HIDDEN Файл спрятан. Он не включается в обычные листинги директорий.

        • FILE_ATTRIBUTE_READONLY Файл только для чтения. Пpиложения могут читать из файла, но не могут писать в него или удалить его.

        • FILE_ATTRIBUTE_SYSTEM Файл - часть операционной системы или используется только ей.
          invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE mov hMemory,eax
          invoke GlobalLock,hMemory mov pMemory,eax
          Когда файл открыт, мы резервирует блок память для использования функциями ReadFile и WriteFile. Мы указываем флаг GMEM_MOVEABLE, чтобы позволить Windows перемещать блок памяти, чтобы уплотнять последнюю.
          Когда GlobalAlloc возвращает положительный результат, eax содержит хэндл зарезервированного блока памяти. Мы передаем этот хэндл функции GlobalLock, который возвращает указатель на блок памяти.
          invoke ReadFile,hFile,pMemory,MEMSIZE-1,ADDR SizeReadWrite,NULL invoke SendMessage,hwndEdit,WM_SETTEXT,NULL,pMemory
          Когда блок памяти готов к использованию, мы вызываем функцию ReadFile для чтения данных из файла. Когда файл только что открыт или создан, указатель на смещение pавен нулю. В этом случае, мы начинаем чтение с первого байта. Первый параметр ReadFile - это хэндл файла, из которого необходимо произвести чтение, второй - это указатель на блок памяти, затем - количество байтов, которое нужно считать из файла, четвертый параметр - это адрес переменной размера DWORD, который будет заполнен количеством байтов, в pеальности считанных из файла.
          После заполнения блока памяти данными, мы помещаем данные в edit control, посылая сообщение WM_SETTEXT контролу, причем lParam содержит указатель на блок памяти. После этого вызова edit control отображает данные в его клиентской области.


          invoke CloseHandle,hFile invoke GlobalUnlock,pMemory invoke GlobalFree,hMemory .endif
          В этой месте у нас нет необходимости держать файл открытым, так как нашей целью является запись модифицированных данных из edit control'а в другой файл, а не в оригинальный. Поэтому мы закрываем файл функцией CloseHandle, передав ей в качестве параметра хэндл файла. Затем мы открываем блок памяти и освобождаем его. В действительности, вам не нужно освобождать ее сейчас, вы можете использовать этот же блок во время операции сохранения. Hо в демонстрационных целях я освобождаю ее сейчас.
          invoke SetFocus,hwndEdit
          Когда на экране отображается окно открытия файла, фокус ввода сдвигается на него. Поэтому, когда это окно закрывается, мы должны передвинуть фокус ввода обратно на edit control.
          Это заканчивает операцию чтения из файла. В этом месте пользователь должен отредактировать содержимое edit control'а. И когда он хочет сохранить данные в другой файла, он должен выбрать File/Save, после чего отобразиться диалоговое окно. Создание окна сохранения файла не слишком отличается от создание окна открытия файла. Фактически, они отличаются только именем функций. Вы можете снова использовать большинство из параметров структуры ofn, кроме параметра Flags.
          mov ofn.Flags,OFN_LONGNAMES or\ OFN_EXPLORER or OFN_HIDEREADONLY
          В нашем случае, мы хотим создать новый файл, так чтобы OFN_FILEMUSTEXIST и OFN_PATHMUSTEXIST должны быть убраны, иначе диалоговое окно не позволит нам создать файл, который уже не существует.
          Параметр dwCreationDistribution функции CreateFile должен быть установлен в CREATE_NEW, так как мы хотим создать новый файл.

          Оставшийся код практически одинаков с тем, что используется при создании окна открытия файла, за исключением следующего:
          invoke SendMessage,hwndEdit,WM_GETTEXT,MEMSIZE-1,pMemory invoke WriteFile,hFile,pMemory,eax,ADDR SizeReadWrite,NULL
          Мы посылаем сообщение WM_GETTEXT edit control'у, чтобы скопировать данные из него в блок памяти, возвращаемое значение в eax - это длина данных внутри буфера. После того, как данные оказываются в блоке памяти, мы записываем их в новый файл.
          [C] Iczelion, пер. Aquila.

          Содержание раздела