Урок 6b. Светомузыка на клавиатуре (глава из "Сам себе Iczelion") Кролики ― это не только ценный мех, но и три-четыре килограмма диетического мяса...Клавиатура это не только 102 клавиши, но и три волшебные лампочки: Num Lock Caps Lock Scroll Lock Если назначение ассемблера ― прямое программирование аппаратуры компьютера, то как программно зажигать и гасить лампочки Caps Lock, Num Lock и Scroll Lock на клавиатуре? Включаем лампочки функцией keybd_event с параметрами (VK_NUMLOCK (VK_CAPITAL, VK_SCROLL), 45h, KEYEVENTF_EXTENDEDKEY, 0) и гасим keybd_event с параметрами (VK_NUMLOCK (VK_CAPITAL, VK_SCROLL), 45h, (KEYEVENTF_EXTENDEDKEY or KEYEVENTF_KEYUP), 0) Функция keybd_event синтезирует нажатие клавиши. Система может использовать такое синтезируемое нажатие клавиши, чтобы создать сообщение WM_KEYUP или WM_KEYDOWN. Вызывает функцию keybd_event программа обработки прерываний драйвера клавиатуры. Синтаксис Код (C++): VOID keybd_event ( BYTE bVk, // код виртуальной клавиши BYTE bScan, // аппаратный скэн-код DWORD dwFlags, // флажки, определяющие различные параметры функции DWORD dwExtraInfo // дополнительные данные, связанные с нажатием клавиши ) Параметры bVk Определяет код виртуальной клавиши. Код должен быть значением в диапазоне от 1 до 254. В нашем случае это коды VK_NUMLOCK, VK_CAPITAL, VK_SCROLL bScan Определяет для клавиши аппаратный скэн-код. dwFlags Набор флаговых битов, которые определяют различные виды операций функции. Прикладная программа может использовать любую комбинацию следующих предопределенных постоянных значений, чтобы установить флажки: KEYEVENTF_EXTENDEDKEY ― Если она установлена, скэн-коду предшествует префиксный байт, имеющий значение 0E0h. KEYEVENTF_KEYUP ― Если установлена, клавиша была отпущена. Если не установлена, клавиша была нажата. dwExtraInfo Определяет дополнительное 32-разрядное значение, связанное с нажатием клавиши. В нашем случае ноль. Код (ASM): .686P .model flat include windows.inc includelib user32.lib includelib kernel32.lib extern _imp__MessageBoxA@16:dword extern _imp__keybd_event@16:dword extern _imp__Sleep@4:dword .code start: xor ebx,ebx; ebx=0 push ebx ;push 0 push offset sztext push offset szText push ebx ;push 0 call _imp__MessageBoxA@16 mov edi,80h ;инициируем счетчик @@: mov esi,VK_CAPITAL;счетчик=XXX1b горит CapsLock mov eax,VK_NUMLOCK;счетчик=XX1Xb горит NumLock mov ecx,VK_SCROLL ;счетчик=X1XXb горит ScrollLock test edi,1 cmovnz esi,eax test edi,2 cmovnz esi,ecx ;включаем/выключаем индикатор push ebx ;dwExtraInfo -- additional data associated with keystroke push KEYEVENTF_EXTENDEDKEY;dwFlags flags specifying various function options push 45h ;bScan -- hardware scan code push esi ;bVk -- virtual-key code call _imp__keybd_event@16;иммитируем нажатие на клавишу push ebx ;push 0 push KEYEVENTF_EXTENDEDKEY or KEYEVENTF_KEYUP push 45h push esi call _imp__keybd_event@16;иммитируем отпускание клавиши push 500 call _imp__Sleep@4;ждем полсекунды dec edi ;уменьшаем счетчик jnz @b ret sztext db "Моё первое приложение",0 szText db "Цветомузыка на Num, Caps и ScrollLock",0 end start Отладчиком заходим внутрь функции keybd_event и, спускаясь во внутрь вложенных в keybd_event функций, дойдем до int 2Eh или до команды sysenter. Внутри функции keybd_event вложена функция user32.SendInput, которая вызавает sysenter с eax=11F6h. Через int 2Eh программно зажигаем и гасим лампочки Caps Lock, Num Lock и Scroll Lock на клавиатуре. Код (ASM): .686P .model flat include windows.inc includelib user32.lib includelib kernel32.lib extern _imp__MessageBoxA@16:dword extern _imp__Sleep@4:dword .code start: xor ebx,ebx ;ebx=0 push ebx ;push 0 push offset sztext push offset szText push ebx ;push 0 call _imp__MessageBoxA@16 mov edi,80h ;инициируем счетчик @@: mov esi,VK_CAPITAL ;счетчик=XXX1b горит CapsLock mov eax,VK_NUMLOCK ;счетчик=XX1Xb горит NumLock mov ecx,VK_SCROLL ;счетчик=X1XXb горит ScrollLock test edi,1 cmovnz esi,eax test edi,2 cmovnz esi,ecx ;включаем/выключаем индикатор ;иммитируем нажатие на клавишу sub esp,1Ch mov [esp+10h],ebx;dwExtraInfo -- additional data associated with keystroke mov [esp+0Ch],ebx mov dword ptr [esp+8],KEYEVENTF_EXTENDEDKEY;dwFlags flags specifying various function options mov ecx,45FFFFh ;bScan -- hardware scan code mov cx,si ;bVk -- virtual-key code mov [esp+4],ecx mov dword ptr [esp],1 mov ecx,esp push 1Ch push ecx push 1 mov eax,11F6h ;SendInput mov edx,esp int 2Eh add esp,3Ch;выравниваю стек ;иммитируем отпускание клавиши mov dword ptr [esp+8],KEYEVENTF_EXTENDEDKEY or KEYEVENTF_KEYUP;dwFlags flags specifying various function options mov ecx,45FFFFh ;bScan -- hardware scan code mov cx,si ;bVk -- virtual-key code mov [esp+4],ecx mov dword ptr [esp],1 mov ecx,esp push 1Ch push ecx push 1 mov eax,11F6h ;SendInput mov edx,esp int 2Eh sub esp,8 ;выравниваю стек push 500 call _imp__Sleep@4 ;ждем полсекунды dec edi ;уменьшаем счетчик jnz @b ret sztext db "Моё первое приложение",0 szText db "Цветомузыка на Num, Caps и ScrollLock",0 end start Через драйвер ― в системе уже есть драйвер управления клавиатурой "\\.\KbdGarland". Поэтому сам драйвер писать не придется. В KmdKit от Four-F есть пример управления состоянием светодидами на клавиатуре. Это модификация этого примера. Код (ASM): .686P .model flat include windows.inc include w2k\ntddkbd.inc includelib user32.lib includelib kernel32.lib extern _imp__MessageBoxA@16:dword extern _imp__keybd_event@16:dword extern _imp__Sleep@4:dword extern _imp__CreateFileA@28:dword extern _imp__DefineDosDeviceA@12:dword extern _imp__DeviceIoControl@32:dword extern _imp__CloseHandle@4:dword FILE_DEVICE_KEYBOARD equ 0000000Bh FILE_ANY_ACCESS equ 0 METHOD_BUFFERED equ 0 .code start: xor ebx,ebx ; ebx=0 push ebx ;push 0 push offset sztext push offset szText push ebx ;push 0 call _imp__MessageBoxA@16 push offset szText3 push offset szText4+4 push DDD_RAW_TARGET_PATH call _imp__DefineDosDeviceA@12 test eax,eax jnz short @f push MB_ICONEXCLAMATION push ebx push offset szText1 push ebx call _imp__MessageBoxA@16 retn @@: push ebx push ebx push OPEN_EXISTING push ebx push ebx push ebx push offset szText4 call _imp__CreateFileA@28 inc eax ;.if eax != INVALID_HANDLE_VALUE jnz @f push MB_ICONEXCLAMATION push ebx push offset szText5 push ebx call _imp__MessageBoxA@16 jmp short a6 ;.else @@: dec eax mov ebp,eax push ebx push offset dwBytesReturned push sizeof kip mov esi,offset kip push esi push ebx push ebx push IOCTL_KEYBOARD_QUERY_INDICATORS push eax ;hDevice call _imp__DeviceIoControl@32 test eax,eax jz a2 cmp dwBytesReturned,ebx jz a2 assume esi:ptr KEYBOARD_INDICATOR_PARAMETERS movzx eax,[esi].LedFlags push eax ;mov LedFlags,eax push 5 pop edi @@: mov [esi].LedFlags, KEYBOARD_NUM_LOCK_ON call Do mov [esi].LedFlags, KEYBOARD_CAPS_LOCK_ON call Do mov [esi].LedFlags, KEYBOARD_SCROLL_LOCK_ON call Do dec edi jnz @b ;.endw pop eax ;mov eax,LedFlags mov [esi].LedFlags,ax ;restore push ebx push offset dwBytesReturned push ebx push ebx push sizeof kip push esi ;adr kip push IOCTL_KEYBOARD_SET_INDICATORS push ebp ;hDevice call _imp__DeviceIoControl@32 assume esi:nothing a2: push ebp ;hDevice call _imp__CloseHandle@4 a6: push ebx push offset szText4+4 push DDD_REMOVE_DEFINITION call _imp__DefineDosDeviceA@12 ret Do proc push ebx push offset dwBytesReturned push ebx push ebx push sizeof kip push esi;adr kip push IOCTL_KEYBOARD_SET_INDICATORS push ebp;hDevice call _imp__DeviceIoControl@32 push 100 call _imp__Sleep@4 retn Do endp sztext db "Моё первое приложение",0 szText db "Цветомузыка на Num, Caps и ScrollLock",0 szText1 db "Couldn't define link to keyboard device",0 szText3 db "\Device\KeyboardClass0",0 szText4 db "\\.\KbdGarland",0 szText5 db "Couldn't open keyboard device",0 dwBytesReturned dd 0 kip KEYBOARD_INDICATOR_PARAMETERS <> end start Еще один "низкоуровневый" способ зажигать и гасить лампочки на клавиатуре Caps Lock, Num Lock и Scroll Lock заключается в записи байта в порт 60h, младшие три бита этого байта определяют состояние светодиодов на лицевой панели клавиатуры. Но WinXP, в отличии от DOS, не "переваривает" обращение к портам ввода/вывода от user-mode программ. Поэтому придется использовать драйвер, который спрячем в ресурсах и вытащим из ресурсов на диск при запуске приложения, по завершению приложения драйвер будем удалять Пример использования, исходные тексты во вложении
Вай, не ожидал такого полного и развернутого ответа, очень порадовало, что есть все таки знающие люди.. Да и за книгу сеньк, вот нашел где почитать http://www.cyberforum.ru/assembler/thread751124.html
Andrei, это статья от 2012 года, писалось и проверялось под 32-разрядную Windows XP. Будет ли работать стабильно сейчас, не знаю, не уверен. На ЕХЕ-файл антивирусник обязательно будет верещать, так как у него в ресурсах скрытый драйвер, который устанавливается при запуске ЕХЕ чтобы дать доступ к 60h порту в юзермоде. Как только ЕХЕ закрывается, драйвер уничтожается... Что касается статей на сайберфоруме, то ничего кроме оглавления и первой главы прочитать не удастся
Не могу в TASM собрать первый example, для какого компилятора код ? 15.06.2018 6:44:09 : Assembling file - C:\Users\redd\Desktop\caps.asm 15.06.2018 6:44:09 : Turbo Assembler Version 4.1 Copyright (c) 1988, 1996 Borland International 15.06.2018 6:44:10 : Assembling file: caps.asm 15.06.2018 6:44:10 : **Error** caps.asm(2) 32-bit segments not allowed without .386 15.06.2018 6:44:10 : **Fatal** caps.asm(3) Can't locate file: windows.inc 15.06.2018 6:44:10 : Error messages: 2 15.06.2018 6:44:10 : Warning messages: None 15.06.2018 6:44:10 : Passes: 1 15.06.2018 6:44:10 : Remaining memory: 452k
window.inc нашел, в masm Но все равно 15.06.2018 7:08:43 : Assembling file - C:\Users\redd\Desktop\caps.asm 15.06.2018 7:08:47 : Turbo Assembler Version 4.1 Copyright (c) 1988, 1996 Borland International 15.06.2018 7:08:47 : Assembling file: caps.asm 15.06.2018 7:08:47 : *Warning* windows.inc(54) Pass-dependent construction encountered: _WININC_ 15.06.2018 7:08:47 : **Error** windows.inc(74) Can't use this outside macro 15.06.2018 7:08:48 : **Error** windows.inc(75) Can't use this outside macro 15.06.2018 7:08:48 : **Error** windows.inc(77) Illegal forward reference: PROTO 15.06.2018 7:08:48 : **Error** windows.inc(113) Illegal forward reference: PROTO 15.06.2018 7:08:48 : **Error** windows.inc(114) Illegal forward reference: PROTO 15.06.2018 7:08:48 : *Warning* windows.inc(293) Reserved word used as symbol: PWORD 15.06.2018 7:08:48 : **Error** windows.inc(326) Illegal forward reference: REAL4 15.06.2018 7:08:48 : **Error** windows.inc(327) Illegal forward reference: REAL8 15.06.2018 7:08:48 : Error messages: 7 15.06.2018 7:08:49 : Warning messages: 2 15.06.2018 7:08:49 : Passes: 1 15.06.2018 7:08:49 : Remaining memory: 116k 15.06.2018 7:08:49 : **Fatal** Out of hash space
Andrei, написано для masm bat-файл, которым пользовался на тот момент Код (Text): cls set filename=%1 call :read_settings %filename% @echo %compiler% %os% %kind_of_file% if exist %filename%.exe del %filename%.exe if exist %filename%.com del %filename%.com if exist %filename%.dll del %filename%.dll set %compiler%_path=d:\Aquila goto :%compiler%%os%%kind_of_file% :wasmwindowsgui %wasm_path%\bin\wasm -6prs -mf -i%wasm_path%\include\WASM %filename%.asm || exit %wasm_path%\bin\wlink file %filename%.obj form windows nt op c LIBPath ^ %wasm_path%\lib\ Library user32.lib,kernel32.lib,gdi32.lib goto :m1 :fasmdoscom :fasmdosexe %fasm_path%\bin\fasm %filename%.asm exit :goasmwindowsgui %goasm_path%\bin\GoAsm %filename% if exist %1.rc ( %goasm_path%\bin\GoRc /r %filename%.rc || exit %goasm_path%\bin\GoLink %filename%.obj %filename%.res user32.dll kernel32.dll ^ gdi32.dll comctl32.dll shell32.dll ole32.dll comdlg32.dll || exit del %filename%.res ) else ( %goasm_path%\bin\GoLink %filename%.obj user32.dll kernel32.dll gdi32.dll ^ comctl32.dll shell32.dll ole32.dll comdlg32.dll ) goto :m1 :goasmwindowsconsole %goasm_path%\bin\GoAsm %filename% %goasm_path%\bin\GoLink /console %filename%.obj kernel32.dll user32.dll ^ winmm.dll gdi32.dll goto :m1 :lzasmwindowsconsole :lzasmwindowsgui %lzasm_path%\bin\lzasm %filename%.asm %lzasm_path%\bin\alink %filename%.obj -oPE -c -subsys %kind_of_file% goto :m1 :lzasmdoscom %lzasm_path%\bin\lzasm %filename%.asm %lzasm_path%\bin\alink %filename%.obj -oCOM -c goto :m1 :lzasmdosexe %lzasm_path%\bin\lzasm %filename%.asm %lzasm_path%\bin\alink %filename%.obj -oEXE -c goto :m1 :masmwindowsgui set masm_path=d:\masm32 if exist %filename%.rc ( %masm_path%\bin\rc /v %1.rc %masm_path%\bin\cvtres /machine:ix86 %1.res %masm_path%\bin\ml /c /Cp /Gz /I%masm_path%\include /coff /nologo %filename%.asm || exit %masm_path%\bin\PoLink1 /SUBSYSTEM:WINDOWS /ALIGN:4 /MERGE:.data=.text ^ /LIBPATH:%masm_path%\lib /NOLOGO /STUB:%masm_path%\bin\stubby.exe %1.obj %filename%.res || exit del %filename%.res ) else ( %masm_path%\bin\ml /c /Cp /Gz /I%masm_path%\include /coff /nologo %filename%.asm || exit %masm_path%\bin\PoLink1 /SUBSYSTEM:WINDOWS /ALIGN:4 /MERGE:.data=.text ^ /LIBPATH:%masm_path%\lib /NOLOGO /STUB:%masm_path%\bin\stubby.exe %filename%.obj ) goto :m1 :tasmwindowsgui %tasm_path%\bin\tasm32 /I%tasm_path%\include\TASM /q /z %filename%.asm %filename%.obj /ml/m3 %tasm_path%\bin\ilink32 -L%tasm_path%\lib /Tpe /aa /c /o /x %filename%.obj goto :m1 :tasmwindowsconsole %tasm_path%\bin\tasm32 /I%tasm_path%\include\TASM %filename%.asm /ml /m3 /q /z %filename%.obj %tasm_path%\bin\ilink32 -L%tasm_path%\lib /Tpe /ap /c /o /x %filename%.obj goto :m1 :nasmwindowsconsole :nasmwindowsgui %nasm_path%\bin\nasmw -O1 -f bin %filename%.asm -o %filename%.exe ^ -I%nasm_path%\include\NASM\ exit :nasmdoscom :nasmdosexe :nasmwindowsdll %nasm_path%\bin\nasmw -f bin %filename%.asm -o %filename%.%kind_of_file% ^ -I%nasm_path%\include\NASM\ exit :tasmdoscom %tasm_path%\bin\tasm %filename%.asm %tasm_path%\bin\tlink /t/x %filename%.obj goto :m1 :tasmdosexe %tasm_path%\bin\tasm %filename%.asm %tasm_path%\bin\tlink /x %filename%.obj goto :m1 :wasmdoscom %wasm_path%\bin\wasm %filename%.asm %wasm_path%\bin\wlink file %filename%.obj form dos com goto :m1 :wasmdosexe %wasm_path%\bin\wasm %filename%.asm %wasm_path%\bin\wlink file %filename%.obj form dos goto :m1 :poasmwindowsconsole %poasm_path%\bin\poasm /Gz /I%poasm_path%\include\PoAsm %filename%.asm %poasm_path%\bin\polink /SUBSYSTEM:CONSOLE /LIBPATH:%poasm_path%\lib /MERGE:.data=.text %filename%.obj goto :m1 :masmdoscom %masm_path%\bin\ml /AT /c %filename%.asm %masm_path%\bin\link16 /T %filename%.obj,,,,, goto :m1 :masmdosexe %masm_path%\bin\ml /c %filename%.asm %masm_path%\bin\link16 %filename%.obj,,,,, :m1 if exist %filename%.map del %filename%.map if exist %filename%.obj del %filename%.obj if exist %filename%.ilc del %filename%.ilc if exist %filename%.ild del %filename%.ild if exist %filename%.ilf del %filename%.ilf if exist %filename%.ils del %filename%.ils if exist %filename%.tds del %filename%.tds exit :read_settings for /f "eol=# tokens=2-5" %%A in (%filename%.asm) do ( set compiler=%%A set os=%%B set kind_of_file=%%C if %%D == # exit /b ) exit /b там в первой строке каждого asm-файла стоит Код (ASM): ; masm windows gui #
Andrei, это универсальный bat-файл, он по первой строке определяет компилятор (masm, tasm, fasm и т.д.), требуемую ось (Win, DOS) и требуемый тип файла (COM, DLL, SYS, GUI-EXE, CONSOLE-EXE и т.д.) и передает управление на ту строку, которая нужна юзеру
Вот нельзя из под Win с портами работать, а ежели писать напрямую в параллельный порт, какой там у него адрес 378 или 3f8 вроде, сработает ??? Ну win позволит писать в параллельный порт ну или RS232 напрямую ?
Mikl___, > Но WinXP, в отличии от DOS, не "переваривает" обращение к портам ввода/вывода от user-mode программ. Как раз таки XP позволяла открыть прямой доступ к портам и даже не одним путём. Можно было напрямую сервисным вызовом из юм изменить IOPL, из км можно было перенастроить IOPM. Вот только такое нельзя было отлаживать, ну или нужно было делать аккуратно - cli в олли вешала всю систему. Andrei, > Вот нельзя из под Win с портами работать, а ежели писать напрямую в параллельный порт, какой там у него адрес 378 или 3f8 вроде Это мера безопасности, порты не должно трогать приложение. Во первых для него их нет, есть слой абстракции от железа и причём не один, оно виртуальное. Обходной путь конечно же найти можно, но так не следует делать. Нужно собрать драйвер, который будет обслуживать железку. Через него вы будете с ней работать. Адреса старого lpt изменялись, их нужно было брать из конфигурации. Сейчас таких портов нет, всюду usb. Можно взять готовый контроллер, который реализует usb интерфейс в параллельный gpio, они стоят копейки. При этом нет рисков упалить всё, подключая не понятные девайсы к портам.
Indy_, WinXP сейчас почти исчез, может быть поделитесь, как в ХР в юм можно было открыть прямой доступ к аппаратуре (портам)? Просто перечислите способы (можно и через ЛС)