C#Win32API编程之PostMessage
  由于C#屏蔽了很多操作系统内核级的操作,将保护机制进⾏了加强,通过普通⽅法是⽆法完成如后台键⿏模拟、进程内存读写、⽹络封包拦截等操作的。
  ⽽C#⼜提供了调⽤⾮托管代码的DllImport,使得我们可以调⽤操作系统较为底层的API来完善程序功能。
  本⽂就C#调⽤Win32API函数PostMessage完成指定窗体后台键⿏模拟作为⽰例,粗略讲解⼀下C#对⾮托管代码的调⽤及Window 的消息处理机制。
  (如果您对DllImport和Window消息机制有较为深⼊的理解,阅读本篇⽂章只是为了了解如何发送键⿏模拟指定和PostMessage中wParam与lParam的具体含义请略过前⾯的章节)
  ⾸先是C#调⽤⾮托管函数。
  我们使⽤DllImport特性从指定动态链接库连接函数到我们的代码中,如下
1 [DllImport("user32", SetLastError = true)]
2            private static extern bool PostMessage(
3                int hWnd,
4                uint Msg,
5                int wParam,
6                int lParam
7                );
  上⾯的代码⽤于在运⾏时将我们定义的PostMessage⽅法替换成user32.dll中的同名⽅法。
  (注意:1.dll的名称可以不包括后缀。
      2.dll的路径需要在程序路径下或Path路径下。
        3组件可以使⽤Tlbimp转换为元数据后在项⽬中Using⽽不需要如此操作。
        4.参数类型最好为32位整型来传递值数据或者封装为IntPtr类型进⾏传递(⼤多数情况下使⽤),使⽤对象引⽤或委托也是可以的;值得⼀提的是虽然系统会⾃动整编,但是在结构数据的存储上您则需要⼀下特定处理。
        5.SetLastError属性让您可以在函数调⽤出错后可以调⽤GetLastError函数(也是外部函数)得到错误代码,通过查阅msdn⼿册或者在C#代码中构造System.ComponentModel.Win32Exception类实例来具体化错误信息。
        6.DllImport特性的EntryPoint属性让您可以设置链接的外部⽅法名,默认为当前⽅法名,如果您希望更改⽅法名则可以使⽤指定⽅法名的⽅式实现。)
  总的说来,DllImport有点像Java的JNI,都是引⽤外部的函数,⽽extern关键字更像是来⾃于C++。
  然后是Windows的消息机制。
  ⾸先我们知道,程序运⾏起来⾸先会有⼀个主线程,这时候线程没有属于⾃⼰的消息队列,他是⾮消息线程或⼯作者线程;系统假定线程不会⽤于任何与⽤户相关的任务,这样可以减少线程对系统资源的要求。
  ⽽后当该线程调⽤⼀个与图形⽤户界⾯有关的函数 ( 如检查它的消息队列或建⽴⼀个窗⼝ ),系统就会为该线程分配⼀些另外的资源,以便它能够执⾏与⽤户界⾯有关的任务,这个时候线程与消息队列相关,它就成了⽤户界⾯线程;当⽤户操作,系统就会产⽣消息并送⼊某队列。(如我们使⽤C和C++开发WindowGUI程序,在WinMain中总会调⽤GetMessage函数循环获取消息队列中的消息;不熟悉
C/C++的朋友也没关系,这句话的意思⼤概是说在进程启动后会使⽤循环主动从消息队列中获取消息)
  所以如果要模拟键⿏操作,我们所要做的很简单,就是使⽤某个函数(当然获取窗体句柄等其他函数)把特定消息(我们⾃⼰构造)放到对应线程的消息队列就OK了。
  函数我们可以使⽤PostMessage,⾄于能不能成功,很⼤⼀定程度上取决于我们消息是否构造得逼真。
  放着PostMessage先不管,我们看看“消息”在WindowsAPI中的定义:
