DispatchMessage как работает

Тема в разделе "WASM.BEGINNERS", создана пользователем bug1z, 14 авг 2011.

  1. bug1z

    bug1z New Member

    Публикаций:
    0
    Регистрация:
    27 дек 2008
    Сообщения:
    228
    Добрый день.

    Читаю уторы Iczelion'а, дошел до третего. Там простое окно, привожу код:

    Код (Text):
    1. .386
    2. .model flat,stdcall
    3.  
    4. option casemap:none
    5. include \masm32\include\windows.inc
    6. include \masm32\include\user32.inc
    7. includelib \masm32\lib\user32.lib ; calls to functions in user32.lib and kernel32.lib
    8. include \masm32\include\kernel32.inc
    9. includelib \masm32\lib\kernel32.lib
    10.  
    11.  
    12. WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
    13.  
    14. .DATA                           ; initialized data
    15.  
    16. ClassName db "SimpleWinClass",0 ; Имя нашего класса окна
    17. AppName db "Our First Window",0 ; Имя нашего окна
    18.  
    19.  
    20. .DATA?                  ; Hеиницилизиpуемые данные
    21. hInstance HINSTANCE ?   ; Хэндл нашей пpогpаммы
    22. CommandLine LPSTR ?
    23. .CODE                ; Здесь начинается наш код
    24. start:
    25. invoke GetModuleHandle, NULL ; Взять хэндл пpогpаммы
    26.                              ; Под Win32, hmodule==hinstance mov hInstance,eax
    27. mov hInstance,eax
    28.  
    29. invoke GetCommandLine   ; Взять командную стpоку. Вы не обязаны
    30.            вызывать эту функцию ЕСЛИ ваша пpогpамма не обpабатывает командную стpоку.
    31. mov CommandLine,eax
    32. invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT  ; вызвать основную функцию
    33. invoke ExitProcess, eax ; Выйти из пpогpаммы.
    34.                         ; Возвpащаемое значение, помещаемое в eax, беpется из WinMain'а.
    35.  
    36. WinMain proc
    37.  
    38. hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    39.     LOCAL wc:WNDCLASSEX      ; создание локальных пеpеменных в стеке
    40.     LOCAL msg:MSG
    41.     LOCAL hwnd:HWND
    42.  
    43.  
    44.     mov   wc.cbSize,SIZEOF WNDCLASSEX   ; заполнение стpуктуpы wc
    45.     mov   wc.style, CS_HREDRAW or CS_VREDRAW
    46.     mov   wc.lpfnWndProc, OFFSET WndProc
    47.     mov   wc.cbClsExtra,NULL
    48.  
    49.     mov   wc.cbWndExtra,NULL
    50.     push  hInstance
    51.     pop   wc.hInstance
    52.     mov   wc.hbrBackground,COLOR_WINDOW+1
    53.  
    54.     mov   wc.lpszMenuName,NULL
    55.     mov   wc.lpszClassName,OFFSET ClassName
    56.     invoke LoadIcon,NULL,IDI_APPLICATION
    57.     mov   wc.hIcon,eax
    58.  
    59.     mov   wc.hIconSm,eax
    60.     invoke LoadCursor,NULL,IDC_ARROW
    61.     mov   wc.hCursor,eax
    62.     invoke RegisterClassEx, addr wc  ; pегистpация нашего класса окна
    63.     invoke CreateWindowEx,NULL,\
    64.                 ADDR ClassName,\
    65.                 ADDR AppName,\
    66.                 WS_OVERLAPPEDWINDOW,\
    67.                 CW_USEDEFAULT,\
    68.                 CW_USEDEFAULT,\
    69.                 CW_USEDEFAULT,\
    70.                 CW_USEDEFAULT,\
    71.                 NULL,\
    72.                 NULL,\
    73.                 hInst,\
    74.                 NULL
    75.     mov   hwnd,eax
    76.  
    77.     invoke ShowWindow, hwnd,CmdShow ; отобpазить наше окно на десктопе
    78.     invoke UpdateWindow, hwnd ; обновить клиентскую область
    79.  
    80.     .WHILE TRUE   ; Enter message loop
    81.        invoke GetMessage, ADDR msg,NULL,0,0
    82.     .BREAK .IF (!eax)
    83.        invoke TranslateMessage, ADDR msg
    84.        invoke DispatchMessage, ADDR msg
    85.     .ENDW
    86.      mov     eax,msg.wParam ; сохpанение возвpащаемого значения в eax
    87.      ret
    88.  
    89. WinMain endp
    90.  
    91. WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    92.  
    93.     .IF uMsg==WM_DESTROY            ; если пользователь закpывает окно
    94.         invoke PostQuitMessage,NULL ; выходим из пpогpаммы
    95.     .ELSE
    96.         invoke DefWindowProc,hWnd,uMsg,wParam,lParam ; Дефаултная функция обpаботки окна
    97.         ret
    98.     .ENDIF
    99.     xor eax,eax
    100.  
    101.     ret
    102. WndProc endp
    103.  
    104.  
    105. end start
    Так вот, в чем собственно вопрос.

    Код (Text):
    1.     .WHILE TRUE   ; Enter message loop
    2.        invoke GetMessage, ADDR msg,NULL,0,0
    3.     .BREAK .IF (!eax)
    4.        invoke TranslateMessage, ADDR msg
    5.        invoke DispatchMessage, ADDR msg
    6.     .ENDW
    7.      mov     eax,msg.wParam ; сохpанение возвpащаемого значения в eax
    8.      ret
    Это цикл обработки сообщений. GetMessage получает сообщение, если это квит - выходит. Дальше самое интересное. DispatchMessage передает сообщение нашему WndProc. Вот хоть убей не пойму, как?
    Ведь по сути DispatchMessage ничего не знает о этой ф-и. Ее адрес хранится только в структуре WNDCLASS, которая опять же в ф-ю не передается. Вопрос, как DispatchMessage переходит к WndProc?

    Нагуглил, что реализацию ее можно найти в исходниках win2k, но возможности их выкачать у меня нет.

    Может кто то подскажет, или приведет код реализации DispatchMessage?

    Заранее спасибо!

    Посмотрел в отладчике реализацию:
    Код (Text):
    1. CPU Disasm
    2. Address   Hex dump          Command                                  Comments
    3. 7E3696B8    8BFF            MOV EDI,EDI                              ; HEX USER32.DispatchMessageA(pMsg)
    4. 7E3696BA    55              PUSH EBP
    5. 7E3696BB    8BEC            MOV EBP,ESP
    6. 7E3696BD    6A 01           PUSH 1
    7. 7E3696BF    FF75 08         PUSH DWORD PTR SS:[EBP+8]
    8. 7E3696C2    E8 2AF2FFFF     CALL 7E3688F1
    9. 7E3696C7    5D              POP EBP
    10. 7E3696C8    C2 0400         RETN 4
    Видно, что вызывается какая то непонятная ф-я, затем еще одна и ее одна, но в них я еще не могу разобраться никак.
     
  2. Dmitry_Milk

    Dmitry_Milk Member

    Публикаций:
    0
    Регистрация:
    20 ноя 2007
    Сообщения:
    535
    По окну, вызвавшему сообщение. Относительно окна всегда же известно, какого оно класса

    windows.inc
    Код (Text):
    1. MSG STRUCT
    2.   hwnd      DWORD      ?    <---------
    3.   message   DWORD      ?
    4.   wParam    DWORD      ?
    5.   lParam    DWORD      ?
    6.   time      DWORD      ?
    7.   pt        POINT      <>
    8. MSG ENDS
     
  3. rmn

    rmn Well-Known Member

    Публикаций:
    0
    Регистрация:
    23 ноя 2004
    Сообщения:
    2.329
    Код (Text):
    1. WNDPROC fnWindowProc = (WNDPROC)GetWindowLong(msg.hwnd, GWL_WNDPROC);
    2. fnWindowProc(msg.hwnd, msg.message, msg.wParam, msg.lParam);
     
  4. bug1z

    bug1z New Member

    Публикаций:
    0
    Регистрация:
    27 дек 2008
    Сообщения:
    228
    Dmitry_Milk
    Тогда структуру MSG должна заполнить GetMessage. Но поскольку второй параметри у нас NULL, то

    Допустим GetMessage извлекла первое сообщение из очереди и поместила в структуру. Допустим это сообщение не относилось к нашему окну(в потоке ведь может быть несколько несвязанных друг с другом окон?). Но сообщение это было WM_QUIT, а значит наша программа завершится. Не сходится.

    Можете, пожалуйста, рассказать поподробнее про этот механизм?

    Хм, только что проверил, создал два окна. Закрываю одно - закрываются оба.
    Из этого делаю вывод: GetMessage получает сообщение из очереди и обрабатывает его. Сначала проверяет, не вм_квит ли это. если да - возвращает фальш.

    Далее ф-я DispatchMessage получает структуру MSG, которая УЖЕ заполнена(спасибо ф-е GetMessage), достает из этой структуры хэндл окна, которому предназначено сообщение. По этому хэндлу как то(пока не ясно, как) узнает адрес ф-и обработчика для нее и передает ей управление.

    Поправьте, где я не прав, пожалуйста.
     
  5. Ezrah

    Ezrah Member

    Публикаций:
    0
    Регистрация:
    22 мар 2011
    Сообщения:
    411
    Каждый поток процесса имеет собственную оконную очередь сообщений. Сообщения приходят потоку, тем самым и образуя очередь. Функции GetMessage и PeekMessage извлекают самое давнее сообщение из очереди (то, что пришло раньше остальных), заполняют структуру MSG и удаляет сообщение из очереди. Эти две функции отличаются по следующим пунктам:
    1) Если очередь сообщений потока пуста, GetMessage будет ждать прихода хотя бы одного сообщения (т.е. будет "висеть"), в то время как PeekMessage возвращает управление программе сразу. Узнать пуста ли очередь сообщений в этом случае можно по возвращённому PeekMessage значению - если 0, то сообщений на момент вызова не было, если не 0, то сообщения были.
    2) Кроме того, функции PeekMessage можно указать, следует ли удалять сообщение из очереди по завершении его обработки, GetMessage - нельзя, она всегда удаляет сообщение из очереди.
    3) GetMessage возвращает 0, если очередным сообщением является WM_QUIT. PeekMessage возвращает 0, если очередь сообщений была пуста. Отсюда вывод - чтобы знать когда завершить цикл обработки сообщений, в котором используется PeekMessage, нужно проверять значение MSG.message - если оно равно WM_QUIT, цикл надо завершить.

    Сообщения могут посылаться как системой, так и самой программой с помощью, например, SendMessage и PostMessage (но есть и другие функции). Отличие функций SendMessage и PostMessage в том, что SendMessage добавляет сообщение в очередь и ждёт, пока его обработка завершится (при этом возвращая результат обработки), а PostMessage не ждёт. Т.е., например, если вызвать SendMessage(hWnd, WM_QUIT, 0, 0), то программа никогда не попадёт на следующую за вызовом SendMessage инструкцию (подумайте, почему), а если вызвать PostMessage(hWnd, WM_QUIT, 0, 0), то попадёт.

    Как правило, при нажатии на крестик в правом верхнем углу окна, в очередь сообщений помещается WM_CLOSE. Когда Вы его обрабатываете, Вы вызываете PostQuitMessage(hWnd, 0), что эквивалентно PostMessage(hWnd, WM_QUIT, 0, 0). В очередь сообщений кладётся WM_QUIT. Когда это сообщение будет извлечено из очереди, цикл обработки сообщений завершится; вскоре после этого завершится и сама программа. Естественно, все созданные окна при этом разрушаются.

    Всё верно. Из хендла можно получить адрес функции обработки сообщений с помощью GetClassName & GetClassInfo.
     
  6. bug1z

    bug1z New Member

    Публикаций:
    0
    Регистрация:
    27 дек 2008
    Сообщения:
    228
    Все ясно, спасибо огромное за объяснение!
    Вопрос исчерпан.
     
  7. leo

    leo Active Member

    Публикаций:
    0
    Регистрация:
    4 авг 2004
    Сообщения:
    2.542
    Адрес:
    Russia
    Поправка: Исключением являются низкоприоритетные сообщения WM_TIMER и WM_PAINT, которые всегда перемещаются в конец очереди и извлекаются последними. К тому же WM_PAINT может присутствовать в очереди только в единственном экземпляре

    Поправка: SendMessage из того же потока не ставит сообщение в очередь, а напрямую вызывает процедуру окна. Если же сообщение шлется из другого потока, то оно ставится в спец.очередь синхронных сообщений, которые обрабатываются внутри Get\PeekMessage до выборки\возврата асинхронных сообщений
     
  8. bug1z

    bug1z New Member

    Публикаций:
    0
    Регистрация:
    27 дек 2008
    Сообщения:
    228
    Спасибо за ответы.

    Но у меня появился еще вопрос. Я ведь пишу все это на фасме и столкнулся с проблемой отсутствия таких типов как HFONT, DWORD и т.д. Есть ли файлик с ними для фасма? Или нужно писать его самому?
     
  9. Ezrah

    Ezrah Member

    Публикаций:
    0
    Регистрация:
    22 мар 2011
    Сообщения:
    411
    Такого файла, скорее всего, нет. И не нужен. В ассемблере переменные объявляются как dd, dw, db и т.п. и усложнять это не нужно.
    Относительно того, какой тип что из себя являет (dd, dw, db или что-то ещё), всё есть тут, нужно только знать основные типы языка C.
     
  10. AsmGuru62

    AsmGuru62 Member

    Публикаций:
    0
    Регистрация:
    12 сен 2002
    Сообщения:
    689
    Адрес:
    Toronto
    bug1z
    Делаем файл TYPES.INC:
    Код (Text):
    1. HWND    equ dd
    2. WCHARS  equ rw
    3. CHARS   equ rb
    4. HFONT   equ dd
    5. HANDLE  equ dd
    Ну и далее, включаем в самом начале (перед остальными модулями):
    Код (Text):
    1. include 'TYPES.INC'
    2. ...
    3. include 'MODULE1.INC'
    4. include 'MODULE2.INC'
    5. include 'MODULE3.INC'
    6. ...
    Далее, в каком либо объявлении структуры:
    Код (Text):
    1. struc OBJECT1
    2. {
    3.     .UnicodePath    WCHARS 256
    4.     .AnsiString CHARS 64
    5.     .HAllocator HANDLE ?
    6.     .HMyDialog  HWND ?
    7. }
    Наглядно.
     
  11. Ezrah

    Ezrah Member

    Публикаций:
    0
    Регистрация:
    22 мар 2011
    Сообщения:
    411
    leo
    Спасибо за уточнение, не знал этого.
     
  12. bug1z

    bug1z New Member

    Публикаций:
    0
    Регистрация:
    27 дек 2008
    Сообщения:
    228
    Понятно, спасибо огромное!
     
  13. bug1z

    bug1z New Member

    Публикаций:
    0
    Регистрация:
    27 дек 2008
    Сообщения:
    228
    Здравствуйте. У меня появился еще один вопрос.

    Можете, пожалуйста, обьяснить, что такое Регион Отсечения?
    В книге написано: "Ограничивает область, внутры которой система разрешает отображение графической информации."
    Так же написано: "Система устанавливает регион отсечения путем пересечения видимого региона и обновляемого региона".

    Но не пойму где тут этот регион отсечения и как это "путем пересечения видимого региона и обновляемого".

    [​IMG]

    Так же обновляемого региона не вижу.

    Заранее спасибо!
     
  14. kernel16

    kernel16 Human Vl

    Публикаций:
    0
    Регистрация:
    29 окт 2010
    Сообщения:
    316
    ну вот на примере красного окошка:
    оно должно пере рисоваться полностью - обновляемая область всё окно. наше окно загораживает белое, значит нет смысле закрашивать область белого. и мы вычитаем из из области красного область белого.

    далее мы тупо рисуем в красном окне, когда всё, по маске происходит блитинг(копирование) того, что в красном окне в видеоПамять
     
  15. Dmitry_Milk

    Dmitry_Milk Member

    Публикаций:
    0
    Регистрация:
    20 ноя 2007
    Сообщения:
    535
    Точнее так:
    Видимый регион - это действительно то, что на рисунке видно нам красным цветом.
    А обновляемый регион - это запрос приложения на то, какую область окна оно хочет обновить.
    В обычном состоянии вся площадь окна считается валидной, то есть ничего не надо обновлять
    Запрос на то, какую именно область окна приложение хочет обновить, задается одним или несколькими последовательными вызовами функции InvalidateRect, или вызовом функции InvalidateRgn. В частном случае можно инвалидировать всю площадь площадь окна, не указывая аргумент-прямоугольник в InvalidateRect.

    В общем случае получается некоторая область проивольной формы - инвалидный(обновляемый) регион. Пересечение этого региона с видимым регионом дадут регион, в котором будут работать функции рисования. В других местах окна функции ничего не изменят, даже если эти места видимы (но не были заявлены инвалидными).
     
  16. Dmitry_Milk

    Dmitry_Milk Member

    Публикаций:
    0
    Регистрация:
    20 ноя 2007
    Сообщения:
    535
    Объясню, на всякий случай, для чего эти сложности нужны.

    Все связано с тем, что рисование в окне обычно отделено от каких-либо действий и обычно выполняется в какой-то достаточно отложенный момент, когда наконец-то придет сообщение WM_PAINT.

    Поскольку реализации функции отклика на WM_PAINT может быть значительно отдалена в программе от действий, требующих каких-либо изменений в изображении, часто оказывается сложно передать в функцию рисования то, что и как именно надо перерисовать, тем более что WM_PAINT может прийти не только от программы, но и от системы из-за перераспределения окон на экране. Проще бывает нарисовать ВСЕ ЦЕЛИКОМ как должно быть видно в окне на момент поступления WM_PAINT. ДЛя того, чтоб не грузить систему лишним перерисовыванием того, что уже и так нарисовано на своих местах, как раз и придуман механизм инвалидации. Скажем, тащите вы какой-то спрайтик по окну, так зачем перерисовывать все, когда достаточно перерисовать прямоугольник, откуда спрайтик ушел, и прямоугольник, где он появился. Для этого объявляют эти два прямоугольника инвалидным регионом, и при вызове WM_PAINT графические функции Windows будут "стараться" только в этих прямоугольниках (причем, если они не загорожены ничем), а в остальных местах они будут "отлынивать", экономя процессрное время на более полезные вещи.

    Впрочем, регионом инвалидации можно воспользоваться и явно в функции рисования, вообще не давая команд рисования в тех местах, которые этим регионом не покрываются.
     
  17. AsmGuru62

    AsmGuru62 Member

    Публикаций:
    0
    Регистрация:
    12 сен 2002
    Сообщения:
    689
    Адрес:
    Toronto
    Вообще-то WM_PAINT можно вызвать напрямую - не дожидаясь очереди:
    Код (Text):
    1. UpdateWindow (hWnd);
     
  18. Dmitry_Milk

    Dmitry_Milk Member

    Публикаций:
    0
    Регистрация:
    20 ноя 2007
    Сообщения:
    535
    Можно.