Сам себе Iczelion

Тема в разделе "WASM.BEGINNERS", создана пользователем Mikl_, 11 май 2007.

Метки:
  1. Mikl_

    Mikl_ New Member

    Публикаций:
    0
    Регистрация:
    14 ноя 2006
    Сообщения:
    907
    Не сочтите за кощунство. Но попробую переписать уроки Iczelion'а с позиции уменьшения размера. Писалось под WinXP разные сервиспаки на масме. Подробности (листинг и ехе в аттаче)
     
  2. MSoft

    MSoft New Member

    Публикаций:
    0
    Регистрация:
    16 дек 2006
    Сообщения:
    2.854
    Не пойму, чего ты хотел достич. Простого уменьшения размера? Ну а смысл? Нет, оптимизация - это конечно хорошо, не в уроках же для начинающих :)

    Что касается минимизации размера, судить не буду. А вот про удобочитаемость скажу - если собираешься это кому-нибудь показывать, раздели хотя бы функции между собой абзацами или коментариями - просто так сразу читать невозможно, а новичок не поймет, что к чему относится и что и как нужно "отделить", шоб удобнее было читать.

    П.С.: все сказанное лишь ИМХО
     
  3. Mikl_

    Mikl_ New Member

    Публикаций:
    0
    Регистрация:
    14 ноя 2006
    Сообщения:
    907
    Вывод текста, урок 4
     
  4. Mikl_

    Mikl_ New Member

    Публикаций:
    0
    Регистрация:
    14 ноя 2006
    Сообщения:
    907
    Вывод текста, еще один вариант
     
  5. Mikl_

    Mikl_ New Member

    Публикаций:
    0
    Регистрация:
    14 ноя 2006
    Сообщения:
    907
    Вывод текста, урок 5 (как у Iczelion'а и свой)
     
  6. _taha_

    _taha_ New Member

    Публикаций:
    0
    Регистрация:
    28 мар 2007
    Сообщения:
    9
    Согласен с MSoft, туторы Iczelion'a для начинающих. И оптимизация, о которой я тоже судить не буду, в некоторых случаях только ухудшает понимание кода. Но если тебе так нравится, почему бы и нет.. Как вариант можешь сделать уроки Iczelion'a для будующих вирусописателей, крякеров и тп.. гг =)
     
  7. Mikl_

    Mikl_ New Member

    Публикаций:
    0
    Регистрация:
    14 ноя 2006
    Сообщения:
    907
    Ввод символов с клавиатуры, урок 6
     
  8. Mikl_

    Mikl_ New Member

    Публикаций:
    0
    Регистрация:
    14 ноя 2006
    Сообщения:
    907
    Мышь, урок 7. В отличие от оригинала обрабатывается WM_LBUTTONDOWN и WM_RBUTTONDOWN, запоминаются до MAXRECTS координат кликов.
     
  9. Mikl_

    Mikl_ New Member

    Публикаций:
    0
    Регистрация:
    14 ноя 2006
    Сообщения:
    907
    Меню, урок 8. Создание меню через файл ресурсов, LoadMenu, программное создание меню, создание меню через Template-структуру, создание "плавающего" меню (т.е. нажал правый клик и меню появилось на месте клика). Первые два примера как у Iczelion'а дальше свое...
     
  10. Mikl_

    Mikl_ New Member

    Публикаций:
    0
    Регистрация:
    14 ноя 2006
    Сообщения:
    907
    Урок 9, controls
     
  11. Mikl_

    Mikl_ New Member

    Публикаций:
    0
    Регистрация:
    14 ноя 2006
    Сообщения:
    907
    Урок 10, диалоги
     
  12. Mikl_

    Mikl_ New Member

    Публикаций:
    0
    Регистрация:
    14 ноя 2006
    Сообщения:
    907
    Урок 11, Больше о диалоговых окнах
     
  13. kero

    kero Модератор SOURCES & 2LZ Команда форума

    Публикаций:
    0
    Регистрация:
    4 апр 2006
    Сообщения:
    1.074
    Адрес:
    Москва
    Mikl__

    lesson10-1: окно регистрируется, но не создается.

    P.S. А почему бы просто не один-единственный урок by Mikl__ по утрамбовыванию кода Iczelion-а ? :)
     
  14. Mikl_

    Mikl_ New Member

    Публикаций:
    0
    Регистрация:
    14 ноя 2006
    Сообщения:
    907
    kero
    А это мысль, можно ограничиться 3-им уроком...:)
     
  15. Mikl_

    Mikl_ New Member

    Публикаций:
    0
    Регистрация:
    14 ноя 2006
    Сообщения:
    907
    Исправленный lesson10-1
     
  16. Mikl_

    Mikl_ New Member

    Публикаций:
    0
    Регистрация:
    14 ноя 2006
    Сообщения:
    907
    Уроки 12-19
     
  17. Mikl_

    Mikl_ New Member

    Публикаций:
    0
    Регистрация:
    14 ноя 2006
    Сообщения:
    907
    урок by Mikl__ по утрамбовыванию кода Iczelion-а
    Кроме того, что Екклесиаст был мудр, он учил еще народ знанию, он все испытывал, исследовал и составил много притчей. (Библия. Екклесиаст. Глава 12)
    Бредисловие
    В онлайновом учебнике в формате HTML по программированию на ассемблере в win32 в переводе UniSoft есть такие строки: "Я провел эксперимент, написал программу (обычное окно с одной кнопкой в центре, которая закрывает его) на разных языках высокого уровня и после компиляции получил вот такие размеры этой самой программы:
    C++ Builder 4 - 22 kb
    Delphi 5 - 291 kb
    Delphi 5 + библиотека KOL - 26 kb
    Ассемблер MASM - около 3 kb"
    Большинство уроков Iczelion-а имеют размер около 4к, я уменьшаю их до 1к и менее, наименьший
    размер к которому можно/нужно стремиться 97 байт, но это PE-заголовок файла перемешанный
    с кодом, это уже ручная выделка... я пока о стандартных методах уменьшения ЕХЕ файла, работающих под WinXP.
    Шаг первый. Ассемблирование и линковка
    В каждом уроке Iczelion-а находится bat-файл предназначенный для написания конкретного ЕХЕ файла. То есть, для каждого нового ехе, каждый раз создается новый bat, где конкретно указывается названия asm и rc файлов. Я сам через это прошел, но в конце концов надоело, и был сделан универсальный бат, чтобы на клике по asm-файлу происходила трансляция и линковка. Итак пишем asm1.bat следующего содержания:
    Код (Text):
    1. cls
    2. if exist %1.exe del %1.exe
    3. if not exist %1.rc goto over1
    4. c:\masm32\bin\rc /v %1.rc
    5. c:\masm32\bin\cvtres /machine:ix86 %1.res
    6. c:\masm32\bin\ml /c /Cp /Gz /Ic:\masm32\include /coff /nologo %1.asm
    7. if errorlevel 1 goto TheEnd
    8. c:\masm32\bin\link /SUBSYSTEM:WINDOWS /ALIGN:16 /MERGE:.data=.text /LIBPATH:c:\masm32\lib /NOLOGO %1.obj %1.res
    9. if errorlevel 1 goto TheEnd
    10. del %1.res
    11. goto TheEnd
    12. :over1
    13. c:\masm32\bin\ml /c /Cp /Gz /Ic:\masm32\include /coff /nologo %1.asm
    14. if errorlevel 1 goto TheEnd
    15. c:\masm32\bin\link /SUBSYSTEM:WINDOWS /ALIGN:16 /MERGE:.data=.text /LIBPATH:c:\masm32\lib /NOLOGO %1.obj
    16. :TheEnd
    17. if exist %1.obj del %1.obj
    Вариант первый. Если ASM файлы набираются в FAR'е жмем на F9-->команды-->асоциации файлов-->F4 "Маска файла"-->"*.asm" "Команда, выполняемая по Enter"-->"call asm1.bat !" а сам asm1.bat копируем в Win\System32.
    Вариант второй. Если набор ASM файлов в NotePad, или еще где-то тогда в bat'е переделай все %1 на %~n1 щелкаем по "Мой компьютер"-->"Сервис"-->"Свойства папки"-->"Типы файлов" и связываем файлы асм с батником.
    Теперь откроем один из моих файлов и начинаем его построчно сравнивать с оригинальным файлом Iczelion-а
    у меня .model flat у Iczelion-а .model flat,stdcall отсутствие stdcall компенсируется ключем "ml /Gz" строка "option casemap:none" у меня вообще отсутствует, она компенсируется "ml /Cp". Далее у Iczelion-а include \masm32\include\windows.inc, у меня include windows.inc но полный путь "ml /Ic:\masm32\include", то же с либами - строка "link /LIBPATH:c:\masm32\lib". Теперь конкретно об уменьшении размера ЕХЕ. Первое, стандартное выравнивание секций 512 байт, мы уменьшим его до 16 -- строка "link /ALIGN:16". Второе, как в старом добром СОМ-файле поместим код и данные в одну секцию -- строка "link /MERGE:.data=.text"
     
  18. Mikl_

    Mikl_ New Member

    Публикаций:
    0
    Регистрация:
    14 ноя 2006
    Сообщения:
    907
    Шаг второй. Пролог и эпилог Win32-приложения
    Стандартное начало и завершение у Iczelion-а
    Код (Text):
    1. invoke GetModuleHandle, NULL
    2.     mov    hInstance,eax
    3.     invoke GetCommandLine
    4.     mov    CommandLine,eax
    5.     invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
    6.     invoke ExitProcess,eax
    Задумаемся, а что нам здесь нужно? Если не указывать link /BASE:address с адресом отличным от 400000h, тогда для ЕХЕ hInstance=400000h и надобности в GetModuleHandle нет. Если мы не обрабатываем командную строку, тогда и GetCommandLine не нужен, что осталось? Запуск приложения с параметром: SW_SHOWDEFAULT, SW_HIDE, SW_SHOWNORMAL/SW_NORMAL, SW_SHOWMINIMIZED, SW_SHOWMAXIMIZED/SW_MAXIMIZE, SW_SHOWNOACTIVATE, SW_SHOW, SW_MINIMIZE, SW_SHOWMINNOACTIVE, SW_SHOWNA, SW_RESTORE, SW_SHOWDEFAULT, SW_MAX. Все это многообразие передается свойствами запускающего ярлычка программы "Окно" "Обычный размер окна" "Свернутое в значок" "Развернутое на весь экран". Что осталось? ExitProcess попробуйте вместо него поставить обычный RET, запустите приложение, завершите приложение. А теперь удалите ваше приложение, если удаляется без проблем оставляйте RET. Если ЕХЕ не удаляется, посмотрите в Диспетчер задач Windows. Если ваше приложение среди запущенных процессов - значит без ExitProcess, не обойтись (это касается тех приложений где используется структуры типа OPENFILENAME). Итак у нас осталась "голая" WinMain.
     
  19. Mikl_

    Mikl_ New Member

    Публикаций:
    0
    Регистрация:
    14 ноя 2006
    Сообщения:
    907
    Шаг третий. Минимизируем WinMain
    Функционально простейшие Win32 приложения можно разделить на две части WinMain и WinProc. Для начала избавимся от invoke'абельного вызова процедур. Для чего? Для уменьшения размера кода. Урок 2 Iczelion - MsgBox.exe Заглянем в MsgBox.exe через hiew
    Код (Text):
    1. 401000:  6A00                  ;push MB_OK
    2. 401002:  6800304000         ;push offset MsgCaption
    3. 401007:  6819304000         ;push offset MsgBoxText
    4. 40100C:  6A00                  ;push 0
    5. 40100E:  E807000000        ;call [40101A];invoke MessageBox, NULL,addr MsgBoxText, addr MsgCaption, MB_OK
    6. 401013:  6A00                  ;push 0
    7. 401015:  E806000000         ;call [401020];invoke ExitProcess,NULL
    8. 40101A: FF250820400000   ;jmp MessageBoxA
    9. 401020: FF250020400000   ;jmp ExitProcessA
    если заменить invoke MessageBox на call _imp__MessageBoxA@16 экономим 7 байт на каждом вызове API функции. Второе, даже здесь на 26 байт кода приходится 3 push 0 (3x2 = 6 байт) если предварительно обнулить регистр EBX (варианты: EBP, ESI или EDI (API-функции не изменяют значения в этих регистрах)) то push ebx (код 53h) это один байт и мы будем экономить по 1 байту, когда засылаем в стек значения 0 (варианты: NULL, FALSE, FILE_BEGIN, PIPE_CLIENT_END, GMEM_FIXED и т.п.) Команда xor ebx,ebx это 2 байта, учтем, что при старте приложения в Windows XP в eax ноль, а команда xchg eax,ebx это один байт. Теперь перейдем к уроку 3, большая часть программы это заполнение WNDCLASSA/EX структуры
    Код (Text):
    1. WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
    2.     LOCAL wc:WNDCLASSEX
    3.     LOCAL msg:MSG
    4.     LOCAL hwnd:HWND
    5.     mov   wc.cbSize,SIZEOF WNDCLASSEX
    6.     mov   wc.style, CS_HREDRAW or CS_VREDRAW
    7.     mov   wc.lpfnWndProc, OFFSET WndProc
    8.     mov   wc.cbClsExtra,NULL
    9.     mov   wc.cbWndExtra,NULL
    10.     push  hInstance
    11.     pop   wc.hInstance
    12.     mov   wc.hbrBackground,COLOR_WINDOW+1
    13.     mov   wc.lpszMenuName,NULL
    14.     mov   wc.lpszClassName,OFFSET ClassName
    15.     invoke LoadIcon,NULL,IDI_APPLICATION
    16.     mov   wc.hIcon,eax
    17.     mov   wc.hIconSm,eax
    18.     invoke LoadCursor,NULL,IDC_ARROW
    19.     mov   wc.hCursor,eax
    как быть здесь? Создаем WNDCLASS структуру в данных и заполняем ее заранее. Вы спросите, а какже значения, возвращаемые функциями LoadCursor и LoadIcon. Для набора стандартных системных иконок и курсоров они будут одними и теми же, и в Win98 и WinXp. Эти значения можно посмотреть заранее. Поэтому мы и не используем LoadCursor и LoadIcon
    Код (Text):
    1. ;WNDCLASSA -----------------------------------------------
    2.     style           dd CS_HREDRAW or CS_VREDRAW; Стиль нашего окна
    3.     lpfnWndProc     dd OFFSET WndProc;Адрес процедуры обработки событий
    4.     cbClsExtra      dd 0        ;это нам пока не нужно
    5.     cbWndExtra      dd 0        ;и это тоже
    6.     hInstance       dd 400000h  ;Адрес нашей проги в памяти (Windows всегда её грузит по этому адресу)
    7.     hIcon           dd 10003h   ;Иконка окна по умолчанию
    8.     hCursor         dd 10011h   ;Курсор окна по умолчанию (здесь указан ID обычной стрелки)
    9.     hbrBackground   dd COLOR_WINDOW ;Фон нашего окна
    10.     lpszMenuName    dd 0        ;Меню у нас отсутствует
    11.     lpszClassName   dd OFFSET szWinClass; Указатель на имя нашего класса
    12. ;----------------------------------------------------------
    Что у нас осталось?
    Код (Text):
    1. .code
    2. start:  xchg ebx,eax
    3.     lea edi,style
    4.     assume edi:ptr WNDCLASSA
    5.     push edi
    6.     call _imp__RegisterClassA@4
    После регистрации окна Iczelion (и не только он один) проделывает три операции: 1) создает окно (функция CreateWindow); 2) Отобpажает его на экpане (ShowWindow) и 3) Обновить содеpжимое окна (UpdateWindow) но, как показала практика, добавление к стилю окна (обычно WS_OVERLAPPEDWINDOW) во время регистрации окна стиля WS_VISIBLE делает ненужным и ShowWindow, и UpdateWindow. Еще один фокус с CreateWindow - правильное указание класса окна необходимо, если мы создаем элемент окна, с заранее определенными свойствами (STATIC, BUTTON, EDIT и т.п.) правда в этом случае нам не нужен RegisterClass. Когда мы создаем наше главное окно, то его название может быть любым, в том числе и на русском и на китайском в юникоде. Экономим на этом, и название класса окна, и название имени окна, которое будет показано на title bar'е - будут одним и тем же. Код обращения к переменной на будет намного короче, если Вы обращаетесь к нему косвенно через регистр
    Код (Text):
    1. start:  xchg ebx,eax
    2.     lea edi,style
    3.     assume edi:ptr WNDCLASSA
    4.     push edi
    5.     call _imp__RegisterClassA@4
    6.     mov ecx,CW_USEDEFAULT
    7.     push ebx       
    8.     push [edi].hInstance
    9.     push ebx   
    10.     push ebx   
    11.     push ecx   
    12.     push ecx   
    13.     push ecx   
    14.     push ecx   
    15.     push WS_OVERLAPPEDWINDOW + WS_VISIBLE
    16.     push [edi].lpszClassName       
    17.     push [edi].lpszClassName
    18.     push ebx
    19.     call _imp__CreateWindowExA@48   ;создать окно
    20. message_loop: push ebx  ;цикл обработки сообщений
    21.     push ebx
    22.     push ebx
    23.     push edi
    24.     call _imp__GetMessageA@16  
    25.     xchg eax,ecx
    26.     jecxz exit_msg_loop
    27.     push edi
    28.     call _imp__DispatchMessageA@4     ;вернуть управление Windows        
    29.     jmp short message_loop
    30. exit_msg_loop: ret
    После создания окна переходим сразу к циклу сообщений. Постойте, закричат нам вслед, а где же место под структуру MSG? А оно заняло место WNDCLASSA структуры, так как сделав свое дело при регистрации и создании окна, больше мы к этой переменной не обратимся. Еще одна мелкая фишка - после GetMessage мы должны проверить EAX на равенство 0, обычно это делается парой команд test eax,eax (2 байта) и jz (2 байта) мы используем xchg eax,ecx (1 байт) и jecxz (2 байта). Обычно к циклу сообщений добавляют еще и TranslateMessage хотя обработка нажатий на клавиши в программе и не предусматривается
     
  20. Mikl_

    Mikl_ New Member

    Публикаций:
    0
    Регистрация:
    14 ноя 2006
    Сообщения:
    907
    Шаг четвертый. Минимизируем WndProc
    Для простоты рассмотрим простейшее окно, которое обрабатывает два сообщения - 1) сообщение о закрытии окна 2) все остальные сообщения.
    Как это сделано у Iczelion:
    Код (Text):
    1. WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    2.     .IF uMsg==WM_DESTROY
    3.         invoke PostQuitMessage,NULL
    4.     .ELSE
    5.         invoke DefWindowProc,hWnd,uMsg,wParam,lParam       
    6.         ret
    7.     .ENDIF
    8.     xor eax,eax
    9.     ret
    10. WndProc endp
    а под hiew
    Код (Text):
    1. push ebp
    2. mov ebp,esp
    3. cmp dword ptr [ebp+0Ch],2
    4. jne 4002CB
    5. push 0
    6. call PostQuitMessage
    7. jmp 4002E0
    8. push [ebp+14h]
    9. push [ebp+10h]
    10. push [ebp+0Ch]
    11. push [ebp+8]
    12. call DefWindowProcA
    13. leave
    14. retn 10h
    15. 4002E0: xor eax,eax
    16. leave
    17. retn 10h
    В программе предусмотрена обработка только одного сообщения, поэтому нам не нужен стандартный пролог. Код "cmp dword ptr [esp+8],WM_DESTROY" короче чем "push ebp/mov ebp,esp/cmp [ebp+8],WM_DESTROY".
    Стандартная обработка остальных сообщений invoke DefWindowProc,hWnd,uMsg,wParam,lParam/leave/ret 10h
    Для функции DefWindowProc передаются те же параметры, что и для функции WndProc, поэтому достаточно будет поставить jmp вместо call и ret 10h. Так как функция WndProc была вызвана без пролога, то мы выходим из нее без эпилога "leave". Возвpат в eax ноль, на самом деле не требуется (это необходимо только для диалогов).
    Код (Text):
    1. WndProc proc ;hwnd:DWORD, Msg:DWORD, wParam:DWORD, lParam:DWORD
    2.         cmp dword ptr [esp+8],WM_DESTROY
    3.         je short @@WM_DESTROY
    4.         jmp _imp__DefWindowProcA@16  
    5. @@WM_DESTROY: push 0    ;завершение программы
    6.         call _imp__PostQuitMessage@4    
    7.         retn 10h
    8. WndProc endp
    Ну и до кучи, подраздел Внутри процедуры окна
    Сообщения, которые Windows посылает приложению, на самом деле получает процедура окна, определенная для данного класса. Чаще всего она представляет собой конструкцию «выбора», которую в языке C/C++ назы­вают SWITCH, в Pascal/Delphi — CASE, в Basic — SELECT CASE, а в абстрактном языке программирования — мультивлетвением. Задача конструкции CASE заключается в проверке на ра­венство значения переменной var элементу из списка.
    Код (Text):
    1. CASE <var> OF
    2. <case A> (выполнить, если var = A)
    3. <case B> (выполнить, если var = B)
    4. .  .  .
    5. <case N> (выполнить, если var = N)
    6. <default> (выполнить, если нет соответствия)
    7. END_CASE
    Данная конструкция направляет сообщения функциям обработчикам. На ассемб­лере конструкция CASE может быть реализована, по крайней мере, семью способами:
    1) тупо, через повторяющиеся команды cmp и je.
    2) через макрос
    3) через высокоуровневые директивы .IF .ELSEIF .ELSE
    4) через косвенные переходы или косвенный вызов процедур
    5) через команду SCASD
    6) через последовательное приближение
    7) через имитацию команды XLAT
    Рассмотрим подпро­грамму, обрабатывающую только четыре сообщения: WM_CREATE=1, WM_DESTROY=2, WM_PAINT=0Fh и WM_TIMER=113h. Пусть сообщение WM_XX обрабатывается по адресу @@WM_XX. Учтем так же, что частота появлений различных сообщений неодинакова (спасибо Y_Mur), поэтому проверяем сперва наиболее часто посылаемые WM_PAINT и WM_TIMER, затем однократно используемую WM_CREATE, и наконец последнее сообщение в жизни нашего приложения WM_DESTROY.
    Итак, первый способ очевиден.
    Код (Text):
    1. cmp dword ptr Msg,WM_TIMER
    2.     je @@WM_TIMER
    3. cmp dword ptr Msg,WM_PAINT
    4.     je @@WM_PAINT
    5. cmp dword ptr Msg,WM_CREATE
    6.     je @@WM_CREATE
    7. cmp dword ptr Msg,WM_DESTROY
    8.     je @@WM_DESTROY
    9. leave        
    10.     jmp DefWindowProcA; обработка по умолчанию      
    11. @@WM_DESTROY:
    12. . . .
    13. @@WM_CREATE:
    14. . . .
    15. @@WM_PAINT:
    16. . . .
    17. @@WM_TIMER:
    18. . . .
    один из вариантов первого способа — заменить команду cmp на команду sub
    Код (Text):
    1. mov eax,Msg
    2. dec eax
    3. jz @@WM_CREATE  ;WM_CREATE=1
    4. dec eax
    5. jz @@WM_DESTROY ;WM_CREATE+1=WM_DESTROY=2
    6. sub eax,0Dh
    7. jz @@WM_PAINT   ;WM_DESTROY+0Dh=WM_PAINT=0Fh
    8. sub eax,104h
    9. jz @@WM_TIMER   ;WM_PAINT+104h=WM_TIMER=113h
    10. mov esp,ebp     ;все сообщения, не обрабатываемые в функции
    11.       pop ebp       ;WndProc, направляются на обработку по умолчанию      
    12.     jmp DefWindowProcA
    13. @@WM_DESTROY:
    14. . . .
    Способ второй. Используем макрос с IRP-блоком (Вы, конечно, мо­жете, если захотите, написать другой макрос). Блоки повторения IRP имеют следующий вид: IRP p,<v1,…,vn>
    <тело блока>
    ENDM
    Запись в уголках <v1,…,vn> это явно указываемые фактические параметры, p — некоторое имя, оно играет роль формального (фиктивного) параметра и используется в предложениях тела блока. Фактические параметры vi пере­числяются через запятую, а вся их совокупность обязательно заключается в угловые скобки. Встретив IRP-блок, макрогенератор заменит блок на n копий тела блока (по одной копии на фактический параметр), причем в i-той копии все вхождения имени p заменятся на vi. Знак & указывает границу формаль­ного параметра, выделяет его из окружающего текста, но в окончательный текст программы не попадает. Если в теле блока повторения или макроса имеются комментарии (в конце каких-то предложений или как самостоятель­ные предложения), то они переносятся во все копии блока. Если коммента­рий полезен при описании самого блока повторения, но совершенно не ну­жен в его копиях — тогда комментарий начинают с двух точек с запятой — такие комментарии не копируются.
    Код (Text):
    1. IRP CASE,<WM_TIMER,WM_PAINT,WM_CREATE,WM_DESTROY>;;последовательность вариантов
    2. cmp dword ptr Msg, CASE;;вариант найден?
    3. je @@&CASE
    4. ENDM;;закончить макроописание
    5. leave       ; обработка по умолчанию
    6.     jmp DefWindowProcA      
    7. @@WM_DESTROY:
    8. . . .
    Способ третий. Пример использования директив ассемблера .IF .ELSEIF .ELSE .ENDIF:
    Код (Text):
    1. .IF (Msg==WM_TIMER)
    2. . . .           ;здесь обрабатывается сообщение WM_TIMER
    3. .ELSEIF (Msg==WM_PAINT)
    4. . . .           ;здесь обрабатывается сообщение WM_PAINT
    5. .ELSEIF (Msg==WM_CREATE)
    6. . . .           ;здесь обрабатывается сообщение WM_CREATE
    7. .ELSEIF (Msg==WM_DESTROY)
    8. . . .           ; здесь обрабатывается сообщение WM_DESTROY
    9. .ELSE
    10.  leave     
    11. jmp DefWindowProcA; обработка по умолчанию
    12. .ENDIF
    И вариант с макросом, и вариант с .IF .ELSEIF .ELSE .ENDIF все равно будут оттранслированы в повторяющиеся команды CMP и JE
    Способ четвертый. Налицо в программе очень много повторяющихся почти одинаковых фрагментов. Нельзя ли написать программу как-нибудь иначе? Чем различаются эти фрагменты программы? Разными операндами в команде CMP, разными адресами переходов или разными вызываемыми про­цедурами. Уберем операнды команды CMP в какой-нибудь регистр и исполь­зуем для разных адресов переходов косвенный переход, а для разных вызы­ваемых процедур — косвенный вызов процедуры.
    Код (Text):
    1. ;Таблица сообщений, которые обрабатывает процедура окна
    2. MsgTable dd WM_PAINT, @@WM_PAINT, WM_TIMER, @@WM_TIMER, WM_CREATE, @@WM_CREATE, WM_DESTROY, @@WM_DESTROY, 0; Конец списка обязательно 0
    3. . . .
    4. ;Вызов соответствующего обработчика
    5. mov esi,offset MsgTable ;устанавливаем в ESI адрес таблицы-диспетчера
    6. a1:     mov eax,[esi]       ;устанавливаем в eax тип сообщения
    7.     test eax,eax    ;это конец таблицы? Если да, то данное сообщение не
    8. jz @@default; обрабатывается приложением - передаем управление Windows
    9.     cmp eax,Msg
    10.     jne a2  ;для данного сообщения не найден обработчик
    11. add esi,4   ;устанавливаем в esi адрес обработки сообщения
    12. jmp [esi]           ;передаем управление обработчику сообщения
    13. a2: add esi,8           ;перейдем к следующему сообщению
    14.     jmp short a1   
    15. @@default: leave        ;Обработчик не найден, возвращается значение,
    16. jmp DefWindowProcA  ; которое дает стандартный обработчик
    17. @@WM_DESTROY:
    Способ пятый. Используем команду SCASD с префиксом REPNE. Вне­сем в конец таблицы сообщений, которые обрабатывает процедура окна, ад­рес обработки по умолчанию, а в регистр ECX количество элементов в таб­лице сообщений минус один (компилятор подсчитает это число автоматически). Напомню, что команда SCASD сравнивает зна­чение в регистре EAX с ячейкой памяти, локализуемой регистром EDI. Пре­фикс REPNE не только заставляет циклически выполняться команду ска­ни­рования, пока ECX не станет равным 0, но и отслеживает состояние флага ZF. Как только содержимое в ячейке памяти совпадет со значением в реги­стре EAX, сканирование остановится. Регистр EDI будет указывать не на ячейку памяти, значение которой совпадает с регистром EAX, а на сле­дующую ячейку. Сканирование также остановится, если содержимое регистра ECX станет равным 0. При этом в регистре EDI будет адрес метки @@default (адрес обработки по умолчанию)
    Код (Text):
    1. ;Таблица сообщений, которые обрабатывает процедура окна
    2. MsgTable dd WM_PAINT, @@WM_PAINT, WM_TIMER, @@WM_TIMER, WM_CREATE, \
    3. @@WM_CREATE, WM_DESTROY, @@WM_DESTROY, @@default
    4. MsgCount = ($ - MSGTable - 4)/4
    5. . . .
    6. mov edi,offset MsgTable;устанавливаем в EDI адрес таблицы-диспетчера
    7. mov eax,Msg
    8. mov ecx,MsgCount;удвоенное количество обрабатываемых сообщений (4*2=8)
    9. repne scasd     ;ищем сообщение и если найдем — передадим
    10. jmp [edi]       ; управление обработчику сообщения
    11. @@default:  leave       ; если обработчик не найден, вызывается
    12.       jmp DefWindowProcA ; обработка по умолчанию
    13. @@WM_DESTROY:
    По величине это, по-моему, самый короткий код функции обработки сообщений. Кстати, читатель может спросить: «а что произойдет, если адрес обработчика сообщения совпадет с номером сообщения?» Этого не может произойти, так как максимальный номер стандартных сообщений равен 400h, а 32-разрядная операционная система Windows отводит каждой программе свое адресное пространство размером около двух гига­байт, начиная с адреса 100000h. За границы своего адресного пространства задача выйти не может, так же, как никакая другая задача не может работать с данным пространством - этим и определяется автономность программы.
    Способ шестой — последовательное приближение. Пусть у нас уже не 4 обрабатываемых сообщения, а 100. Тогда при использовании с первого по пятый способ мы должны будем последовательно сравнить до 100 значений, до тех пор пока не найдем искомое. Увеличим скорость обработки сообщений, которые Windows посылает Вашему приложению. Рассортируем номера обрабатываемых нашей программой сообщений по возрастанию, теперь разделим номера сообщений на две равные группы — 50 сообщений с меньшими номерами в одной группе, 50 сообщений с большими номерами в другой. После определения, к какой из групп сообщений принадлежит вновь поступившее сообщение, нам потребуется уже не 100, а только 50+1 сравнение. Разобьем каждую из образовавшихся групп с номерами сообщений еще на две подгруппы, и потребуется уже 25+2 сравнений, вместо 100. Продолжаем делить подгруппы с номерами сообщений, и получаем 13+3 вместо 100, затем 7+4, далее 4+5, и, наконец, 2+6, больше разбивать подгруппы не имеет смысла (дальше было бы 1+7, а это одно и то же, что и 2+6). То есть, в общем случае, если 2^(n-1) < N <= 2^n, то определить N можно не более чем за n+1 приближение. Заметьте, увеличение скорости обработки будет сопровождаться разрастанием кода программы.
    Код (Text):
    1. mas     dd WM_CREATE, WM_DESTROY... WM_PAINT ;массив сообщений
    2.         n = ($ - mas)/4
    3. mas1        dd @@WM_CREATE, @@WM_DESTROY,...  @@WM_PAINT;массив адресов
    4. ...
    5.         xor ecx,ecx         ;в ecx индекс первого элемента
    6.         mov edx,n-1         ;в edx индекс последнего элемента
    7.         mov eax,Msg
    8.         cmp eax,WM_CREATE;сравниваем с минимальным элементом таблицы
    9.         jb short @@default;если меньше, то обработка по умолчанию
    10.         mov ebx,edx     ;создаем индекс центрального элемента
    11. a1:     cmp ecx,edx     ;проверка ecx>edx на окончание поиска
    12.     ja short @@default;проверены все элементы, обработка по умолчанию
    13.         shr ebx,1   ;индекс центрального элемента равен (edx+ecx)/2
    14.         cmp mas[ebx*4],eax  ;сравниваем с искомым значением
    15.         jb short a2         ;mas[ebx*4]<Msg
    16.         je short a3         ;mas[ebx*4]=Msg    
    17. inc ebx     ;учтем только что проверенное значение
    18.         mov ecx,ebx         ;изменяем нижнюю границу поиска
    19.         add ebx,edx     ;создаем индекс центрального элемента
    20.         jmp short a1        ;переходим к следующему элементу
    21. a2:     dec ebx     ;учтем только что проверенное значение
    22. mov edx,ebx         ;изменяем верхнюю границу поиска
    23.         add ebx,ecx     ;создаем индекс центрального элемента
    24.         jmp short a1        ;переходим к следующему элементу
    25. @@default:  leave           ;искомое значение не найдено,
    26. jmp DefWindowProc   ;обработка по умолчанию
    27. a3:     jmp mas1[ebx*4]     ;искомое значение найдено
    28. @@WM_CREATE: ...
    А это еще один, более короткий вариант последовательного приближения. Но для него требуется, чтобы количество сообщений должно быть равным 2^n. Допустим, мы обрабатываем 16 сообщений,
    Количество элементов 2^(n-1) < N <= 2^n
    ebx (индекс элемента) 2^(n-1)
    ecx (счетчик приближений) n+1
    так как N = 16, то в соответствии с таблицей: n = 4, ebx = 8, ecx = 5
    Код (Text):
    1. mas     dd WM_CREATE, WM_DESTROY, ... WM_PAINT;массив сообщений
    2. mas1        dd @@WM_CREATE, @@WM_DESTROY,... @@WM_PAINT ;массив адресов
    3. n equ 4
    4. ...
    5.         mov ecx,n+1         ;ecx=5
    6.         mov ebx,1
    7.         shl ebx,n-1         ;ebx=8 индекс центрального элемента
    8.         xor edx,edx         ;нижняя граница поиска
    9.         mov eax,Msg
    10.         cmp eax,WM_CREATE;сравниваем с минимальным элементом таблицы
    11.         jb short @@default;если меньше, то обработка по умолчанию
    12. a1:     cmp mas[edx+ebx*4],eax  ;сравниваем с искомым значением
    13.         ja short a2         ;mas[ebx*4]<Msg
    14.         je short a3         ;mas[ebx*4]=Msg    
    15.         lea edx,[edx+ebx*4] ;изменяем нижнюю границу поиска
    16. a2:     shr ebx,1       ;изменяем индекс центрального элемента
    17.         loop a1         ;переходим к следующему элементу
    18. @@default:  leave           ;искомое значение не найдено
    19. jmp DefWindowProc       ;обработка по умолчанию
    20. a3:     jmp mas1[edx+ebx*4]     ;искомое значение найдено
    21. @@WM_CREATE: ...
    Если Вы и дальше хотите экспериментировать с обработкой сообще­ний — попробуйте заменить команду cmp на команды sub, dec, test, or или and.
    Способ седьмой — имитация команды XLAT. При программировании на ассемблере стремятся либо получить максимально компактный код, либо достичь максимального быстродействия, хотя иногда добиваются и того, и другого. Способы четыре и пять, реализуемые через косвенный пе­реход или косвенный вызов, хотя и имеют наименьший раз­мер кода, но по быстродействию остаются таким же, как и способы с прямым переходом. То есть, всегда возможен вариант, когда будут перебраны все вари­анты сообщений, перед тем как запустится обработка сообщения по умолча­нию. Реализуем мультиветвление при помощи косвенного перехода по таб­лице. Для увеличения скорости про­смотра очереди сообщений строим про­цедуру подобную команде XLAT (сама команда XLAT, к сожалению, дает возможность работать только с 256 вызовами), строим таблицу MsgTable, где номер элемента таблицы соответствует но­меру сообщения. Сами табличные элементы представ­ляют собой адреса (процедур), где эти сообщения обрабатываются. Чтобы не перечислять все 400h (1024) сообщений, можно определить номер макси­мального сообщения Max_Msg и если номер сообщения больше максималь­ного, сразу отправлять на обработку сообще­ния по умолчанию (метка @@default). Сообщения, не обрабатываемые в процедуре WinProc, также имеют адрес обработки соответствующий адресу метки @@default. При этом время выполнения перехода всегда одинаково для всех вариантов мультиветвления.
    Код (Text):
    1. mov eax,Msg
    2. cmp eax,Max_Msg
    3. ja @@default;MsgTable - таблица с адресами обработки
    4. jmp MsgTable[eax*4];каждый адрес обработки расположен через 4 байта
    Но у этого способа есть и оборотная сторона. К сожалению, большая ско­рость обработки сообщений будет компенсирована раздувшейся таблицей MsgTable.
    P.S. Спасибо W4FhLF, Y_Mur за замечания и поправки