一、基于windows消息机制的鼠标键盘模拟(一)、针对指定程序(窗口)模拟(PostMessage)——局部模拟(可后台)1、模拟键盘按键。2、模拟鼠标3、例子4、附录:获取窗口句柄handle1)WindowFromPoint2)FindWindow/FindWindowEx或者用GetWindow来遍历查找,3)通过进程的文件名来查找窗口句柄。(二)全局模拟1、用API函数keybd_event和mouse_event。1)函数keybd_event模拟键盘2)mouse_event模拟按下鼠标2、SendInput函数也可以模拟全局键盘鼠标事件。1)键盘模拟2)鼠标模拟3)附录:SIMouseKeyboard.pas的使用——利用SendInput模拟鼠标键盘的输入4)SendInput模拟键盘输入应注意的问题5)SendInput与WInIo的对比3、用全局钩子也可以模拟键盘消息。二、驱动级模拟(绕过windows消息)——WINIO模拟按键
*******************************************************************************************
我们怎样才能用Delphi来写一个程序,用来代替人们按键的方法呢?那就让我们来先了解一下windows中响应键盘事件的机制。
这个过程可以简单的如下表示:
用户按下键盘上的一个键>>>>>键盘控制器就把这个键的扫描码传给计算机,然后交给键盘驱动程序>>>>>键盘驱动程序会把这个扫描码转换为键盘虚拟码(VK_A,VK_B这样的常数,比如键A的虚拟码是65,写成16进制就是&H41)传给操作系统>>>>>操作系统则会把这些信息封装在一个消息中,并把这个键盘消息插入到消息列队>>>>>键盘消息被发送到当前活动窗口>>>>>活动窗口所在的应用程序接收到这个消息后,就知道键盘上哪个键被按下,也就可以决定该作出什么响应给用户了。
明白了这个过程,我们就可以编程实现在其中的某个环节来模拟键盘操作了。在Delphi中,有多种方法可以实现键盘模拟,我们就介绍几种比较典型的。
windows提供了几个这样的API函数可以实现直接向目标程序发送消息的功能,常用的有SendMessage和PostMessage,它们的区别是PostMessage函数直接把消息仍给目标程序就不管了,而SendMessage把消息发出去后,还要等待目标程序返回些什么东西才好。这里要注意的是,模拟键盘消息一定要用PostMessage函数才好,用SendMessage是不正确的(因为模拟键盘消息是不需要返回值的,不然目标程序会没反应),切记切记!
PostMessage(
hWnd:HWND;{目标程序上某个控件的句柄}
Msg:UINT;{消息的类型}
wParam:WPARAM;{32位指定附加的消息特定的信息}
lParam:LPARAM{32位指定附加的消息特定的信息}
):BOOL;
参数hwnd是你要发送消息的目标程序上某个控件的句柄,
参数Msg是消息的类型,表示你要发送什么样的消息,
参数wParam和lParam这两个参数是随消息附加的数据,具体内容要由消息决定。
参数Msg这个,要模拟按键就靠这个了。
键盘消息参数Msg常用的有如下几个:
WM_KEYDOWN表示一个普通键被按下
WM_KEYUP表示一个普通键被释放
WM_SYSKEYDOWN表示一个系统键被按下,比如Alt键
WM_SYSKEYUP表示一个系统键被释放,比如Alt键
鼠标消息参数Msg:
WM_LBUTTONDOWN//左键按下
WM_RBUTTONDOWN//右键按下
WM_MBUTTONDOWN//中间键按下
WM_LButtonUp//左键放开
WM_RbuttonUp//右键放开
WM_MButtonUp//中键放开
WM_LBUTTONDBLCLK//左键双击
WM_RBUTTONDBLCLK//左键双击
WM_MBUTTONDBLCLK//中键双击
应用程序从Windows接收的关于键盘事件的消息可分为击键消息和字符消息两种。这与你看待键盘的两种方式是一致的。首先,你可以认为键盘是键的集合。键盘上仅有一个键表示为“A”。按下此键是一次击键,释放此键也认为是一次击键。同时键盘也是能产生可显示字符或者控制字符的输入设备。有些键不产生字母,如Shift键、功能键、光标移动键和特殊字符键(如Insert键和Delete键)。对于这些键,Windows只产生击键消息。
一般有以下四个击键消息:
WM_KEYDOWN、WM_SYSKEYDOWN、WM_KEYUP、WM_SYSKEYUP。这里分为系统击键消息和非系统击键消息。而非系统击键消息是我们常用的。WM_KEYDOWN和WM_KEYUP消息通常是在按下或释放不带Alt键的键时产生;WM_SYSKEYDOWN和WM_SYSKEYUP消息通常由与Alt组合的击键产生,这些键激活程序菜单或系统菜单上的选项,或切换活动窗口,也可以用作系统菜单加速键。由于Windows处理所有Alt键的功能,应用程序无需捕获这些消息。
例如:
单个按健:PostMessage(h,WM_KEYDOWN,VK_F9,0);
ALT+按键:PostMessage(h,WM_SYSKEYDOWN,70,$20000000);
格式::PostMessage(MyHwnd,WM_KEYDOWN,wParam,lParam)
如果你确定要发送键盘消息,那么再来看看如何确定键盘消息中的wParam和lParam这两个参数。
wParam参数的含义较简单,它表示你要发送的键盘事件的按键虚拟码,比如你要对目标程序模拟按下A键,那么wParam参数的值就设为VK_A,
lParam这个参数就比较复杂了,因为它包含了多个信息,一般可以把它设为0。即PostMessage(Hwnd,WM_KEYDOWN,key,0);
但是如果你想要你的模拟更真实一些,或者你向记事本的文本框发送字符消息。那么你还需要设置一下这个参数。那么我们就详细了解一下lParam吧。
lParam这个参数就比较复杂了,因为它包含了多个信息。如果按住一个键不放,会使得自动重复功能生效,那么该键最后被释放时,Windows会给窗口过程发送一系列的WM_KEYDOWN(或WM_SYSKEYDWON)消息和一个WM_KEYUP(或WM_SYSKEYUP)消息。如果是发送“击键”,不需要产生字符,一般可以把它设为0。即PostMessage(Hwnd,WM_KEYDOWN,key,0);而对于需要产生“字符”的击键消息,比如如向记事本发送字符“A”,要设置参数lParam,否则会产生一系列A。那么我们就详细了解一下lParam吧。
lParam是一个32bit的参数,它在内存中占4个字节,写成二进制就是
00000000000000000000000000000000
大家一般习惯写成16进制的,那么就应该是
&H00000000
一共是32位,我们从右向左数,假设最右边那位为第0位(注意是从0而不是从1开始计数),最左边的就是第31位。那么该参数的
0-15位表示键的发送次数等扩展信息//一般为&H0001如果不设置发送次数,因为按下键盘键,会自动触发重复发送消息。比如你向记事本的文本框发送字符消息消息时会出现多个字符。
16-23位为按键的扫描码
24-31位表示是按下键还是释放键。按下键&H00,释放键则为&HC0,
MapVirtualKey(
uCode:UINT;{键值、扫描码或虚拟码keycode,scancodeorvirtualkey}
uMapType:UINT{flagsfortranslationmode}
):UINT;{returnstranslatedkeycode}
参数uCode表示待转换的码,参数uMapType表示从什么转换为什么,如果是虚拟码转扫描码,则uMapType设置为0,如果是扫描码转虚拟码,则wMapType设置为1,如果是虚拟码转ASCII码,则uMapType设置为2。相信有了这些,我们就可以构造键盘事件的lParam参数了。
下面给出一个构造lParam参数的函数:
functionVKB_param(VirtualKey:Integer;flag:Integer):Integer;//函数名
var
s,Firstbyte,Secondbyte:String;
S_code:Integer;
Begin
ifflag=1then//按下键
begin
Firstbyte:='00'
end
else//弹起键
Firstbyte:='C0'
end;
S_code:=MapVirtualKey(VirtualKey,0);
Secondbyte:='00'+inttostr(s_code);
Secondbyte:=copy(Secondbyte,Length(Secondbyte)-1,2);
s:='$'+Firstbyte+Secondbyte+'0001';
Result:=strtoint(s);
End;
使用按键的方法:
说明:key为键值,如2键的值是$32,flag传递的是按键状态,1是按下,0是弹起。
lparam:=VKB_param(key,1);{按下键}
PostMessage(Hwnd,WM_KEYDOWN,key,lParam);
lParam:=VKB_param(key,0);{松开键}
PostMessage(Hwnd,WM_KEYUP,key,lParam);
hwnd,lparam:Cardinal;
//hwnd:=FindWindow(nil,'无标题-记事本');//这个函数不能在记事本输入。因为记事本里接收字符的是子窗口edit。
hwnd:=FindWindowEx(FindWindow(nil,'无标题-记事本'),0,'edit',nil);//获取记事本窗口句柄
lparam:=VKB_param(97,1);//按下键
//PostMessage(hwnd,WM_KEYDOWN,vk_F3,lparam);//按下F3键
PostMessage(hwnd,WM_CHAR,97,lparam);//输入字符(edit控件接收字符)
lParam:=VKB_param(97,0);//松开键
//PostMessage(hwnd,WM_KEYUP,vk_F3,lparam);//释放F3键
////////////////////////////////////
FindWindow(
lpClassName,{窗口的类名}
lpWindowName:PChar{窗口的标题}
):HWND;{返回窗口的句柄;失败返回0}
//FindWindowEx比FindWindow多出两个句柄参数:
FindWindowEx(
Parent:HWND;{要查找子窗口的父窗口句柄}
Child:HWND;{子窗口句柄}
ClassName:PChar;{窗口的类名}
WindowName:PChar{窗口的标题}
):HWND;
///////////////////////////////
Msg模拟鼠标点击消息参数:
/////////
wparam模拟鼠标点击设置为0
Lparam参数为鼠标坐标。
对lparam的描述是:
lParam:低16位存放X坐标,高16位存放Y坐标
那么0到15位就是低16位,16到31就是高16位。
比如你有个坐标p.x=290p.y=48。那么换成2进制
p.x=00000000000000000000000100100010
p.y=00000000000000000000000000110000
因为刚才说了lParam高16位是要存放y坐标的值,低16位存放X坐标的值。这样才能组成一个正确的lParam.
所以就要把p.y的值放到高16位去。怎么放呢?就是把p.y的低16的值全部左移16位移到高16位去。运算:p.xshl16;移动之后就变成
p.y=00000000001100000000000000000000
然后把p.y和p.x的值组合成一个新的整数。lparam:=p1.X+p1.Yshl16;
lParam=00000000001100000000000100100010
前面的是p.y的二进制数表示,后面是p.x的二进制表示。
----------------------------------------------------------------------------------------------------------------------
{补充小知识:}
shl左移位shr右移位
shl是按位左移,右边补零shr是按位右移,左边补零
左移一位等于乘2,右移一位等于除2。
左移2位等于乘2的2次方,右移一位等于除2的2次方。
左移n位等于乘2的n次方,右移n位等于除2的n次方。
所以lparam:=p1.X+p1.Yshl16也可以用:lparam:=p1.X+p1.Y*power(2,16)(即lparam:=p1.X+p1.Y*65536)
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Var
P1:Tpoint;
Lparam:integer;
GetCursorPos(P1);//获取屏幕坐标
P1.X:=P1.X+100;
P1.Y:=P1.Y+200;
lparam:=p1.X+p1.Yshl16;
sendmessage(h,messages.WM_LBUTTONDOWN,0,lparam);//按下鼠标左键
sendmessage(h,messages.WM_LBUTTONUP,0,lparam);//抬起鼠标左键
{-------------------------------------------}
{模拟键盘鼠标过程}
unitUAnalogKeyboardAndMouseDM;
interface
uses
Windows,Messages;
type
(*定义鼠标入键盘事件常量*)
TClickType=(leftDown,rightDown,midDown,leftUp,rightUp,midUp,leftDB,rightDB,midDB,vkeyDown,vkeyUp,vKeyClick,pageUp,PageDown);
procedureSendMouseClick(constWinHandle:HWND;constPosX,PosY:integer;constClickFlag:TClickType);
procedureSendKey(constWinHandle:HWND;constVkey:word;
constKeyClickFlag:TClickType);
implementation
{-------------------}
{*模拟鼠标*}
caseClickFlagof
leftDown:
PostMessage(WinHandle,WM_LButtonDown,0,PosX+PosY*65536);//左键按下
leftUp:
PostMessage(WinHandle,WM_LButtonUp,0,PosX+PosY*65536);//左键放开
//-----
rightDown:
PostMessage(WinHandle,WM_RButtonDown,0,PosX+PosY*65536);//右键按下
rightUp:
PostMessage(WinHandle,WM_RButtonUp,0,PosX+PosY*65536);//右键放开
midDown:
PostMessage(WinHandle,WM_MBUTTONDOWN,0,PosX+PosY*65536);//中间键按下
midUp:
PostMessage(WinHandle,WM_MButtonUp,0,PosX+PosY*65536);//中键放开
leftDB:
PostMessage(WinHandle,WM_LBUTTONDBLCLK,0,PosX+PosY*65536);//左键双击
rightDB:
PostMessage(WinHandle,WM_RBUTTONDBLCLK,0,PosX+PosY*65536);//右键双击
midDB:
PostMessage(WinHandle,WM_MBUTTONDBLCLK,0,PosX+PosY*65536);//中键双击
{*模拟键盘*}
caseKeyClickFlagof
vkeyDown:postMessage(WinHandle,WM_KEYDOWN,vkey,MapVirtualKey(Vkey,0));
vkeyUp:postMessage(WinHandle,WM_KEYUP,vkey,MapVirtualKey(Vkey,0));
vkeyClick:
postMessage(WinHandle,WM_KEYDOWN,vkey,MapVirtualKey(Vkey,0));
postMessage(WinHandle,WM_KEYUP,vkey,MapVirtualKey(Vkey,0));
end.
利用PostMessage模拟键盘鼠标的关键是获取窗口句柄handle。那么如何获取窗口(或子窗口)句柄呢
获取handle(窗口句柄)的方法通常有:
这个函数能够找出鼠标当前位置所对应的窗口句柄。
如:
handle:=FindWindow(nil,PChar('窗口的标题'));
或者:
procedureTForm1.Button1Click(Sender:TObject);
hCurrentWindow:HWnd;
WndText:String;
hCurrentWindow:=GetWindow(Handle,GW_HWNDFIRST);
whilehCurrentWindow<>0do
WndText:=GetWndText(hCurrentWindow);
ifUpperCase(WndText)='窗口的标题'thenbegin
...
hCurrentWindow:=GetWindow(hCurrentWindow,GW_HWNDNEXT);
//==========================================================
ClassName:PChar;{}
WindowName:PChar{}
{
如果Parent是0,则函数以桌面窗口为父窗口,查找桌面窗口的所有子窗口;
如果Parent是HWND_MESSAGE,函数仅查找所有消息窗口;
Child子窗口必须是Parent窗口的直接子窗口;
如果Child是0,查找从Parent的第一个子窗口开始;
如果Parent和Child同时是0,则函数查找所有的顶层窗口及消息窗口.
}
//测试1:试着找找新建程序主窗口的句柄
h:HWND;
{现在我们知道窗口的标题是:Form1、窗口的类名是:TForm1}
h:=FindWindow('TForm1','Form1');
ShowMessage(IntToStr(h));{656180;这是随机,每次启动窗口肯定不一样}
{假如不知道类名}
h:=FindWindow(nil,'Form1');
ShowMessage(IntToStr(h));{656180}
{假如不知道标题名}
h:=FindWindow('TForm1',nil);
{其实这个窗口的句柄不就是Self.Handle吗}
ShowMessage(IntToStr(Handle));{656180}
//测试2:找计算器窗口的句柄(先打开计算器)
{如果不是简体中文系统,这样可能不灵}
h:=FindWindow(nil,'计算器');
ShowMessage(IntToStr(h));{1508334}
{最好这样,但你得提前知道计算器窗口的类名是:SciCalc}
h:=FindWindow('SciCalc',nil);
首先通过进程快照得到要查找的进程ID(ProcessId),
其次,再跟据ProcessId获取进程的窗口句柄。以下为代码:
usesTLHelp32;//注意加上这个模块
ProcessName:string;//进程名
FSnapshotHandle:THandle;//进程快照句柄
FProcessEntry32:TProcessEntry32;//进程入口的结构体信息
ContinueLoop:BOOL;
MyHwnd:THandle;
FSnapshotHandle:=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);//创建一个进程快照
FProcessEntry32.dwSize:=Sizeof(FProcessEntry32);
ContinueLoop:=Process32First(FSnapshotHandle,FProcessEntry32);//得到系统中第一个进程
//循环例举
whileContinueLoopdo
ProcessName:=FProcessEntry32.szExeFile;
if(ProcessName='要找的应用程序名.exe')thenbegin
MyHwnd:=GetHWndByPID(FProcessEntry32.th32ProcessID);
ContinueLoop:=Process32Next(FSnapshotHandle,FProcessEntry32);
CloseHandle(FSnapshotHandle);//释放快照句柄
//跟据ProcessId获取进程的窗口句柄
functionTForm1.GetHWndByPID(consthPID:THandle):THandle;
PEnumInfo=^TEnumInfo;
TEnumInfo=record
ProcessID:DWORD;
HWND:THandle;
functionEnumWindowsProc(Wnd:DWORD;varEI:TEnumInfo):Bool;stdcall;
PID:DWORD;
GetWindowThreadProcessID(Wnd,@PID);
Result:=(PID<>EI.ProcessID)or
(notIsWindowVisible(WND))or
(notIsWindowEnabled(WND));
ifnotResultthenEI.HWND:=WND;
functionFindMainWindow(PID:DWORD):DWORD;
EI:TEnumInfo;
EI.ProcessID:=PID;
EI.HWND:=0;
EnumWindows(@EnumWindowsProc,Integer(@EI));
Result:=EI.HWND;
ifhPID<>0then
Result:=FindMainWindow(hPID)
else
Result:=0;
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
即所有窗口都可以接收到键盘模拟。所以在利用全局模拟时有必要先把接收模拟键盘鼠标的窗口置顶。
模拟全局键盘和鼠标消息常见的可以有以下一些方法:
keybd_event(
bVk:Byte;{虚拟键值}
bScan:Byte;{扫描码}
dwFlags:DWORD;{标志选项}
dwExtraInfo:DWORD{关于键的附加选项}
);{这个过程没有返回值}
keybd_event(VK_A,0,0,0);//按下A键
Sleep(200);
keybd_event(VK_A,0,KEYEVENTF_KEYUP,0);//释放A键
Sleep(
dwMilliseconds:DWORD{指定要暂停的毫秒数}
);
那么如果要模拟按下功能键怎么做呢?比如要按下Ctrl+C实现拷贝这个功能,可以这样:
keybd_event(VK_Ctrl,0,0,0);//按下Ctrl键
keybd_event(VK_C,0,0,0);//按下C键
Sleep(500);//延时500毫秒
keybd_event(VK_C,0,KEYEVENTF_KEYUP,0);//释放C键
keybd_event(VK_Ctrl,0,KEYEVENTF_KEYUP,0);//释放Ctrl键
好了,现在你可以试试是不是可以骗过目标程序了,这个函数对大部分的窗口程序都有效,可是仍然有一部分游戏对它产生的键盘事件熟视无睹,这时候,你就要用上bScan这个参数了。
一般的,bScan都传0,但是如果目标程序是一些DirectX游戏,那么你就需要正确使用这个参数传入扫描码,用了它可以产生正确的硬件事件消息,以被游戏识别。这样的话,就可以写成这样:
keybd_event(VK_A,MapVirtualKey(VK_A,0),0,0);//按下A键
keybd_event(VK_A,MapVirtualKey(VK_A,0),KEYEVENTF_KEYUP,0);//释放A键
以上就是用keybd_event函数来模拟键盘事件。
mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);//鼠标左键按下
mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);//鼠标左键弹起
{===========================================
模拟多次点击鼠标左键。默认1次。
============================================}
procedureSeriesLeftClick(i:Byte=1);
f:Byte;
forf:=1toido
Mouse_Event(MouseEventf_leftDown,0,0,0,0);
Mouse_Event(MouseEventf_leftUp,0,0,0,0);
SendInput可以直接把一条消息插入到消息队列中,算是比较底层的了。
functionSendInput(
cInputs:UINT;//pInputs中记录数组的元素数目
varpInputs:TInput;//TInput类型记录数组的第1个元素
cbSize:Integer//定义TInput的大小,一般为SizeOf(TInput)
):UINT;stdcall;
cInputs:定义pInputs中记录数组的元素数目。
pInputs:TInput类型记录数组的第1个元素。每个元素代表插人到系统消息队列的键盘或鼠标事件。
cbSize:定义TInput的大小,一般为SizeOf(TInput)。函数返回成功插入系统消息队列中事件的数目,失败返回0。
tagINPUT=packedrecord
Itype:DWORD;
caseIntegerof
0:(mi:TMouseInput);
1:(ki:TKeybdInput);
2:(hi:THardwareInput);
TInput=tagINPUT;
其中mi、ki、hi是3个共用型的记录结构,Itype指出记录结构中所使用的类型,它有3个值。
INPUT_MOUSE:表示使用mi记录结构,忽略ki和hi;
INPUT_KEYBOARD:表示使用ki记录结构,忽略mi和hi。
tagKEYBDINPUT=packedrecord
wVk:WORD;//是将要操作的按键的虚键码
wScan:WORD;//是安全码,一般不用
dwFlags:DWORD;//指定键盘所进行的操作,为0时表示按下某键,KEYEVENTF_KEYUP表示放开某键
time:DWORD;
dwExtraInfo:DWORD;//是扩展信息,可以使用API函数GetMessageExtraInfo的返回值
TKeybdInput=tagKEYBDINPUT;
procedureKeyPressA;
Inputs:array[0..1]ofTInput;
Inputs[0].Itype:=INPUT_KEYBOARD;
withInputs[0].kido
wVk:=VK_A;
wScan:=0;
dwFlags:=0;
time:=GetTickCount;
dwExtraInfo:=GetMessageExtraInfo;
Inputs[1].Itype:=INPUT_KEYBOARD;
withInputs[1].kido
dwFlags:=KEYEVENTF_KEYUP;
SendInput(2,Inputs[0],SizeOf(TInput));
tagMOUSEINPUT=packedrecord
dx:Longint;
dy:Longint;
mouseData:DWORD;
dwFlags:DWORD;
dwExtraInfo:DWORD;
TMouseInput=tagMOUSEINPUT;
其中dx、dy是鼠标移动时的坐标差(不是象素单位),在鼠标移动时有效。
mouseData是鼠标滚轮滚动值,在滚动鼠标滚轮时有效。当mouseData小于0时向下滚动,当mouseData大于0时向上滚动,mouseData的绝对值一般设为120。
dwFlags指定鼠标所进行的操作,例如,MOUSEEVENTF_MOVE表示移动鼠标,MOUSEEVENTF_LEFTDOWN表示按下鼠标左键,MOUSEEVENTF_LEFTUP表示放开鼠标左键。
dwExtraInfo是扩展信息,可以使用API函数GetMessageExtraInfo的返回值。例如单击鼠标左键的程序如下:
procedureMouseClick;
Inputs[0].Itype:=INPUT_MOUSE;
withInputs[0].mido
dx:=0;
dy:=0;
mouseData:=0;
dwFlags:=MOUSEEVENTF_LEFTDOWN;
Inputs[1].Itype:=INPUT_MOUSE;
withInputs[1].mido
dwFlags:=MOUSEEVENTF_LEFTUP;
鼠标的移动总是很麻烦,上面的dx、dy不是以象素为单位的,而是以鼠标设备移动量为单位的,它们之间的比值受鼠标移动速度设置的影响。具体的解决方法可参考《Delphi下利用WinIo模拟鼠标键盘详解》,这里不再重复。
dwFlags可以设置一个MOUSEEVENTF_ABSOLUTE标志,这使得可以用另外一种方法移动鼠标。当dwFlags设置了MOUSEEVENTF_ABSOLUTE标志,dx、dy为屏幕坐标值,表示将鼠标移动到dx,dy的位置。但是这个坐标值也不是以象素为单位的。这个值的范围是0到65535($FFFF),当dx等于0、dy等于0时表示屏幕的最左上角,当dx等于65535、dy等于65535时表示屏幕的最右下角,相当于将屏幕的宽和高分别65536等分。API函数GetSystemMetrics(SM_CXSCREEN)可以返回屏幕的宽度,
函数GetSystemMetrics(SM_CYSCREEN)可以返回屏幕的高度,利用屏幕的宽度和高度就可以将象素坐标换算成相应的dx、dy。注意:这种换算最多会出现1象素的误差。
例如:将鼠标指针移动到屏幕150,120坐标处的程序如下:
procedureMouseMove;
Input:TInput;
Input.Itype:=INPUT_MOUSE;
withInput.mido
dx:=($FFFFdiv(GetSystemMetrics(SM_CXSCREEN)-1))*150;
dy:=($FFFFdiv(GetSystemMetrics(SM_CYSCREEN)-1))*120;
dwFlags:=MOUSEEVENTF_MOVEorMOUSEEVENTF_ABSOLUTE;
SendInput(1,Input,SizeOf(TInput));
1、procedureSIKeyDown(Key:WORD);
按下指定的键。Key为虚键码。
2、procedureSIKeyUp(Key:WORD);
放开指定的键。Key为虚键码。
3、procedureSIKeyPress(Key:WORD;Interval:Cardinal);
4、procedureSIKeyInput(constText:String;Interval:Cardinal);
示范程序,组合键Ctrl+A如下:
SIKeyDown(VK_CONTROL);//按下Ctrl
SIKeyPress(VK_A);//击键A
SIKeyUp(VK_CONTROL);//放开Ctrl
5、procedureSIMouseDown(Key:WORD);
按下鼠标的指定键。Key为虚键码,鼠标左键为VK_LBUTTON,右键为VK_RBUTTON,中键为VK_MBUTTON。
6、procedureSIMouseUp(Key:WORD);
放开鼠标的指定键。Key为虚键码,鼠标左键为VK_LBUTTON,右键为VK_RBUTTON,中键为VK_MBUTTON。
7、procedureSIMouseClick(Key:WORD;Interval:Cardinal);
8、procedureSIMouseWheel(dZ:Integer);
滚动鼠标的滚轮。当dZ小于0时向下滚动,当dZ大于0时向上滚动,dZ的绝对值一般设为120。
9、procedureSIMouseMoveTo(X,Y:Integer;MaxMove:Integer;Interval:Cardinal);
示范程序,拖放到指定位置如下:
SIMouseDown(VK_LBUTTON);//按下鼠标左键
SIMouseMoveTo(780,300);//移动到指定位置
SIMouseUp(VK_LBUTTON);//放开鼠标左键
//作者:yeye552010年1月31日
//
//如果你转载了本文件中的代码,请注明代码出处和代码作者;
//如果你修改了本文件中的代码,请注明修改位置和修改作者。
unitSIMouseKeyboard;
Windows;
const
//虚键码定义
VK_LBUTTON=$01;
VK_RBUTTON=$02;
VK_CANCEL=$03;
VK_MBUTTON=$04;//*NOTcontiguouswithL&RBUTTON*/
VK_BACK=$08;
VK_TAB=$09;
VK_CLEAR=$0C;
VK_RETURN=$0D;
VK_SHIFT=$10;
VK_CONTROL=$11;
VK_MENU=$12;
VK_PAUSE=$13;
VK_CAPITAL=$14;
VK_KANA=$15;
VK_HANGEUL=$15;//*oldname-shouldbehereforcompatibility*/
VK_HANGUL=$15;
VK_JUNJA=$17;
VK_FINAL=$18;
VK_HANJA=$19;
VK_KANJI=$19;
VK_ESCAPE=$1B;
VK_CONVERT=$1C;
VK_NONCONVERT=$1D;
VK_ACCEPT=$1E;
VK_MODECHANGE=$1F;
VK_SPACE=$20;
VK_PRIOR=$21;
VK_NEXT=$22;
VK_END=$23;
VK_HOME=$24;
VK_LEFT=$25;
VK_UP=$26;
VK_RIGHT=$27;
VK_DOWN=$28;
VK_SELECT=$29;
VK_PRINT=$2A;
VK_EXECUTE=$2B;
VK_SNAPSHOT=$2C;
VK_INSERT=$2D;
VK_DELETE=$2E;
VK_HELP=$2F;
VK_C0=$C0;//“`”和“~”
VK_BD=$BD;//“-”和“_”
VK_BB=$BB;//“=”和“+”
VK_DC=$DC;//“\”和“|”
VK_DB=$DB;//“[”和“{”
VK_DD=$DD;//“]”和“}”
VK_BA=$BA;//“;”和“:”
VK_DE=$DE;//“'”和“"”
VK_BC=$BC;//“,”和“<”
VK_BE=$BE;//“.”和“>”
VK_BF=$BF;//“/”和“”
{*VK_0thruVK_9arethesameasASCII'0'thru'9'(0x30-0x39)*}
VK_0=$30;
VK_1=$31;
VK_2=$32;
VK_3=$33;
VK_4=$34;
VK_5=$35;
VK_6=$36;
VK_7=$37;
VK_8=$38;
VK_9=$39;
{*VK_AthruVK_ZarethesameasASCII'A'thru'Z'(0x41-0x5A)*}
VK_A=$41;
VK_B=$42;
VK_C=$43;
VK_D=$44;
VK_E=$45;
VK_F=$46;
VK_G=$47;
VK_H=$48;
VK_I=$49;
VK_J=$4A;
VK_K=$4B;
VK_L=$4C;
VK_M=$4D;
VK_N=$4E;
VK_O=$4F;
VK_P=$50;
VK_Q=$51;
VK_R=$52;
VK_S=$53;
VK_T=$54;
VK_U=$55;
VK_V=$56;
VK_W=$57;
VK_X=$58;
VK_Y=$59;
VK_Z=$5A;
VK_LWIN=$5B;
VK_RWIN=$5C;
VK_APPS=$5D;
VK_NUMPAD0=$60;
VK_NUMPAD1=$61;
VK_NUMPAD2=$62;
VK_NUMPAD3=$63;
VK_NUMPAD4=$64;
VK_NUMPAD5=$65;
VK_NUMPAD6=$66;
VK_NUMPAD7=$67;
VK_NUMPAD8=$68;
VK_NUMPAD9=$69;
VK_MULTIPLY=$6A;
VK_ADD=$6B;
VK_SEPARATOR=$6C;
VK_SUBTRACT=$6D;
VK_DECIMAL=$6E;
VK_DIVIDE=$6F;
VK_F1=$70;
VK_F2=$71;
VK_F3=$72;
VK_F4=$73;
VK_F5=$74;
VK_F6=$75;
VK_F7=$76;
VK_F8=$77;
VK_F9=$78;
VK_F10=$79;
VK_F11=$7A;
VK_F12=$7B;
VK_F13=$7C;
VK_F14=$7D;
VK_F15=$7E;
VK_F16=$7F;
VK_F17=$80;
VK_F18=$81;
VK_F19=$82;
VK_F20=$83;
VK_F21=$84;
VK_F22=$85;
VK_F23=$86;
VK_F24=$87;
VK_NUMLOCK=$90;
VK_SCROLL=$91;
{*
*VK_L*&VK_R*-leftandrightAlt,CtrlandShiftvirtualkeys.
*UsedonlyasparameterstoGetAsyncKeyState()andGetKeyState().
*NootherAPIormessagewilldistinguishleftandrightkeysinthisway.
*}
VK_LSHIFT=$A0;
VK_RSHIFT=$A1;
VK_LCONTROL=$A2;
VK_RCONTROL=$A3;
VK_LMENU=$A4;
VK_RMENU=$A5;
VK_PROCESSKEY=$E5;
VK_ATTN=$F6;
VK_CRSEL=$F7;
VK_EXSEL=$F8;
VK_EREOF=$F9;
VK_PLAY=$FA;
VK_ZOOM=$FB;
VK_NONAME=$FC;
VK_PA1=$FD;
VK_OEM_CLEAR=$FE;
//功能函数
procedureSIKeyDown(Key:WORD);
procedureSIKeyUp(Key:WORD);
procedureSIKeyPress(Key:WORD;Interval:Cardinal=0);
procedureSIKeyInput(constText:String;Interval:Cardinal=0);
procedureSIMouseDown(Key:WORD);
procedureSIMouseUp(Key:WORD);
procedureSIMouseClick(Key:WORD;Interval:Cardinal=0);
procedureSIMouseWheel(dZ:Integer);
procedureSIMouseMoveTo(X,Y:Integer;MaxMove:Integer=20;Interval:Cardinal=0);
PerWidth:Integer;//每象素宽度单位
PerHeight:Integer;//每象素高度单位
{功能函数}
//按下指定的键。
Input.Itype:=INPUT_KEYBOARD;
withInput.kido
wVk:=Key;
//放开指定的键。
procedureSIKeyPress(Key:WORD;Interval:Cardinal);
ifInterval<>0thenSleep(Interval);
//模拟键盘输入指定的文本,文本中只能是单字节字符(#32~#126)
//以及Tab(#9)键和回车键(#13),其它字符会被忽略,
procedureSIKeyInput(constText:String;Interval:Cardinal);
TCharTable=record
Key:WORD;
Char:array[0..1]ofAnsiChar;
CharCount=50;
CharTable:array[0..CharCount-1]ofTCharTable=(
(Key:VK_A;Char:'aA'),(Key:VK_B;Char:'bB'),
(Key:VK_C;Char:'cC'),(Key:VK_D;Char:'dD'),
(Key:VK_E;Char:'eE'),(Key:VK_F;Char:'fF'),
(Key:VK_G;Char:'gG'),(Key:VK_H;Char:'hH'),
(Key:VK_I;Char:'iI'),(Key:VK_J;Char:'jJ'),
(Key:VK_K;Char:'kK'),(Key:VK_L;Char:'lL'),
(Key:VK_M;Char:'mM'),(Key:VK_N;Char:'nN'),
(Key:VK_O;Char:'oO'),(Key:VK_P;Char:'pP'),
(Key:VK_Q;Char:'qQ'),(Key:VK_R;Char:'rR'),
(Key:VK_S;Char:'sS'),(Key:VK_T;Char:'tT'),
(Key:VK_U;Char:'uU'),(Key:VK_V;Char:'vV'),
(Key:VK_W;Char:'wW'),(Key:VK_X;Char:'xX'),
(Key:VK_Y;Char:'yY'),(Key:VK_Z;Char:'zZ'),
(Key:VK_0;Char:'0)'),(Key:VK_1;Char:'1!'),
(Key:VK_2;Char:'2@'),(Key:VK_3;Char:'3#'),
(Key:VK_4;Char:'4$'),(Key:VK_5;Char:'5%'),
(Key:VK_6;Char:'6^'),(Key:VK_7;Char:'7&'),
(Key:VK_8;Char:'8*'),(Key:VK_9;Char:'9('),
(Key:VK_C0;Char:'`~'),(Key:VK_BD;Char:'-_'),
(Key:VK_BB;Char:'=+'),(Key:VK_DC;Char:'\|'),
(Key:VK_DB;Char:'[{'),(Key:VK_DD;Char:']}'),
(Key:VK_BA;Char:';:'),(Key:VK_DE;Char:#39+'"'),
(Key:VK_BC;Char:',<'),(Key:VK_BE;Char:'.>'),
(Key:VK_BF;Char:'/'),(Key:VK_SPACE;Char:''+#0),
(Key:VK_TAB;Char:#9#0),(Key:VK_RETURN;Char:#13#0));
CapsState,NeedShift:Boolean;
i,id:Integer;
CapsState:=((GetKeyState(VK_CAPITAL)and1)<>0);
fori:=1toLength(Text)do
forid:=0toCharCount-1do
if(CharTable[id].Char[0]=Text[i])or
(CharTable[id].Char[1]=Text[i])then
break;
ifid>=CharCountthencontinue;
NeedShift:=(CharTable[id].Char[1]=Text[i]);
if(CharTable[id].Char[0]>='a')and
(CharTable[id].Char[0]<='z')andCapsStatethen
NeedShift:=notNeedShift;
//按下上档键
ifNeedShiftthen
wVk:=VK_SHIFT;
//按下指定键
wVk:=CharTable[id].Key;
//放开指定键
//放开上档键
//按下鼠标的指定键。
caseKeyof
VK_LBUTTON:dwFlags:=MOUSEEVENTF_LEFTDOWN;
VK_RBUTTON:dwFlags:=MOUSEEVENTF_RIGHTDOWN;
VK_MBUTTON:dwFlags:=MOUSEEVENTF_MIDDLEDOWN;
elseexit;
//放开鼠标的指定键。
VK_LBUTTON:dwFlags:=MOUSEEVENTF_LEFTUP;
VK_RBUTTON:dwFlags:=MOUSEEVENTF_RIGHTUP;
VK_MBUTTON:dwFlags:=MOUSEEVENTF_MIDDLEUP;
procedureSIMouseClick(Key:WORD;Interval:Cardinal);
//滚动鼠标的滚轮。
mouseData:=DWORD(dZ);
dwFlags:=MOUSEEVENTF_WHEEL;
//将鼠标指针移动到指定位置,返回是否成功,
//X和Y为象素值,X和Y的值的范围不能超出屏幕,
//MaxMove为移动时的dX和dY的最大值,
procedureSIMouseMoveTo(X,Y:Integer;MaxMove:Integer;Interval:Cardinal);
p:TPoint;
n:Integer;
ifMaxMove<=0thenMaxMove:=$7FFFFFFF;
GetCursorPos(p);
while(p.X<>X)or(p.Y<>Y)do
n:=X-p.X;
ifAbs(n)>MaxMovethen
ifn>0thenn:=MaxMove
elsen:=-MaxMove;
p.X:=p.X+n;
n:=Y-p.Y;
p.Y:=p.Y+n;
dx:=p.X*PerWidth;
dy:=p.Y*PerHeight;
initialization
PerWidth:=($FFFFdiv(GetSystemMetrics(SM_CXSCREEN)-1));//每象素宽度单位
PerHeight:=($FFFFdiv(GetSystemMetrics(SM_CYSCREEN)-1));//每象素高度单位
//=================================================
叛逆的鲁鲁修loveCC于2019-07-2223:56:37发布
最近接触到这个函数,因此了解了一下,总结一下列在这。
我了解它的出发点是如何通过它向活动窗口输入字符,这是很多程序都有的功能(我猜VisualAssistX就用了这个功能)。
根据MSDN,此函数模拟按键操作,将一些消息插入键盘或鼠标的输入流中,Windows对它进行处理,生成相应的WM_KEYDOWN或WM_KEYUP事件,这些事件与普通键盘输入一起进入应用程序的消息循环,它们不仅可以转换为WM_CHAR消息,还可以转换为其它(诸如加速键)等消息。
使用它来发送字符消息,并没有看起来那么简单。这有两个需要考虑的问题:
1.输入法的转换。例如需要向活动窗口发送一些英文字符,我们可能想象这样来实现:获取对应键盘字符的虚拟键码,发送一个SendInput。但是如果活动窗口正在使用一个输入法,那么我们发送出去的消息,会进入输入法的Composition窗口,最终被转换为象形文字或被丢弃。只有当输入法关闭时,程序运行的效果才会像我们期望的那样,在活动窗口中显示出英文字符。
如上所述,若直接如想象中那样使用SendInput来输入字符,则必须分析活动窗口的输入法状态。而且输入英文时,要求关闭输入法,输入中文时,又要求打开输入法。若真要以这样的思路来实现,则必定是难以成功的。
那么,有没有不依赖活动窗口输入法状态的方式呢?
其实是有的,使用SendInput模拟键盘输入时,其参数是KEYBDINPUT结构,通过将其dwFlags成员设置KEYEVENTF_UNICODE就可以了。使用此方式,只需将KEYBDINPUT.wScan设置为字符的Unicode编码即可。对于英文字符,不需要关闭活动窗口的输入法;对于中文字符,也不要求活动窗口打开输入法和将字符转换为输入法编码。
MSDN对此方式的说明为:INPUT_KEYBOARD支持非键盘的输入方式,例如手写识别或语音识别,通过KEYEVENTF_UNICODE标识,这些方式与键盘(文本)输入别无二致。如果指定了KEYEVENTF_UNICODE,SendInput发送一个WM_KEYDOWN或WM_KEYUP消息给活动窗口的线程消息队列,消息的wParam参数为VK_PACKET。GetMessage或PeedMessage一旦获得此消息,就把它传递给TranslateMessage,TranslateMessage根据wScan中指定的Unicode字符产生一个WM_CHAR消息。若窗口是ANSI窗口,则Unicode字符会自动转换为相应的ANSI字符。
任何需要向活动窗口输入字符(包括英文)的功能均应使用这种方式来实现。事实上,键盘消息转换为字符消息的过程是很复杂的,这可能与键盘布局、区域、换档状态等诸多因素有关,这也是Windows要使用TranslateMessage来转换消息的原因。因此,不应该试图通过击键事件来意图向活动窗口输入特定的字符。
经测试,SendInput还有两个值得注意的地方:
1.没有为KEYBDINPUT.dwFlags指定KEYEVENTF_KEYUP标识时,SendInput将生成WM_KEYDOWN消息,否则生成WM_KEYUP消息,由于只有WM_KEYDOWN会转换为字符消息,因此,若以输入字符为目标,则不应指定KEYEVENTF_KEYUP标识。
输入法也可以处理SendInput发送的Unicode消息,具体方式不详。见MSDN中ImmGetProperty方法的参考:当dwIndex参数为IGP_PROPERTY时,IME_PROP_ACCEPT_WIDE_VKEY是一个可能的返回值,它表示IME会处理SendInput函数以VK_PACKET注入的Unicode字符,若返回值无该标识,则Unicode字符会直接发送给应用程序。
WinIo有很多缺点,SendInput几乎没有这些缺点。SendInput的模拟要比WinIo简单的多。事件是被直接插入到系统消息队列的,所以它的速度比WinIo要快。系统也会保证数据的完整性,不会出现数据包混乱的情况。利用“绝对移动”可以将鼠标指针移动到准确的位置,同鼠标的配置隔离不会出现兼容性的问题。SendInput的缺点也是最要命的,它会被一些程序屏蔽。所以说在SendInput与WInIo都可以使用的情况下优先考虑SendInput。另外SendInput与WInIo可以接合使用,一些程序对鼠标左键单击敏感,可以使用WinIo模拟鼠标左键单击,其它操作由SendInput模拟。
{*****************************************************************************}
如果你对windows中消息钩子的用法已经有所了解,那么你可以通过设置一个全局HOOK来模拟键盘消息,比如,你可以用WH_JOURNALPLAYBACK这个钩子来模拟按键。WH_JOURNALPLAYBACK是一个系统级的全局钩子,它和WH_JOURNALRECORD的功能是相对的,常用它们来记录并回放键盘鼠标操作。WH_JOURNALRECORD钩子用来将键盘鼠标的操作忠实地记录下来,记录下来的信息可以保存到文件中,而WH_JOURNALPLAYBACK则可以重现这些操作。当然亦可以单独使用WH_JOURNALPLAYBACK来模拟键盘操作。
SetWindowsHookEx函数,它可以用来安装消息钩子:
SetWindowsHookEx(
idHook:Integer;{hooktypeflag}
lpfn:TFNHookProc;{apointertothehookfunction}
hmod:HINST;{ahandletothemodulecontainingthehookfunction}
dwThreadId:DWORD{theidentifieroftheassociatedthread}
):HHOOK;
先安装WH_JOURNALPLAYBACK这个钩子,然后你需要自己写一个钩子函数,在系统调用它时,把你要模拟的事件传递给钩子参数lParam所指向的EVENTMSG区域,就可以达到模拟按键的效果。不过用这个钩子模拟键盘事件有一个副作用,就是它会锁定真实的鼠标键盘,不过如果你就是想在模拟的时候不会受真实键盘操作的干扰,那么用用它倒是个不错的主意。
在不需要监视系统消息时需要调用提供UnHookWindowsHookEx来解除对消息的监视。下面来建立程序,在Delphi中建立一个工程,在Form1上添加3个按钮用于程序操作。另外再添加一个按钮控件和一个Edit控件用于验证操作。
下面是Form1的全部代码
unitUnit1;
Windows,Messages,SysUtils,Classes,Graphics,Controls,Forms,Dialogs,
StdCtrls;
TForm1=class(TForm)
Button1:TButton;
Button2:TButton;
Button3:TButton;
Edit1:TEdit;
Button4:TButton;
procedureFormCreate(Sender:TObject);
procedureButton1Click(Sender:TObject);
procedureButton2Click(Sender:TObject);
procedureButton3Click(Sender:TObject);
private
{Privatedeclarations}
public
{Publicdeclarations}
Form1:TForm1;
EventArr:array[0..1000]ofEVENTMSG;
EventLog:Integer;
PlayLog:Integer;
hHook,hPlay:Integer;
recOK:Integer;
canPlay:Integer;
bDelay:Bool;
{$R*.DFM}
FunctionPlayProc(iCode:Integer;wParam:wParam;lParam:lParam):LRESULT;stdcall;
canPlay:=1;
ifiCode<0then//必须将消息传递到消息链的下一个接受单元
Result:=CallNextHookEx(hPlay,iCode,wParam,lParam)
elseifiCode=HC_SYSMODALONthen
canPlay:=0
elseifiCode=HC_SYSMODALOFFthen
canPlay:=1
elseif((canPlay=1)and(iCode=HC_GETNEXT))thenbegin
ifbDelaythenbegin
bDelay:=False;
Result:=50;
pEventMSG(lParam)^:=EventArr[PlayLog];
elseif((canPlay=1)and(iCode=HC_SKIP))thenbegin
bDelay:=True;
PlayLog:=PlayLog+1;
ifPlayLog>=EventLogthenbegin
UNHookWindowsHookEx(hPlay);
functionHookProc(iCode:Integer;wParam:wParam;lParam:lParam):LRESULT;stdcall;
recOK:=1;
ifiCode<0then
Result:=CallNextHookEx(hHook,iCode,wParam,lParam)
recOK:=0
recOK:=1
elseif((recOK>0)and(iCode=HC_ACTION))thenbegin
EventArr[EventLog]:=pEventMSG(lParam)^;
EventLog:=EventLog+1;
ifEventLog>=1000thenbegin
UnHookWindowsHookEx(hHook);
procedureTForm1.FormCreate(Sender:TObject);
Button1.Caption:='纪录';
Button2.Caption:='停止';
Button3.Caption:='回放';
Button4.Caption:='范例';
Button2.Enabled:=False;
Button3.Enabled:=False;
EventLog:=0;//建立键盘鼠标操作消息纪录链
hHook:=SetwindowsHookEx(WH_JOURNALRECORD,HookProc,HInstance,0);
Button2.Enabled:=True;
Button1.Enabled:=False;
procedureTForm1.Button2Click(Sender:TObject);
hHook:=0;
Button1.Enabled:=True;
Button3.Enabled:=True;
procedureTForm1.Button3Click(Sender:TObject);
PlayLog:=0;//建立键盘鼠标操作消息纪录回放链
hPlay:=SetwindowsHookEx(WH_JOURNALPLAYBACK,PlayProc,
HInstance,0);
在DOS时代,当用户按下或者放开一个键时,就会产生一个键盘中断(如果键盘中断是允许的),这样程序会跳转到BIOS中的键盘中断处理程序去执行。打开windows的设备管理器,可以查看到键盘控制器由两个端口控制。其中&H60是数据端口,可以读出键盘数据,而&H64是控制端口,用来发出控制信号。也就是,从&H60号端口可以读此键盘的按键信息,当从这个端口读取一个字节,该字节的低7位就是按键的扫描码,而高1位则表示是按下键还是释放键。当按下键时,最高位为0,称为通码,当释放键时,最高位为1,称为断码。既然从这个端口读数据可以获得按键信息,那么向这个端口写入数据就可以模拟按键了!用过QbASIC4.5的朋友可能知道,QB中有个OUT命令可以向指定端口写入数据,而INP函数可以读取指定端口的数据。那我们先看看如果用QB该怎么写代码:
假如你想模拟按下一个键,这个键的扫描码为&H50,那就这样
OUT&H64,&HD2'把数据&HD2发送到&H64端口。这是一个KBC指令,表示将要向键盘写入数据
OUT&H60,&H50'把扫描码&H50发送到&H60端口,表示模拟按下扫描码为&H50的这个键
那么要释放这个键呢?像这样,发送该键的断码:
OUT&H60,(&H50or&H80)'把扫描码&H50与数据&H80进行或运算,可以把它的高位置1,得到断码,表示释放这个键
好了,现在的问题就是在delphi中如何向端口写入数据了。因为在windows中,普通应用程序是无权操作端口的,于是我们就需要一个驱动程序来帮助我们实现。在这里我们可以使用一个组件WINIO来完成读写端口操作。
下载该组件,解压缩后可以看到几个文件夹,其中Release文件夹下的3个文件就是我们需要的,这3个文件是WinIo.sys(用于winxp下的驱动程序),WINIO.VXD(用于win98下的驱动程序),WinIo.dll(封装函数的动态链接库),我们只需要调用WinIo.dll中的函数,然后WinIo.dll就会安装并调用驱动程序来完成相应的功能。
值得一提的是这个组件完全是绿色的,无需安装,你只需要把这3个文件复制到与你的程序相同的文件夹下就可以使用了。
用法很简单:
1.先用里面的InitializeWinIo函数安装驱动程序,
2.然后就可以用GetPortVal来读取端口或者用SetPortVal来写入端口了。
3.最后必须在中止应用函数之前或者不再需要WinIO库时调用ShutdownWinIo函数在内存中清除WinIO库。
好,让我们来做一个驱动级的键盘模拟吧。先把winio的3个文件拷贝到你的程序的文件夹下。
下面给出使用WINIO模拟按键的单元和使用方法:(注意是32位和64位的区别)
{****************************************************************************}
unitMNwinio;
KBC_KEY_CMD=$64;//键盘命令端口
KBC_KEY_DATA=$60;//键盘数据端口
functionInitializeWinIo:Boolean;stdcall;external'WinIo.dll'name'InitializeWinIo';
本函数初始化WioIO函数库。
必须在调用所有其它功能函数之前调用本函数。
如果函数调用成功,返回值为非零值。
如果调用失败,则返回值为0。
procedureTForm1.FormActivate(Sender:TObject);//通常在程序启动时调用
ifInitializeWinIo=Falsethen
Messagebox(handle,'初始化失败!','提示',MB_OK+MB_IconError)
functionInstallWinIoDriver(pszWinIoDriverPath:PString;IsDemandLoaded:boolean
=false):Boolean;stdcall;external'WinIo.dll'name'InstallWinIoDriver';
functionRemoveWinIoDriver:Boolean;stdcall;external'WinIo.dll'name
'RemoveWinIoDriver';
functionGetPortVal(PortAddr:Word;PortVal:PDWord;bSize:Byte):Boolean;
stdcall;external'WinIo.dll'name'GetPortVal';
functionSetPortVal(PortAddr:Word;PortVal:DWord;bSize:Byte):Boolean;
stdcall;external'WinIo.dll'name'SetPortVal';
functionGetPhysLong(PhysAddr:PByte;PhysVal:PDWord):Boolean;stdcall;
external'WinIo.dll'name'GetPhysLong';
functionSetPhysLong(PhysAddr:PByte;PhysVal:DWord):Boolean;stdcall;external
'WinIo.dll'name'SetPhysLong';
functionMapPhysToLin(PhysAddr:PByte;PhysSize:DWord;PhysMemHandle:PHandle):
PByte;stdcall;external'WinIo.dll'name'MapPhysToLin';
functionUnMapPhysicalMemory(PhysMemHandle:THandle;LinAddr:PByte):Boolean;
stdcall;external'WinIo.dll'name'UnmapPhysicalMemory';
procedureShutdownWinIo;stdcall;external'WinIo.dll'name'ShutdownWinIo';
{本函数在内存中清除WinIO库
本函数必须在中止应用函数之前或者不再需要WinIO库时调用
procedureTForm1.FormClose(Sender:TObject;varAction:TCloseAction);//通常在程序关闭时调用
ShutdownWinIo;
{**********以上为WINIO.dll中API函数的调用***************}
procedureKBCWait4IBE;//等待键盘缓冲区为空
dwVal:DWord;
repeat
GetPortVal($64,@dwVal,1);
{这句表示从&H64端口读取一个字节并把读出的数据放到变量dwVal中.GetPortVal函数的用法是GetPortVal(端口号,存放读出数据的变量地址,读入的长度}
until(dwValand$2)=0;
procedureMyKeyDown(vKeyCoad:Integer);//这个用来模拟按下键,参数vKeyCoad传入按键的虚拟码
btScancode:DWord;
btScancode:=MapVirtualKey(vKeyCoad,0);
KBCWait4IBE;//发送数据前应该先等待键盘缓冲区为空
SetPortVal($64,$D2,1);//发送键盘写入命令
{SetPortVal函数用于向端口写入数据,它的用法是:SetPortVal(端口号,欲写入的数据,写入数据的长度)}
KBCWait4IBE;
SetPortVal($60,btScancode,1);//写入按键信息,按下键
procedureMyKeyUp(vKeyCoad:Integer);//这个用来模拟释放键,参数vKeyCoad传入按键的虚拟码