Не сочтите за кощунство. Но попробую переписать уроки Iczelion'а с позиции уменьшения размера. Писалось под WinXP разные сервиспаки на масме. Подробности (листинг и ехе в аттаче)
Не пойму, чего ты хотел достич. Простого уменьшения размера? Ну а смысл? Нет, оптимизация - это конечно хорошо, не в уроках же для начинающих Что касается минимизации размера, судить не буду. А вот про удобочитаемость скажу - если собираешься это кому-нибудь показывать, раздели хотя бы функции между собой абзацами или коментариями - просто так сразу читать невозможно, а новичок не поймет, что к чему относится и что и как нужно "отделить", шоб удобнее было читать. П.С.: все сказанное лишь ИМХО
Согласен с MSoft, туторы Iczelion'a для начинающих. И оптимизация, о которой я тоже судить не буду, в некоторых случаях только ухудшает понимание кода. Но если тебе так нравится, почему бы и нет.. Как вариант можешь сделать уроки Iczelion'a для будующих вирусописателей, крякеров и тп.. гг =)
Мышь, урок 7. В отличие от оригинала обрабатывается WM_LBUTTONDOWN и WM_RBUTTONDOWN, запоминаются до MAXRECTS координат кликов.
Меню, урок 8. Создание меню через файл ресурсов, LoadMenu, программное создание меню, создание меню через Template-структуру, создание "плавающего" меню (т.е. нажал правый клик и меню появилось на месте клика). Первые два примера как у Iczelion'а дальше свое...
Mikl__ lesson10-1: окно регистрируется, но не создается. P.S. А почему бы просто не один-единственный урок by Mikl__ по утрамбовыванию кода Iczelion-а ?
урок 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): cls if exist %1.exe del %1.exe if not exist %1.rc goto over1 c:\masm32\bin\rc /v %1.rc c:\masm32\bin\cvtres /machine:ix86 %1.res c:\masm32\bin\ml /c /Cp /Gz /Ic:\masm32\include /coff /nologo %1.asm if errorlevel 1 goto TheEnd c:\masm32\bin\link /SUBSYSTEM:WINDOWS /ALIGN:16 /MERGE:.data=.text /LIBPATH:c:\masm32\lib /NOLOGO %1.obj %1.res if errorlevel 1 goto TheEnd del %1.res goto TheEnd :over1 c:\masm32\bin\ml /c /Cp /Gz /Ic:\masm32\include /coff /nologo %1.asm if errorlevel 1 goto TheEnd c:\masm32\bin\link /SUBSYSTEM:WINDOWS /ALIGN:16 /MERGE:.data=.text /LIBPATH:c:\masm32\lib /NOLOGO %1.obj :TheEnd 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"
Шаг второй. Пролог и эпилог Win32-приложения Стандартное начало и завершение у Iczelion-а Код (Text): invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine mov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT 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.
Шаг третий. Минимизируем WinMain Функционально простейшие Win32 приложения можно разделить на две части WinMain и WinProc. Для начала избавимся от invoke'абельного вызова процедур. Для чего? Для уменьшения размера кода. Урок 2 Iczelion - MsgBox.exe Заглянем в MsgBox.exe через hiew Код (Text): 401000: 6A00 ;push MB_OK 401002: 6800304000 ;push offset MsgCaption 401007: 6819304000 ;push offset MsgBoxText 40100C: 6A00 ;push 0 40100E: E807000000 ;call [40101A];invoke MessageBox, NULL,addr MsgBoxText, addr MsgCaption, MB_OK 401013: 6A00 ;push 0 401015: E806000000 ;call [401020];invoke ExitProcess,NULL 40101A: FF250820400000 ;jmp MessageBoxA 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): WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD 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 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 как быть здесь? Создаем WNDCLASS структуру в данных и заполняем ее заранее. Вы спросите, а какже значения, возвращаемые функциями LoadCursor и LoadIcon. Для набора стандартных системных иконок и курсоров они будут одними и теми же, и в Win98 и WinXp. Эти значения можно посмотреть заранее. Поэтому мы и не используем LoadCursor и LoadIcon Код (Text): ;WNDCLASSA ----------------------------------------------- style dd CS_HREDRAW or CS_VREDRAW; Стиль нашего окна lpfnWndProc dd OFFSET WndProc;Адрес процедуры обработки событий cbClsExtra dd 0 ;это нам пока не нужно cbWndExtra dd 0 ;и это тоже hInstance dd 400000h ;Адрес нашей проги в памяти (Windows всегда её грузит по этому адресу) hIcon dd 10003h ;Иконка окна по умолчанию hCursor dd 10011h ;Курсор окна по умолчанию (здесь указан ID обычной стрелки) hbrBackground dd COLOR_WINDOW ;Фон нашего окна lpszMenuName dd 0 ;Меню у нас отсутствует lpszClassName dd OFFSET szWinClass; Указатель на имя нашего класса ;---------------------------------------------------------- Что у нас осталось? Код (Text): .code start: xchg ebx,eax lea edi,style assume edi:ptr WNDCLASSA push edi 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): start: xchg ebx,eax lea edi,style assume edi:ptr WNDCLASSA push edi call _imp__RegisterClassA@4 mov ecx,CW_USEDEFAULT push ebx push [edi].hInstance push ebx push ebx push ecx push ecx push ecx push ecx push WS_OVERLAPPEDWINDOW + WS_VISIBLE push [edi].lpszClassName push [edi].lpszClassName push ebx call _imp__CreateWindowExA@48 ;создать окно message_loop: push ebx ;цикл обработки сообщений push ebx push ebx push edi call _imp__GetMessageA@16 xchg eax,ecx jecxz exit_msg_loop push edi call _imp__DispatchMessageA@4 ;вернуть управление Windows jmp short message_loop exit_msg_loop: ret После создания окна переходим сразу к циклу сообщений. Постойте, закричат нам вслед, а где же место под структуру MSG? А оно заняло место WNDCLASSA структуры, так как сделав свое дело при регистрации и создании окна, больше мы к этой переменной не обратимся. Еще одна мелкая фишка - после GetMessage мы должны проверить EAX на равенство 0, обычно это делается парой команд test eax,eax (2 байта) и jz (2 байта) мы используем xchg eax,ecx (1 байт) и jecxz (2 байта). Обычно к циклу сообщений добавляют еще и TranslateMessage хотя обработка нажатий на клавиши в программе и не предусматривается
Шаг четвертый. Минимизируем WndProc Для простоты рассмотрим простейшее окно, которое обрабатывает два сообщения - 1) сообщение о закрытии окна 2) все остальные сообщения. Как это сделано у Iczelion: Код (ASM): 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 а под hiew Код (Text): push ebp mov ebp,esp cmp dword ptr [ebp+0Ch],2 jne 4002CB push 0 call PostQuitMessage jmp 4002E0 push [ebp+14h] push [ebp+10h] push [ebp+0Ch] push [ebp+8] call DefWindowProcA leave retn 10h 4002E0: xor eax,eax leave 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 ноль, на самом деле не требуется (это необходимо только для диалогов). Код (ASM): WndProc proc ;hwnd:DWORD, Msg:DWORD, wParam:DWORD, lParam:DWORD cmp dword ptr [esp+8],WM_DESTROY je short @@WM_DESTROY jmp _imp__DefWindowProcA@16 @@WM_DESTROY: push 0 ;завершение программы call _imp__PostQuitMessage@4 retn 10h WndProc endp Ну и до кучи, подраздел Внутри процедуры окна Сообщения, которые Windows посылает приложению, на самом деле получает процедура окна, определенная для данного класса. Чаще всего она представляет собой конструкцию «выбора», которую в языке C/C++ называют SWITCH, в Pascal/Delphi — CASE, в Basic — SELECT CASE, а в абстрактном языке программирования — мультивлетвением. Задача конструкции CASE заключается в проверке на равенство значения переменной var элементу из списка. Код (Text): CASE <var> OF <case A> (выполнить, если var = A) <case B> (выполнить, если var = B) . . . <case N> (выполнить, если var = N) <default> (выполнить, если нет соответствия) END_CASE Данная конструкция направляет сообщения функциям обработчикам. На ассемблере конструкция CASE может быть реализована, по крайней мере, семью способами: тупо, через повторяющиеся команды cmp и je или через cmp и jne. через макрос через высокоуровневые директивы .IF .ELSEIF .ELSE через косвенные переходы или косвенный вызов процедур через команду SCASD через последовательное приближение через имитацию команды 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. Итак, первый способ очевиден. Код (ASM): cmp dword ptr Msg,WM_TIMER je @@WM_TIMER cmp dword ptr Msg,WM_PAINT je @@WM_PAINT cmp dword ptr Msg,WM_CREATE je @@WM_CREATE cmp dword ptr Msg,WM_DESTROY je @@WM_DESTROY leave jmp DefWindowProcA; обработка по умолчанию @@WM_DESTROY: . . . @@WM_CREATE: . . . @@WM_PAINT: . . . @@WM_TIMER: . . . один из вариантов первого способа — заменить команду cmp на команду sub Код (ASM): mov eax,Msg dec eax jz @@WM_CREATE ;WM_CREATE=1 dec eax jz @@WM_DESTROY ;WM_CREATE+1=WM_DESTROY=2 sub eax,0Dh jz @@WM_PAINT ;WM_DESTROY+0Dh=WM_PAINT=0Fh sub eax,104h jz @@WM_TIMER ;WM_PAINT+104h=WM_TIMER=113h mov esp,ebp ;все сообщения, не обрабатываемые в функции pop ebp ;WndProc, направляются на обработку по умолчанию jmp DefWindowProcA @@WM_DESTROY: . . . Способ второй. Используем макрос с IRP-блоком (Вы, конечно, можете, если захотите, написать другой макрос). Блоки повторения IRP имеют следующий вид: IRP p,<v1,…,vn> <тело блока>ENDMЗапись в уголках <v1,…,vn> это явно указываемые фактические параметры, p — некоторое имя, оно играет роль формального (фиктивного) параметра и используется в предложениях тела блока. Фактические параметры vi перечисляются через запятую, а вся их совокупность обязательно заключается в угловые скобки. Встретив IRP-блок, макрогенератор заменит блок на n копий тела блока (по одной копии на фактический параметр), причем в i-той копии все вхождения имени p заменятся на vi. Знак & указывает границу формального параметра, выделяет его из окружающего текста, но в окончательный текст программы не попадает. Если в теле блока повторения или макроса имеются комментарии (в конце каких-то предложений или как самостоятельные предложения), то они переносятся во все копии блока. Если комментарий полезен при описании самого блока повторения, но совершенно не нужен в его копиях — тогда комментарий начинают с двух точек с запятой — такие комментарии не копируются. Код (ASM): IRP CASE,<WM_TIMER, WM_PAINT, WM_CREATE, WM_DESTROY> ;;последовательность вариантов cmp dword ptr Msg, CASE;;вариант найден? je @@&CASE ENDM;;закончить макроописание leave ; обработка по умолчанию jmp DefWindowProcA @@WM_DESTROY: . . . Способ третий. Пример использования директив ассемблера .IF .ELSEIF .ELSE .ENDIF: Код (ASM): .IF (Msg==WM_TIMER) . . . ;здесь обрабатывается сообщение WM_TIMER .ELSEIF (Msg==WM_PAINT) . . . ;здесь обрабатывается сообщение WM_PAINT .ELSEIF (Msg==WM_CREATE) . . . ;здесь обрабатывается сообщение WM_CREATE .ELSEIF (Msg==WM_DESTROY) . . . ; здесь обрабатывается сообщение WM_DESTROY .ELSE leave jmp DefWindowProcA; обработка по умолчанию .ENDIF И вариант с макросом, и вариант с .IF .ELSEIF .ELSE .ENDIF все равно будут оттранслированы в повторяющиеся команды CMP и JE Способ четвертый. Налицо в программе очень много повторяющихся почти одинаковых фрагментов. Нельзя ли написать программу как-нибудь иначе? Чем различаются эти фрагменты программы? Разными операндами в команде CMP, разными адресами переходов или разными вызываемыми процедурами. Уберем операнды команды CMP в какой-нибудь регистр и используем для разных адресов переходов косвенный переход, а для разных вызываемых процедур — косвенный вызов процедуры. Код (ASM): ;Таблица сообщений, которые обрабатывает процедура окна MsgTable dd WM_PAINT, @@WM_PAINT, WM_TIMER, @@WM_TIMER, WM_CREATE, @@WM_CREATE, WM_DESTROY, @@WM_DESTROY, 0; Конец списка обязательно 0 . . . ;Вызов соответствующего обработчика mov esi,offset MsgTable ;устанавливаем в ESI адрес таблицы-диспетчера a1: mov eax,[esi] ;устанавливаем в eax тип сообщения test eax,eax ;это конец таблицы? Если да, то данное сообщение не jz @@default; обрабатывается приложением - передаем управление Windows cmp eax,Msg jne a2 ;для данного сообщения не найден обработчик add esi,4 ;устанавливаем в esi адрес обработки сообщения jmp [esi] ;передаем управление обработчику сообщения a2: add esi,8 ;перейдем к следующему сообщению jmp short a1 @@default: leave ;Обработчик не найден, возвращается значение, jmp DefWindowProcA ; которое дает стандартный обработчик @@WM_DESTROY: Способ пятый. Используем команду SCASD с префиксом REPNE. Внесем в конец таблицы сообщений, которые обрабатывает процедура окна, адрес обработки по умолчанию, а в регистр ECX количество элементов в таблице сообщений минус один (компилятор подсчитает это число автоматически). Напомню, что команда SCASD сравнивает значение в регистре EAX с ячейкой памяти, локализуемой регистром EDI. Префикс REPNE не только заставляет циклически выполняться команду сканирования, пока ECX не станет равным 0, но и отслеживает состояние флага ZF. Как только содержимое в ячейке памяти совпадет со значением в регистре EAX, сканирование остановится. Регистр EDI будет указывать не на ячейку памяти, значение которой совпадает с регистром EAX, а на следующую ячейку. Сканирование также остановится, если содержимое регистра ECX станет равным 0. При этом в регистре EDI будет адрес метки @@default (адрес обработки по умолчанию) Код (ASM): ;Таблица сообщений, которые обрабатывает процедура окна MsgTable dd WM_PAINT, @@WM_PAINT, WM_TIMER, @@WM_TIMER, WM_CREATE, \ @@WM_CREATE, WM_DESTROY, @@WM_DESTROY, @@default MsgCount = ($ - MSGTable - 4)/4 . . . mov edi,offset MsgTable;устанавливаем в EDI адрес таблицы-диспетчера mov eax,Msg mov ecx,MsgCount;удвоенное количество обрабатываемых сообщений (4*2=8) repne scasd ;ищем сообщение и если найдем — передадим jmp [edi] ; управление обработчику сообщения @@default: leave ; если обработчик не найден, вызывается jmp DefWindowProcA ; обработка по умолчанию @@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). То есть, в общем случае, если 2n-1 < N ≤ 2n, то определить N можно не более чем за (log2(N)+2) приближение. Заметьте, увеличение скорости обработки будет сопровождаться разрастанием кода программы. Код (ASM): mas dd WM_CREATE, WM_DESTROY... WM_PAINT ;массив сообщений n = ($ - mas)/4 mas1 dd @@WM_CREATE, @@WM_DESTROY,... @@WM_PAINT;массив адресов ... xor ecx,ecx ;в ecx индекс первого элемента mov edx,n-1 ;в edx индекс последнего элемента mov eax,Msg cmp eax,WM_CREATE;сравниваем с минимальным элементом таблицы jb short @@default;если меньше, то обработка по умолчанию mov ebx,edx ;создаем индекс центрального элемента a1: cmp ecx,edx ;проверка ecx>edx на окончание поиска ja short @@default;проверены все элементы, обработка по умолчанию shr ebx,1 ;индекс центрального элемента равен (edx+ecx)/2 cmp mas[ebx*4],eax ;сравниваем с искомым значением jb short a2 ;mas[ebx*4]<Msg je short a3 ;mas[ebx*4]=Msg inc ebx ;учтем только что проверенное значение mov ecx,ebx ;изменяем нижнюю границу поиска add ebx,edx ;создаем индекс центрального элемента jmp short a1 ;переходим к следующему элементу a2: dec ebx ;учтем только что проверенное значение mov edx,ebx ;изменяем верхнюю границу поиска add ebx,ecx ;создаем индекс центрального элемента jmp short a1 ;переходим к следующему элементу @@default: leave ;искомое значение не найдено, jmp DefWindowProc ;обработка по умолчанию a3: jmp mas1[ebx*4] ;искомое значение найдено @@WM_CREATE: ... А это еще один, более короткий вариант последовательного приближения. Но для него требуется, чтобы количество сообщений должно быть равным 2n. Допустим, мы обрабатываем 16 сообщений, Количество элементов 2n-1 < N ≤ 2n ebx (индекс элемента) 2n-1 ecx (счетчик приближений) n+1 так как N = 16, то в соответствии с таблицей: n = 4, ebx = 8, ecx = 5 Код (ASM): mas dd WM_CREATE, WM_DESTROY, ... WM_PAINT;массив сообщений mas1 dd @@WM_CREATE, @@WM_DESTROY,... @@WM_PAINT ;массив адресов n equ 4 ... mov ecx,n+1 ;ecx=5 mov ebx,1 shl ebx,n-1 ;ebx=8 индекс центрального элемента xor edx,edx ;нижняя граница поиска mov eax,Msg cmp eax,WM_CREATE;сравниваем с минимальным элементом таблицы jb short @@default;если меньше, то обработка по умолчанию a1: cmp mas[edx+ebx*4],eax ;сравниваем с искомым значением ja short a2 ;mas[ebx*4]<Msg je short a3 ;mas[ebx*4]=Msg lea edx,[edx+ebx*4] ;изменяем нижнюю границу поиска a2: shr ebx,1 ;изменяем индекс центрального элемента loop a1 ;переходим к следующему элементу @@default: leave ;искомое значение не найдено jmp DefWindowProc ;обработка по умолчанию a3: jmp mas1[edx+ebx*4] ;искомое значение найдено @@WM_CREATE: ... Если Вы и дальше хотите экспериментировать с обработкой сообщений — попробуйте заменить команду cmp на команды sub, dec, test, or или and. Способ седьмой — имитация команды XLAT. При программировании на ассемблере стремятся либо получить максимально компактный код, либо достичь максимального быстродействия, хотя иногда добиваются и того, и другого. Способы четыре и пять, реализуемые через косвенный переход или косвенный вызов, хотя и имеют наименьший размер кода, но по быстродействию остаются таким же, как и способы с прямым переходом. То есть, всегда возможен вариант, когда будут перебраны все варианты сообщений, перед тем как запустится обработка сообщения по умолчанию. Реализуем мультиветвление при помощи косвенного перехода по таблице. Для увеличения скорости просмотра очереди сообщений строим процедуру подобную команде XLAT (сама команда XLAT, к сожалению, дает возможность работать только с 256 вызовами), строим таблицу MsgTable, где номер элемента таблицы соответствует номеру сообщения. Сами табличные элементы представляют собой адреса (процедур), где эти сообщения обрабатываются. Чтобы не перечислять все 400h (1024) сообщений, можно определить номер максимального сообщения Max_Msg и если номер сообщения больше максимального, сразу отправлять на обработку сообщения по умолчанию (метка @@default). Сообщения, не обрабатываемые в процедуре WinProc, также имеют адрес обработки соответствующий адресу метки @@default. При этом время выполнения перехода всегда одинаково для всех вариантов мультиветвления. Код (ASM): mov eax,Msg cmp eax,Max_Msg ja @@default;MsgTable - таблица с адресами обработки jmp MsgTable[eax*4];каждый адрес обработки расположен через 4 байта Но у этого способа есть и оборотная сторона. К сожалению, большая скорость обработки сообщений будет компенсирована раздувшейся таблицей MsgTable. P.S. Спасибо W4FhLF, Y_Mur за замечания и поправки