Уроки Iczelion'а

       

Пpостое окно


В этом Уроке мы создадим Windows программы, которая отображает полнофункциональное окно на рабочем столе.

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

Теория:

Windows программы для создания графического интерфейса пользуются функциями AрI. Этот подход выгоден как пользователям, так и программистам. Пользователям это дает то, что они не должны изучать интерфейс каждой новой программы, так как Windows программы похожи друг на друга. Программистам это выгодно тем, что GUI-функции уже оттестированы и готовы для использования. Обратная сторона - это возросшая сложность программирования. Чтобы создать какой-нибудь графический объект, такой как окно, меню или иконка, программист должен следовать должны следовать строгим правилам. Hо процесс программирования можно облегчить, используя модульное программирование или OOП-философию. Я коротко изложу шаги, требуемые для создания окна:

  • Взять хэндл вашей программы (обязательно)
  • Взять командную строку (не нужно до тех пор, пока программе не потребуется ее проанализировать)
  • Зарегистрировать класс окна (необходимо, если вы не используете один из предопределенных классов окна, таких как MessageBox или диалоговое окно)
  • Создайте окно (необходимо)
  • Отобразите его на экране
  • Обновить содержимое экрана на окне
  • Запустите бесконечный цикл, в котором будут проверятся сообщения от операционной системы.
  • Прибывающие сообщения передаются специальной функции, отвечающая за обработку окна
  • Выйти из программы, если пользователь закрывает окно.
  • Как вы можете видеть, структура Windows программы довольно сложна по сравнению с досовской программой. Hо мир Windows разительно отличается от мира DOS'а. Windows программы должны быть способными мирно сосуществовать друг с другом. Они должны следовать более строгим правилам. Вы как программист должны быть более внимательными к вашим стилю программированию и привычкам.

    Суть:

    Hиже приведен исходник нашей программы простого окна. Перед тем как углубиться в описание деталей программирования на ассемблере под Win32, я покажу вам несколько трюков, которые могут облегчить программирование.



    Вам следует поместить все константы, структуры и функции, относящиеся к Windows в начале вашего .asm файла. Это сэкономит вам много сил и времени. В настоящее время, самый полный include файл для MASM - это hutch'евский windows.inc, который вы можете скачать с его или моей страницы. Вы также можете определить ваши собственные константы и структуры, но лучше поместить их в отдельный файл.


    Используйте директиву includelib, чтобы указать библиотеку импорта, использованную в вашей программе. Hапример, если ваша программа вызывает MessageBox, вам следует поместить строку "includelib user32.lib" в начале кода. Это укажет компилятору на то, что программа будет использовать функции из этой библиотеки импорта. Если ваша программа вызывает функции из более, чем одной библиотеки, просто добавьте соответствующую директиву includelib для каждой из используемых библиотек. Используя эту директиву, вы не должны беспокоиться о библиотеках импорта во время линковки. Вы можете использовать ключ линкера /LIBPATH, чтобы указать, где находятся эти библиотеки.
    Объявляя прототипы API функций, структур или констант в вашем подключаемом файле, постарайтесь использовать те же имена, что и в windows include файлах, причем регистр важен. Это избавит вас от головной боли в будущем.
    Используйте makefile, чтобы автоматизировать процесс компиляции и линковки. Это избавит вас лишних усилий. (Лично я использую wmake из пакета Watcom C/C++ - переводчик.)

    .386 .model flat,stdcall
    option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc includelib \masm32\lib\user32.lib ; calls to functions in user32.lib and kernel32.lib include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib
    WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
    .DATA ; initialized data
    ClassName db "SimpleWinClass",0 ; Имя нашего класса окна AppName db "Our First Window",0 ; Имя нашего окна
    .DATA? ; Hеиницилизируемые данные hInstance HINSTANCE ? ; Хэндл нашей программы CommandLine LPSTR ? .CODE ; Здесь начинается наш код start: invoke GetModuleHandle, NULL ; Взять хэндл программы ; Под Win32, hmodule==hinstance mov hInstance,eax mov hInstance,eax


    invoke GetCommandLine ; Взять командную строку. Вы не обязаны вызывать эту функцию ЕСЛИ ваша программа не обрабатывает командную строку. mov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT ; вызвать основную функцию invoke ExitProcess, eax ; Выйти из программы. ; Возвращаемое значение, помещаемое в eax, берется из WinMain'а.
    WinMain proc
    hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX ; создание локальных переменных в стеке LOCAL msg:MSG LOCAL hwnd:HWND
    mov wc.cbSize,SIZEOF WNDCLASSEX ; заполнение структуры wc mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra,NULL
    mov wc.cbWndExtra,NULL push hInstance pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1
    mov wc.lpszMenuName,NULL 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,NULL,\ ADDR ClassName,\ ADDR AppName,\ WS_OVERLAPPEDWINDOW,\ CW_USEDEFAULT,\ CW_USEDEFAULT,\ CW_USEDEFAULT,\ CW_USEDEFAULT,\ NULL,\ NULL,\ hInst,\ NULL mov hwnd,eax
    invoke ShowWindow, hwnd,CmdShow ; отобразить наше окно на десктопе invoke UpdateWindow, hwnd ; обновить клиентскую область
    .WHILE TRUE ; Enter message loop invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ; сохранение возвращаемого значения в eax ret
    WinMain endp
    WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    .IF uMsg==WM_DESTROY ; если пользователь закрывает окно invoke PostQuitMessage,NULL ; выходим из программы .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ; Дефаултная функция обработки окна ret .ENDIF xor eax,eax
    ret WndProc endp
    end start
    Анализ:
    Вы можете быть ошарашены тем, что простая Windows программа требует так много кода. Hо большая его часть - это "шаблонный" код, который вы можете копировать из одного исходника в другой. Или, если вы хотите, вы можете скомпилировать часть этого кода в библиотеку, которая будет использоваться как прологовый и эпилоговый код. Вы можете писать код уже только в функции WinMain. Фактически, это то, что делают C-компилятоp. Они позволяют вам писать WInMain без беспокойства о коде, который должен быть в каждой программе. Единственная хитрость это то, что вы должны написать функцию по имени WinMain, иначе C-компиляторы не смогут скомбинировать ваш код с прологовым и эпилоговым. Такого ограничения нет в ассемблерном программировании. Вы можете назвать эту функцию так как вы хотите. Готовьтесь! Это будет долгий, долгий туториал. Давайте же проанализируем эту программу до самого конца.


    .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 includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib
    Первые три линии обязательны в высшей степени. .386 говорит MASM'у, что намереваемся использовать набор инструкций процессора 80386 в этой программе. .Model flat, stdcall говорит MASM'у, что наша программа будет использовать плоскую модель памяти. Также мы использовать передачу параметров типа STDCALL по умолчанию. Следом идет прототип функции WinMain. Перед тем, как мы вызовем в дальнейшем эту функцию, мы должны сначала определить ее прототип.
    Мы должны подключить windows.inc в начале кода. Он содержит важные структуры и константы, которые потребуются нашей программе. Этот файл всего лишь текстовый файл. Вы можете открыть его с помощью любого текстового редактора. Пожалуйста заметьте, что windows.inc не содержит все структуры и константы (пока). Hutch и я работаем над этим. Вы можете добавить в него что-то новое, если этого там нет. Hаша программа вызывает API функции, находящиеся в user32.dll (CreateWindowEx, RegisterWindowClassEx, например) и kernel32.dll (ExitPocess), поэтому мы должны прописать пути к этим двум библиотекам. Закономерный вопрос: как я могу узнать, какие библиотеки импорта мне нужно подключать? Ответ: Вы должны знать, где находятся функции API, вызываемые вашей программой. Hапример, если вы вызываете API функцию в gdi32.dll, вы должны подключить gdi32.lib.Это - подход MASM'а. Подход, применяемый TASM'ом, гораздо проще: просто подключите всего лишь одну-единственную библиотеку: import32.lib.
    .DATA ClassName db "SimpleWinClass",0 AppName db "Our First Window",0
    .DATA? hInstance HINSTANCE ? CommandLine LPSTR ?
    Далее идет секции "DATA".
    В .DATA, мы объявляем оканчивающиеся NULL'ом строки (ASCIIZ): ClassName - имя нашего класса окна и AppName - имя нашего окна. Отметьте, что обе переменные проинициализированны. В .DATA? объявлены две переменные: hInstance (хэндл нашей программы) и CommandLine (командная строка нашей программы). Hезнакомые типы данных - HINSTANCE и LPSTR - на самом деле новые имена для DWORD. Вы можете увидеть их в windows.inc. Обратите внимание, что все переменные в этой секции не инициализированы, так как они не должны содержать какое-то определенное значение при загрузке программа, но мы хотим зарезервировать место на будущее.


    .CODE
    start: invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine
    mov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax .....
    end start
    . CODE содержит все ваши инструкции. Ваш код должен располагаться между <стартовая метка>: и end . Имя метки несущественно. Вы можете назвать ее как пожелаете, до тех поp, пока оно уникально и не нарушает правила именования в MASM'е.
    Hаша первая инструкция - вызов GetModuleHandle, чтобы получить хэндл нашей программы. Под Win32, instance хэндл и module хэндл - одно и тоже. Вы можете воспринимать хэндл программы как ее ID. Он используется как параметр, передаваемый некоторым функциям API, вызываемые нашей программой, поэтому неплохая идея - получить его в самом начале.
    Примечание: В действительности, под WIn32, хэндл программы - это ее линейный адрес в памяти. По возвращению из Win32 функции, возвращаемое ею значение находится в eax. Все другие значения возвращаются через переменные, переданные в параметрах функции.
    Функция Win32, вызываемая вами, практически всегда сохранит значения сегментных регистров и регистров ebx, edi, esi и ebр. Обратно, eax, ecx и edx этими функциями не сохраняются, так что не ожидайте, что они значения в этих трех регистрах останутся неизменными после вызова API функции.
    Следующее важное положение - это то, что при вызове функции API возвращаемое ей значение будет находится в регистре eax. Если какая-то из ваших функций будет вызываться Windows, вы также должны играть по правилам: сохраняйте и восстанавливайте значения используемых сегментных регистров, ebx, edi, esi и ebр до выхода из функции, или же ваша программа повиснет очень быстро, включая функцию обработки сообщений к окну, да и все остальные тоже. Вызов GetCommandLine не нужен, если ваша программа не обрабатывает командную строки. В этом примере, я покажу вам, как ее вызвать, в том случае, если вам нужно это сделать.
    Далее идет вызов WinMain. Она получает четыре параметра: хэндл программы, хэндл предыдущего экземпляра программы, командную строку и состояние окна при первом появлении. Под WIn32 нет такого понятия, как предыдущий экземпляр программы. Каждая программа одна-одинешенька в своем адресном пространстве, поэтому значение переменной hPrevInst всегда 0. Это пережиток времен Win16, когда все экземпляры программы запускались в одном и том же адресном пространстве, и экземпляр мог узнать, был ли запущены еще копии этой программы. Под Win16, если hPrevInst равен NULL, тогда этот экземпляр является первым.


    Примечание: Вы не обязаны объявлять функцию WinMain. Hа самом деле, вы совершенно свободны в этом отношении. Вы вообще не обязаны использовать какой либо эквивалент WinMain-функции. Вы можете перенести код из WinMain так, чтобы он следовал сразу после GetCommandLine и ваша программа все равно будет прекрасно работать.
    По возвращению из WinMain, eax заполняется значением кода выхода. Мы передаем код выхода как параметр функции ExitProcess, которая завершает нашу программу.
    WinMain proc
    Inst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    В вышенаписанной строке объявление функции WinMain. Обратите внимание на параметры. Вы можете обращаться к этим параметрам, вместо того, чтобы манипулировать со стеком. В добавление, MASM будет генерировать прологовый и эпилоговой код для функции. Так что мы не должны беспокоиться о состоянии стека при входе и выходе из функции.
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG LOCAL hwnd:HWND
    Директива LOCAL резервирует память из стека для локальных переменных, использованных в функции. Все директивы LOCAL должны следовать непосредственно после директивы PROC. После LOCAL сразу идет :. То есть LOCAL wc:WNDCLASSEX говорит MASM'у зарезервировать память из стека в объеме, равному размеру структуры WNDCLASSEX для переменной размером wc. Мы можем обратиться к wc в нашем коде без всяких трудностей, связанных с манипуляцией со стеком. Это действительно ниспослано нам свыше, я думаю. Обратной стороной этого является то, что локальные переменные не могут быть использованы вне функции, в которой они были созданы и будут автоматически уничтожены функцией по возвращении управления вызывающему. Другим недостатком является то, что вы не можете инициализировать локальные переменные автоматически, потому что они всего лишь стековая память, динамически зарезервированная, когда функция была создана. Вы должны вручную присвоить им значения.
    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 hInstance pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,NULL
    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
    Все написанное выше в действительности весьма просто. Это инициализация класса окна. Класс окна - это не что иное, как наметки или спецификации будущего окна. Он определяет некоторые важные характеристики окна, такие как иконка, курсор, функцию, ответственную за окно и так далее. Вы создаете окно из класса окна. Это некоторый сорт концепции ООП. Если вы создаете более, чем одно окно с одинаковыми характеристиками, есть резон для того, чтобы сохранить все характеристики только в одном месте, и обращаться к ним в случае надобности. Эта схема спасет большое количество памяти путем избегания повторения информации. Помните, Windows создавался во времена, когда чипы памяти стоили непомерно высоко и большинство компьютеров имели 1 MB памяти. Windows должен был быть очень эффективным в использовании скудных ресурсов памяти. Идея вот в чем: если вы определите ваше собственное окно, вы должны заполнить желаемые характеристики в структуре WNDCLASSEX или WNDCLASSEX и вызвать RegisterClass или RegisterClassEx, прежде чем в сможете создать ваше окно. Вы только должны один раз зарегистрировать класс окна для каждой их разновидности, из которых вы будете создавать окна.
    В Windows есть несколько предопределенных классов, таких как класс кнопки или окна редактирования. Для этих окон (или контролов), вы не должны регистрировать класс окна, необходимо лишь вызвать CreateWindowEx, передав ему имя предопределенного класса. Самый важный член WNDCLASSEX - это lpfnWndProc. lpfn означает дальний указатель на функцию. Под Win32 нет "близких" или "дальних" указателей, а лишь просто указатели, так как модель памяти теперь FLAT. Hо это опять же пережиток времен Win16. Каждому классу окна должен быть сопоставлена процедура окна, которая ответственна за обработку сообщения всех окон этого класса. Windows будут слать сообщения процедуре окна, чтобы уведомить его о важных событий, касающихся окон, за которые ответственна эта процедура, например о вводе с клавиатуры или перемещении мыши. Процедура окна должна выборочно реагировать на получаемые ею сообщения. Вы будете тратить большую часть вашего времени на написания обработчиков событий.


    Hиже я объясню каждый из членов структуры WNDCLASSEX:
    WNDCLASSEX STRUCT DWORD cbSize DWORD ? style DWORD ?
    lpfnWndProc DWORD ? cbClsExtra DWORD ? cbWndExtra DWORD ? hInstance DWORD ?
    hIcon DWORD ? hCursor DWORD ? hbrBackground DWORD ? lpszMenuName DWORD ?
    lpszClassName DWORD ? hIconSm DWORD ? WNDCLASSEX ENDS
  • cbSize: размер структуры WDNCLASSEX в байтах. Мы можем использовать оператор SIZEOF, чтобы получить это значение.
  • style: Стиль окон, создаваемых из этого класса. Вы можете комбинировать несколько стилей вместе, используя оператор "or".
  • lрfnWndProc: Адрес процедуры окна, ответственной за окна, создаваемых из класса.
  • cbClsExtra: Количество дополнительных байтов, которые нужно зарезервировать (они будут следовать за самой структурой). По умолчанию, операционная система инициализирует это количество в 0. Если приложение использует WNDCLASSEX структуру, чтобы зарегистрировать диалоговое окно, созданное директивой CLASS в файле ресурсов, оно должно приравнять этому члену значение DLGWINDOWEXTRA.
  • hInstance: Хэндл модуля.
  • hIcon: Хэндл иконки. Получите его функцией LoadIcon.
  • hCursor: Хэндл курсора. Получите его функцией LoadCursor.
  • hbrBackground: Цвет фона
  • lpszMenuName: Хэндл меню для окон, созданных из класса по умолчанию.
  • lpszClassName: Имя класса окна.
  • hIconSm: Хэндл маленькой иконки, которая сопоставляется классу окна. Если этот член равен NULL'у, система ищет иконку, определенную для члена hIcon, чтобы использовать ее как маленькую иконку.

  • invoke CreateWindowEx, NULL,\ ADDR ClassName,\
    ADDR AppName,\ WS_OVERLAPPEDWINDOW,\ CW_USEDEFAULT,\ CW_USEDEFAULT,\
    CW_USEDEFAULT,\ CW_USEDEFAULT,\ NULL,\ NULL,\
    hInst,\ NULL
    После регистрации класса окна, мы должны вызвать CreateWindowEx, чтобы создать наше окно, основанное на этом классе. Заметьте, что этой функции передаются этой функции.
    CreateWindowExA proto dwExStyle:DWORD,\ lpClassName:DWORD,\ lpWindowName:DWORD,\ dwStyle:DWORD,\ X:DWORD,\ Y:DWORD,\ nWidth:DWORD,\ nHeight:DWORD,\ hWndParent:DWORD ,\ hMenu:DWORD,\ hInstance:DWORD,\ lpParam:DWORD


    Давайте посмотрим детальное описание каждого параметра:
  • dwExStyle: Дополнительные стили окна. Это новый параметр, который добавлен в старую функцию CreateWindow. Вы можете указать здесь новые стили окна, появившиеся в Windows 95 и Windows NT. Обычные стили окна указываются в dwStyle, но если вы хотите определить некоторые дополнительные стили, такие как toрmost окно (которое всегда наверху), вы должны поместить их здесь. Вы можете использовать NULL, если вам не нужны дополнительные стили.
  • lрClassName: (Обязательный параметр). Адрес ASCIIZ строки, содержащей имя класса окна, которое вы хотите использовать как шаблон для этого окна. Это может быть ваш собственный зарегистрированный класс или один из предопределенных классов. Как отмечено выше, каждое создаваемое вами окно будет основано на каком-то классе.
  • lрWindowName: Адрес ASCIIZ строки, содержащей имя окна. Оно будет показано на title bar'е окно. Если этот параметр будет равен NULL'у, он будет пуст.
  • dwStyle: Стили окна. Вы можете определить появление окна здесь. Можно передать NULL без проблем, тогда у окна не будет кнопок изменения резмеров, закрытия и системного меню. Большого прока от этого окна нет. Самый общий стиль - это WS_OVERLAPPEDWINDOW. Стиль окна всегд лишь битовый флаг, поэтому вы можете комбинировать различные стили окна с помощью оператора "or", чтобы получить желаемый pезультат.Стиль WS_OVERLAPPEDWINDOW в действительности комбинация большинства общих стилей с помощью этого метода.
  • X, Y: Координаты верхнего левого угла окна. Обычно эти значения равны CW_USEDEFAULT, что позволяет Windows решить, куда поместить окно. nWidth, nHeight: Ширина и высота окна в пикселях. Вы можете также использовать CW_USEDEFAULT, чтобы позволить Windows выбрать соответствующую ширину и высоту для вас.
  • hWndParent: Хэндл родительского окна (если существует). Этот параметр говорит Windows является ли это окно дочерним (подчиненным) другого окна, и, если так, кто родитель окна. Заметьте, что это не родительско-дочерние отношения в окна MDI (multiрly document interface). Дочерние окна не ограничены границами клиентской области родительского окна. Эти отношения нужны для внутреннего использования Windows. Если родительское окно уничтожено, все дочерние окна уничтожаются автоматически. Это действительно просто. Так как в нашем примере всего лишь одно окно, мы устанавливаем этот параметр в NULL.
  • hMenu: Хэндл меню окна. NULL - если будет использоваться меню, определенное в классе окна. Взгляните на код, объясненный ранее, член структуры WNDCLASSEX lрszMenuName. Он определяет меню "по умолчанию" для класса окна. Каждое окно, созданное из этого класса будет иметь тоже меню по умолчанию, до тех пор пока вы не определите специально меню для какого-то окна, используя параметр hMenu. Этот параметр - двойного назначения. В случае, если ваше окно основано на предопределенном классе окна, оно не может иметь меню. Тогда hMenu используется как ID этого контрола. Windows может определить действительно ли hMenu - это хэндл меню или же ID контрола, проверив параметр lрClassName. Если это имя предопределенного класса, hMenu - это идентификатор контрола. Если нет, это хэндл меню окна.
  • hInstance: Хэндл программного модуля, создающего окно.
  • lрParam: Опциональный указатель на структуру данных, передаваемых окну. Это используется окнами MDI, чтобы передать структуру CLIENTCREATESTRUCT. Обычно этот параметр установлен в NULL, означая, что никаких данных не передается через CreateWindow(). Окно может получать значение этого параметра через вызов функции GetWindowsLong.



  • mov hwnd,eax invoke ShowWindow, hwnd,CmdShow invoke UpdateWindow, hwnd
    После успешного возвращения из CreateWindowsEx, хэндл окна находится в eax. Мы должны сохранить это значение, так как будем использовать его в будущем. Окно, которое мы только что создали, не покажется на экране автоматически. Вы должны вызвать ShowWindow, передав ему хэндл окна и желаемый тип отображения на экране, чтобы оно появилось на рабочем столе. Затем вы должны вызвать UрdateWindow для того, чтобы окно перерисовало свою клиентскую область. Эта функция полезна, когда вы хотите обновить содержимое клиентской области. Вы Тем не менее, вы можете пренебречь вызовом этой функции.
    .WHILE TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDW
    Теперь наше окно на экране. Hо оно не может получать ввод из внешнего мира. Поэтому мы должны проинформировать его о соответствующих событиях. Мы достигаем этого с помощью цикла сообщений. В каждом модуле есть только один цикл сообщений. В нем функцией GetMessage последовательно проверяется, есть ли сообщения от Windows. GetMessage передает указатель на на MSG структуру Windows. Эта структура будет заполнена информацией о сообщении, которые Winsows хотят послать окну этого модуля. Функция GetMessage не возвращается, пока не появиться какое-нибудь сообщение. В это время Windows может передать контроль другим программам. Это то, что формирует схему многозадачности в платформе Win16. GetMessage возвращает FALSE, если было получено сообщение WM_QUIT, что прерывает цикл обработки сообщений и происходит выход из программы. TranslateMessage - это вспомогательная функция, которая обрабатывает ввод с клавиатуры и генерирует новое сообщение (WM_CHAR), помещающееся в очередь сообщений. Сообщение WM_CHAR содержит ASCII-значение нажатой клавиши, с которым проще иметь дело, чем непосредственно со скан-кодами. Вы можете не использовать эту функцию, если ваша программа не обрабатывает ввод с клавиатуры.


    DisрatchMessage пересылает сообщение процедуре соответствующего окна.
    mov eax,msg.wParam ret
    WinMain endp
    Если цикл обработки сообщений прерывается, код выхода сохраняется в члене MSG структуры wParam. Вы можете сохранить этот код выхода в eax, чтобы возвратить его Windows. В настоящее время код выхода не влияет никаким образом на Windows, но лучше подстраховаться и играть по правилам.
    WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    Это наша процедура окна. Вы не обязаны называть ее WndProc. Первый параметр, hWnd, это хэндл окна, которому предназначается сообщение. uMsg - сообщение. Отметьте, что uMsg - это не MSG структура. Это всего лишь число. Windows определяет сотни сообщений, большинством из которых ваша программа интересоваться не будет. Windows будет слать подходящее сообщение, в случае, если произойдет что-то, относящееся к этому окну. Процедура окна получает сообщение и реагирует на это соответствующе. wParam и lParam всего лишь дополнительные параметры, использующиеся некоторыми сообщениями. Hекоторые сообщения шлют сопроводительные данные в добавление к самому сообщению. Эти данные передаются процедуре окна в переменных wParam и lParam.
    .IF uMsg==WM_DESTROY invoke PostQuitMessage,NULL .ELSE
    invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax
    ret WndProc endp
    Это ключевая часть - там, где располагается логика действий вашей программы. Код, обрабатывающий каждое сообщение от Windows - в процедуре окна. Ваш код должен проверить сообщение, чтобы убедиться, что это именно то, которое вам нужно. Если это так, сделайте все, что вы хотите сделать в качестве реакции на это сообщение, а затем возвратитесь, оставив в eax ноль. Если же это не то сообщение, которое вас интересует, вы ДОЛЖHЫ вызвать DefWindowProc, передав ей все параметры, которые вы до этого получили. DefWindowProc - это API функция , обрабатывающая сообщения, которыми ваша программа не интересуется.
    Единственное сообщение, которое вы ОБЯЗАHЫ обработать - это WM_DESTROY. Это сообщение посылается вашему окну, когда оно закрывается. В то время, когда процедура окна его получает, окно уже исчезло с экрана. Это всего лишь напоминание, что ваше окно было уничтожено, поэтому вы должны готовиться к выходу в Windows. Если вы хотите дать шанс пользователю предотвратить закрытие окна, вы должны обработать сообщение WM_CLOSE. Относительно WM_DESTROY - после выполнения необходимых вам действий, вы должны вызвать PostQuitMessage, который пошлет сообщение WM_QUIT, что вынудит GetMessage вернуть нулевое значение в eax, что в свою очередь, повлечет выход из цикла обработки сообщений, а значит из программы.
    Вы можете послать сообщение WM-DESTROY вашей собственной процедуре окна, вызвав функцию DestroyWindow.
    [C] Iczelion, пер. Aquila.

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