1 typedef struct tagMSG {    // msg
2    HWND  hwnd;
3    UINT  message;
4    WPARAM wParam;
5    LPARAM lParam;
6    DWORD  time;
7    POINT  pt;
8 } MSG;
  hwnd参数表⽰该哪个Window来处理这个消息。
  message表⽰消息是什么。键盘按键、⿏标点击还是其他。
  wParam和lParam是参数,他们在message不同时拥有不同的含义。
  time表⽰时间。
  pt表⽰坐标。
    个⼈表⽰很纠结,最讨厌C和C++把类型名字定义太多,明明都是整型却偏要搞这么多花样(笔者只是随⼝⼀提,读者要明⽩上⽂中各参数类型并不完全相同)。
  我们需要构造的消息就是上⾯这个,time和pt不⽤我们构造,系统会⽣成。我们需要将前⾯四个参数构造出来。
  请读者原谅笔者先卖个关⼦,笔者希望先把PostMessage函数做⼀个讲解。
  PostMessage函数是将⼀个消息的组成部分合成⼀个消息(如我们调⽤时则只需依次传⼊上⽂消息的前四个参数即可)并放⼊对应线程消息队列的⽅法,它的返回值表⽰操作组装和放⼊队列是否成功,⽽⾮执⾏是否成功。它是异步的操作,如下图:
  我们在调⽤PostMessage之后,消息就会进⼊消息队列。轮到该消息被获取和响应时我们就能看到效果了。
  如果您希望⽴即或很快看到效果则可以使⽤SendMessage⽅法,该⽅法和PostMessage使⽤⽅法完全相同(不过它是同步的,且不善于处理⾮执⾏线程的消息插⼊,在某些情况下可能会造成死锁或进程停⽌,
配合钩⼦使⽤时则需尤为注意),您可以通过返回值判断操作是否成功。只是它的返回是在消息被处理之后,调⽤线程可能出现画⾯停顿或变成⽩⾊,不是很推荐⼤家使⽤。
  最后是PostMessage⽅法的调⽤传参。
  第⼀个参数是要交由处理的控件句柄。这个参数的获取很重要,⼀般来说我们需要获取到真正的控件句柄⽽⾮其⽗窗体或控件的句柄,因为它们不能正确处理这个消息。(⽐如按钮点击,我们最好拿到按钮的句柄⽽⾮其⽗控件的句柄,可以通过⿏标位置拿到控件句柄,使⽤GetCursorPos和WindowFromPoint⽅法即可,他们都来⾃外部函数)
  第⼆个参数为消息的整型表⽰,它被定义在头⽂件中以WM_开头的宏⾥,从0x1到0x400。查阅msdn或c语⾔Windows的平台sdk头⽂件即可看到,值得⼀提的是在多个头⽂件中都有消息定义。
  第三个和第四个参数是扩展信息,在消息类型(第⼆个参数)不同时它们的值和含义会有很⼤差别。
  下⾯是msdn上对⿏标、键盘相关消息中扩展信息的描述(个⼈翻译,可能有出⼊)
    当⿏标左键按下:
      参数1:略
      参数2:WM_LBUTTONDOWN=0x201
      参数3:指⽰此时Ctrl、Shift、⿏标左键、⿏标中键、⿏标右键的按下情况(这个数值为32位,是使⽤MK_LBUTTON=1、MK_RBUTTON=2、MK_SHIFT=4、MK_CTROL=8、MK_MBUTTON=16、MK_XBUTTON1=32(需要nt5.x)、
MK_XBUTTON2=64(需要nt5.x)按位或得到的。它们被定义在winuser.h头⽂件中。也就说如果此时只是单纯的⿏标左键按下,则此参数应该为1,如果此时Ctrl键已经被按下了,则应该设置为9)。
      参数4:⿏标点击的坐标,相对于窗体⽽⾔(这个数值是32位的,⾼位存y坐标,地位存x坐标,传递⽅法可以为x+y*65536或者x+(y<<16))。
    当⿏标左键抬起时参数3则需要变动,此时最低位应置零。
    ⿏标右键、中键按下和抬起、双击实现⼀样(在右键抬起时第⼆位应该置零哦)。
    ⿏标滚轮滚动时实现略有不同:
      参数1:略
      参数2:WM_MOUSEWHEEL=0x20A
      参数3:指⽰此时Ctrl、Shift、⿏标左键、⿏标中键、⿏标右键的按下情况及⿏标滚轮的滚动情况(该数值为32位,低位表⽰按键情况,依然可以通过按位或组装;⾼位表⽰滚动情况,⼀般来说,对于向下滚动永远为n*-120(n⼀般为1),对于向上滚动永远为
n*120(n⼀般为1);分别对应-1*120与1*120。即如果向下滚动且未按下任何键,此数值应设置为-120*65536即0xFF880000,向上滚动则设置为120*65536即0x00780000;如果此时Ctrl键是按下状态,则数值应加上8)。
      参数4:⿏标位置,⾼位y,低位x。
    键盘按下时:
      参数1:略
      参数2:WM_KEYDOWN=0x0100
      参数3:指⽰按下的键的虚拟码在winuser.h头⽂件中查看VK_开头的宏定义即可
      参数4:指⽰扩展信息(此数值为32位,0-15位表⽰按键被按下的次数,应当置为1;16-23表⽰按键对应的硬件扫描码,这个值和硬件有关,不过可以使⽤MapVirtualKey函数来得到;24位表⽰这个键是
否是扩展键,如右Ctrl和Alt,在101和102键盘中,此值为1,否则为0;25-28位为保留位;29位指⽰此时Alt键是否处于按下状态;30位只是此键之前的状态,如之前为按下状态,此值为1,反之为0,此处则应置零;第31位为特殊标识,在WM_KEYDOWN消息中此值始终为0)。
    键盘抬起时:
      参数1:略
      参数2:WM_KEYDOWN=0x0101
      参数3:同上
      参数4:扩展信息,同上(第30位和31位都为1)。
    普通组合按键:
      Ctrl和Shift按键还好,先按下Ctrl或Shift再按下其他键,松开其他键再松开Ctrl或Shift即可。
    Alt组合按键:
      对于Alt组合按键则复杂⼀点,Alt是系统关键按键,它是默认的快捷键组合键。我们发送Alt组合按键
时应该先发⼀个Alt键,然后发送其他按键。有两种⽅式:
        ⽅式⼀:省略其他信息,直接发送⼀条消息表⽰组合按键,如下
1 PostMessage(hwnd, WM_SYSKEYDOWN, 'A', 1 << 29);
2 //Thread.Sleep(50);
3 //PostMessage(hwnd, WM_SYSKEYUP, 'A', 1 << 29);
sendmessage返回值        如果看懂了上⽂对lParam的解释,这个代码应该容易理解。
        ⽅式⼆:发送完整消息,先发送Alt按下,然后发送组合按键(值得⼀提的是此时应该使⽤WM_SYSKEYDOWN,lParam 第29位也应该置为1),然后发送组合按键松开(同前),最后发送Alt松开的消息。
  如果⼤家还不是很明⽩,请⼤家我写的范例来看。断断续续搞了两三天,代码写得漏洞百出,不过先发出来⼤家稍微看看,有错误的地⽅请⼤家见谅(⽆情提⽰:不要盲⽬地认为我的代码是正确的,事实证明代码⾥的功能很⼤程度上不完善)。我把使⽤到的资料也⼀并打包了。
欢迎您移步我们的交流,⽆聊的时候⼤家⼀起打发时间: 或者通过QQ与我联系:
(最后编辑时间2013-05-14 15:14:41)

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。