在C#中SendMessage和PostMessage的参数传递
本文源码下载地址:http://download.csdn.net/detail/testcs_dn/5152868
在C#中可以使用Window API提供的SendMessage和PostMessage来传递参数。两者的区别简单介绍下:返回值的不同,我们先看一下 MSDN 里的声明:
LRESULT SendMessage(
HWND hWnd,
UINT Msg,
WPARAM wParam,
LPARAM lParam
);
BOOL PostMessage(
HWND hWnd,
UINT Msg,
WPARAM wParam,
LPARAM lParam
);
其中 4 个参数的意义是一样的,返回值类型不同(其实从数据上看他们一样是一个 32 位的数,只是意义不一样),LRESULT 表示的是消息被处理后的返回值,BOOL 表示的是消息是不是 Post 成功。
2、PostMessage 是异步的,SendMessage 是同步的。
PostMessage 只把消息放入队列,不管消息是否被处理就返回,消息可能不被处理;而 SendMessage 等待消息被处理完了之后才返回,如果消息不被处理,发送消息的线程将一直被阻塞。
3、如果在同一个线程内,SendMessage 发送消息时,由 USER32.DLL
模块调用目标窗口的消息处理程序,并将结果返回。SendMessage 在同一线程中发送消息并不入线程消息队列。PostMessage
发送消息时,消息要先放入线程的消息队列,然后通过消息循环分派到目标窗口(DispatchMessage)。
如果在不同线程内,SendMessage 发送消息到目标窗口所属线程的消息队列,然后发送消息的线程在 USER32.DLL
模块内监视和等待消息处理,直到目标窗口处理完返回。SendMessage 在返回前还做了很多工作,比如,响应别的线程向它
SendMessage。Post 到别的线程时,最好用 PostThreadMessage 代替
PostMessage,PostMessage 的 hWnd 参数可以是 NULL,等效于 PostThreadMessage +
GetCurrentThreadId。Post WM_QUIT 时,应使用 PostQuitMessage 代替。
4、系统只整编(marshal)系统消息(0 到 WM_USER 之间的消息),发送用户消息(WM_USER 以上)到别的进程时,需要自己做整编。
用 PostMessage、SendNotifyMessage、SendMessageCallback 等异步函数发送系统消息时,参数里不可以使用指针,因为发送者并不等待消息的处理就返回,接受者还没处理指针就已经被释放了。 5、在 Windows 2000/XP 里,每个消息队列最多只能存放 10,000 个 Post 的消息,超过的还没被处理的将不会被处理,直接丢掉。这个值可以改得更大:[HKEY_LOCAL_MACHINE/SOFTWARE/ Microsoft/Windows NT/CurrentVersion/Windows]
USERPostMessageLimit,最小可以是 4000。 PostMessage只负责将消息放到消息队列中,不确定何时及是否处理 SendMessage要等到受到消息处理的返回码(DWord类型)后才继续 PostMessage执行后马上返回 SendMessage必须等到消息被处理后才会返回。 下面通过一个小例子来说明下这2个方法进行参数传递的不同点:
//Win32 API 类using System;
using System.Runtime.InteropServices;
namespace TestHwnd
{
public class Win32API
{
[DllImport("User32.dll", EntryPoint = "FindWindow")]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("User32.dll", EntryPoint = "FindWindowEx")]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpClassName, string lpWindowName);
/// <summary>
/// 自定义的结构
/// </summary>
public struct My_lParam
{
public int i;
public string s;
}
/// <summary>
/// 使用COPYDATASTRUCT来传递字符串
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cbData;
[MarshalAs(UnmanagedType.LPStr)]
public string lpData;
}
//消息发送API
[DllImport("User32.dll", EntryPoint = "SendMessage")]
public static extern int SendMessage(
IntPtr hWnd, // 信息发往的窗口的句柄
int Msg, // 消息ID
int wParam, // 参数1
int lParam //参数2
);
//消息发送API
[DllImport("User32.dll", EntryPoint = "SendMessage")]
public static extern int SendMessage(
IntPtr hWnd, // 信息发往的窗口的句柄
int Msg, // 消息ID
int wParam, // 参数1
ref My_lParam lParam //参数2
);
//消息发送API
[DllImport("User32.dll", EntryPoint = "SendMessage")]
public static extern int SendMessage(
IntPtr hWnd, // 信息发往的窗口的句柄
int Msg, // 消息ID
int wParam, // 参数1
ref COPYDATASTRUCT lParam //参数2
);
//消息发送API
[DllImport("User32.dll", EntryPoint = "PostMessage")]
public static extern int PostMessage(
IntPtr hWnd, // 信息发往的窗口的句柄
int Msg, // 消息ID
int wParam, // 参数1
int lParam // 参数2
);
//消息发送API
[DllImport("User32.dll", EntryPoint = "PostMessage")]
public static extern int PostMessage(
IntPtr hWnd, // 信息发往的窗口的句柄
int Msg, // 消息ID
int wParam, // 参数1
ref My_lParam lParam //参数2
);
//异步消息发送API
[DllImport("User32.dll", EntryPoint = "PostMessage")]
public static extern int PostMessage(
IntPtr hWnd, // 信息发往的窗口的句柄
int Msg, // 消息ID
int wParam, // 参数1
ref COPYDATASTRUCT lParam // 参数2
);
}
}
//主窗体,发送消息using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace TestHwnd
{
public partial class Main : Form
{
public IntPtr hwndTest;
public int IwndTest;
public IntPtr hwndfrmTest;
public Main()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Test test = new Test();
test.Show(this);
}
private void timer1_Tick(object sender, EventArgs e)
{
string strTest = "25425";
Win32API.COPYDATASTRUCT cds;
cds.dwData = (IntPtr)100;
cds.lpData = strTest;
byte[] sarr = System.Text.Encoding.UTF8.GetBytes(strTest);
int len = sarr.Length;
cds.cbData = len + 1;
Win32API.My_lParam lp=new Win32API.My_lParam();
lp.i=3;
lp.s="test";
if(hwndTest!=(IntPtr)0)
{
if (DateTime.Now.Second % 2 == 0)
{
Win32API.SendMessage(hwndTest, 0x60, 1, 3);//传递2个整型参数成功
}
if(DateTime.Now.Second % 3 == 0)
{
Win32API.SendMessage(hwndTest, 0x61, 5, ref lp);//传递整型参数和结构类型成功,这个方法加以改变后可以传递对象
}
if(DateTime.Now.Second % 5 == 0)
{
Win32API.SendMessage(hwndTest, 0x62, 5, ref cds);//传递整型参数和不定长的字符串成功
}
if(DateTime.Now.Second % 7 == 0)
{
Win32API.PostMessage(hwndTest, 0x63, 5, 6);//传递2个整型参数成功
}
if(DateTime.Now.Second % 9 == 0)
{
Win32API.PostMessage(hwndTest, 0x64, 3, ref lp);//传递整型参数成功,但是传递参数lp失败,3可以传递成功。
}
if(DateTime.Now.Second % 11 == 0)
{
Win32API.PostMessage(hwndTest, 0x65, 3, ref cds);//传递整型参数成功,传递参数cds失败,3可以传递成功。
}
}
}
}
}
//子窗体接收消息以及参数using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace TestHwnd
{
public partial class Test : Form
{
Main main;
public Test()
{
InitializeComponent();
}
private void Test_Load(object sender, EventArgs e)
{
main = this.Owner as Main;
main.hwndTest = this.Handle;
}
///重写窗体的消息处理函数DefWndProc,从中加入自己定义消息的检测的处理入口
protected override void DefWndProc(ref Message m)
{
switch (m.Msg)
{
//接收自定义消息MYMESSAGE,并显示其参数
case 0x60:
{
label1.Text = DateTime.Now.ToString() + "-" + m.WParam.ToInt32().ToString() + "-" + m.LParam.ToInt32().ToString();
}
break;
case 0x61:
{
Win32API.My_lParam ml = new Win32API.My_lParam();
Type t = ml.GetType();
ml = (Win32API.My_lParam)m.GetLParam(t);
label2.Text = DateTime.Now.ToString() + "-" + m.WParam.ToInt32().ToString() + "-" + ml.i.ToString()+":"+ml.s;
}
break;
case 0x62:
{
Win32API.COPYDATASTRUCT mystr = new Win32API.COPYDATASTRUCT();
Type mytype = mystr.GetType();
mystr = (Win32API.COPYDATASTRUCT)m.GetLParam(mytype);
string str2 = mystr.lpData;
label3.Text = DateTime.Now.ToString() + "-" + m.WParam.ToInt32().ToString() + "-" + str2;
}
break;
case 0x63:
{
label4.Text = DateTime.Now.ToString() + "-" + m.WParam.ToInt32().ToString() + "-" + m.LParam.ToInt32().ToString();
}
break;
case 0x64:
{
Win32API.My_lParam ml = new Win32API.My_lParam();
Type t = ml.GetType();
ml = (Win32API.My_lParam)m.GetLParam(t);
label5.Text = DateTime.Now.ToString() + "-" + m.WParam.ToInt32().ToString() + "-" + ml.i.ToString()+":"+ml.s;
}
break;
case 0x65:
{
Win32API.COPYDATASTRUCT mystr = new Win32API.COPYDATASTRUCT();
Type mytype = mystr.GetType();
mystr = (Win32API.COPYDATASTRUCT)m.GetLParam(mytype);
string str2 = mystr.lpData;
label6.Text = DateTime.Now.ToString() + "-" + m.WParam.ToInt32().ToString() + "-" + str2;
}
break;
default:
base.DefWndProc(ref m);
break;
}
}
private void button1_Click(object sender, EventArgs e)
{
main.hwndTest = (IntPtr) (0);
this.Close();
}
}
}
本文源码下载地址:http://download.csdn.net/detail/testcs_dn/5152868
本篇文章来源于 Linux公社网站(www.linuxidc.com) 原文链接:http://www.linuxidc.com/Linux/2012-01/51557.htm
用C#打造"QQ对战平台挤房器"
一、什么是“QQ对战平台挤房器”?
喜欢在“QQ对战平台”或“浩方对战平台”玩游戏的人都知道。平常平台上的房间基本很多都是人满的,如果想找个房间,那可是要费好长的时间来“挤”才能进去,如果是节假日或晚上,那更要花费更多的时间在“挤”房上了,如下图:
而对于“QQ对战平台”,如果房间已满挤不进去,却变态的还会弹出两次提示!平时,就只有拼命的按“回车键”或“空格键”关闭这两个讨厌的弹出窗口,然后再用鼠标点房间,如果房间还是进不去,就只能再按上面来一次循环……一次、两次不是问题;五次、十次也许还不是问题;但如果十几次或上百次还是挤不进去,还手按那就有问题了!当然,如果你喜欢手虐那就另外说了-_-#
我不喜欢手虐,所以当有次碰到N次还是挤不进去时,我就在想,为什么不写个工具来代替我的手,用工具自动去帮我“挤”房间,帮我点那两个讨厌的提示窗口呢?于是这篇文章中的“QQ对战平台挤房器”就这样诞生了(这可解放多少人的双手啊,大家鼓掌……)
二、“挤房”要怎样“挤”?
上面说了,“挤房器”就是帮我们自动“挤”房,但是它要怎么帮我们“挤”呢?它毕竟是机器,而不是人,它不会自动一看到房间就帮我们“挤”,除非我们给它定制了一套规则(也可以叫命令)。机器就是机器,有规则它才会去做事,如果没规则也会做事,那它就是“人”(智能机器人?)了。
这规则要怎样定制?让我们先来看看我们平时进入房间的流程,如下图:
好了,根据上面的流程,我们要给“挤房器”定制的规则就是分别以下几条:
1)、点击房间。
2)、判断是否已进入房间,如果没有进入房间,则负责将显示房间满的两个讨厌的提示窗口关闭掉,并重新回到第1步。
3)、已进入房间,则停止“挤”房动作。
三、自动“挤房”的实现。
这里的实现,就是用代码去实现上面定制的三个规则。
1、点击房间:
在这里我们简单点,只是模拟鼠标去点击鼠标当前所在的房间。说到这里,也许做过WINDOWS应用程序开发的朋友已想到了用哪几个API函数去模拟,对!就是以下几个API的结合一起使用,就能实现鼠标的模拟点击了。
1)、GetCursorPos : 获取鼠标的当前所在位置。
定义原型如下:
[DllImport("user32.dll")] internal static extern bool GetCursorPos(out Point lpPoint);
此函数返回当前鼠标所在的坐标位置。
2)、mouse_event : 鼠标事件的模拟
定义原型如下:
[DllImport("user32.dll", EntryPoint = "mouse_event")] public static extern void mouse_event(
int dwFlags,
int dx,
int dy,
int dwData,
int dwExtraInfo
);
此函数通过不同的dwFlags参数定义,可以模拟不同的鼠标事件,如鼠标左键的按下、弹起事件等。
结合以上两个API函数,我们就能实现自动点击鼠标所在的房间"的效果了,类似如下代码:
Point point;
if(Win32API.GetCursorPos(out point)){
//MouseAPI是对mouse_event API函数的一个简单封装。
MouseAPI.SendMouseEvent(MouseAPI.MouseEvents.LeftButtonDown | MouseAPI.MouseEvents.LeftButtonUp, point, true);
}
但是上面的代码是非常“机械”的,也即是不管当前鼠标所在的窗口是不是在房间上,它都会自动“点击”一次!这样可不人性化,所以我们再改进一下,点击时先判断当前鼠标停留的是不是在对战平台的房间上,但可惜的QQ对战平台的房间不是标准WINDOWS控件,所以无法获取到房间的数据,最后只好简单点判断鼠标停留的窗口是不是QQ对战平台的大厅,因为大厅是可获取到的WINDOWS窗口控件,如下图是用Spy++获取到的大厅数据:
也就是我们判断鼠标停留的窗口的类型是不是属于“Afx:400000:3”即可,如果是则表明鼠标是停留在QQ对战平台的游戏大厅中的,就可以点击房间了(注意:这里停留的位置不一定是房间上,但这不影响使用,所以我就没加以处理了)
这里需要使用的API有:
3)、WindowFromPoint : 获取某个坐标位置所在的窗口
定义原型如下:
[DllImport("user32.dll")] internal static extern IntPtr WindowFromPoint(Point Point);
4)、GetClassName : 获取某个窗口的类型名称
定义原形如下:
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] internal static extern int GetClassName(IntPtr hWnd, StringBuilder buf, int nMaxCount);
加入上面的条件判断,上面的代码改进后如下:
if (Win32API.GetCursorPos(out point))
{
hwnd = Win32API.WindowFromPoint(point);
if (hwnd != IntPtr.Zero)
{
string className = Win32API.GetWindowClassName(hwnd);
if (className.Equals("Afx:400000:3"))
{
MouseAPI.SendMouseEvent(MouseAPI.MouseEvents.LeftButtonDown | MouseAPI.MouseEvents.LeftButtonUp, point, true);
}
}
}
这样“点击房间”这步就可以算完美了,当鼠标不在房间(正确的说不在游戏大厅)上停留时,鼠标也不会乱点,这就方便在“挤房间”时随时可以切换窗口了。
2、判断是否已进入房间,如果没有进入房间,则负责将显示房间满的两个讨厌的提示窗口关闭掉。
1)判断是否已进入房间: 和上面那步判断鼠标是否停留在游戏大厅一样,也是判断鼠标是否停留在聊天室中,如果是那么就表示已挤进去了。用Spy++获取到聊天室的窗口类型分别是“RichEdit20A”和“Afx:44a0000:0” ,所以判断代码如下:
//判断鼠标是否停留在聊天窗口上
if (Win32API.GetCursorPos(out point))
{
hwnd = Win32API.WindowFromPoint(point);
if (hwnd != IntPtr.Zero)
{
string className = Win32API.GetWindowClassName(hwnd);
if (className.Equals("RichEdit20A")
|| className.Equals("Afx:44a0000:0"))
{
isOk = true;
}
}
}
如果isOk=true则表示已挤进房间。则就可以停止“挤房”,否则就要处理那两个讨厌的提示窗口了。
2)关闭提示窗口:
先需要判断是否有这两个提示窗口出现,因为在挤房过程中,QQ对战平台会优先显示一个进度条(窗口)。我们为了简单点处理,只是判断当前活动窗口是否是“提示窗口”即可,也即是简单的判断当前活动窗口中是否包含那个“确定”按钮,如果存在,那么就简单的认为这窗口是那提示窗口,关闭它即可。需要用到的API函数有以下几个:
A、GetForegroundWindow : 获取当前活动窗口
它的定义原形:
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] public static extern IntPtr GetForegroundWindow();
B、FindWindowEx : 查找某个窗口
它的定义原型:
[DllImport("user32.dll", EntryPoint = "FindWindowEx", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
关闭提示窗口,则只使用简单的方法,也即是发送“回车键”即可,因为我们获取到的窗口是当前活动窗口,并且又是模式窗口,所以它能接受键盘消息。关于键盘的模拟,请参考我的这篇文章《C#对游戏手柄的编程开发-API篇(3)》。
最后代码如下:
if (!isOk)
{
//没有挤进去,则判断是否有提示窗口弹出,有的话关闭掉它
hwnd = Win32API.GetForegroundWindow();
if (hwnd != IntPtr.Zero)
{
//查找活动窗口是否包含有"确定"按钮
IntPtr buttonHwnd = Win32API.FindWindowEx(hwnd, IntPtr.Zero, "Button", null);
if (buttonHwnd != IntPtr.Zero)
{
KeyboardAPI.SendKeyEvent(Keys.Enter, KeyboardAPI.KeyboardEvents.KeyDown | KeyboardAPI.KeyboardEvents.KeyUp);
}
}
}
isOk是上面判断有没有“挤”进去的值,如果没有“挤”进去就进行上面的“关闭提示窗口”的操作,如果已“挤进去”则停止即可。
到此,将上面的所有代码整合到一起,并加入到Timer的定时执行中,我们的“QQ对战平台挤房器”就实现了。
以下提供程序下载(不提供源码,但你可以对其反编译查看):
/Files/kingthy/QQBattleThruster.rar
本文转自Kingthy博客园博客,原文链接:http://www.cnblogs.com/kingthy/archive/2009/10/20/qq-battle-thruster.html,如需转载请自行联系原作者
为什么要学Win32及Win32程序框架
源代码下载地址:http://www.vczx.com/article/file/20041202141740_vczx_Win32sdk.rar前言: 最近在看侯捷的《深入浅出MFC》,在理解MFC时觉得必须要掌握win32,才能更好的理解MFC,才能写出更有用的程序。于是将自己对win32程序的理解写了下来,供朋友们参考。文中的两幅图是从书中截取的。正文: 现在有很多想走VC这条路的朋友,一开始就是MFC,虽然啃过一段时间书后,能写出一些小程序,但越往后你就会越觉得困难。我的一个网友昨天跟我聊天的时候说“TMD,MFC用的越来越觉得不爽,早知道开始学VC的时候听朋友之劝,先学Win32编程好了”。的确,MFC把大部分精华都封装起来了,我们只能用它现成的库,或尤其派生出一些自己的库。如果不了解Win32编程,我们就不懂程序的运作原理,那么我们如何操控程序,我们还哪有自主权? 如果你从来没有在事件驱动(event driven)系统中撰写过以消息为基础(message based)的应用程序,就想一步跨入MFC,直接用Application wizard开发windows程序,我觉得不大可能。虽然你可以继承MFC中的类,来开发出一个颇具规模的windows应用程序,但如果你不了解windows程序的运作本质(event driven,message based ),是不可能进入高深境界的。正如侯大师所言:“勿在浮沙筑高台”,不会走千万别想跑。 在正式学习MFC之前,需要掌握的东西(个人认为)有:了解windows程序的事件驱动机制(包括消息的产生、捕获、分派和处理等)。另外还有一个比较重要的就是C++中多态(polymorphism)和虚函数的理解(不说精通,起码也得达到熟练)。 下面简单说一下Win32程序框架。 一个Win32程序是由程序代码和UI(User Interface)两大部分组成,当我们编辑好这两部分后,再由RC(resource compiler)编译器将这两部分整合成一个EXE文件。程序代码不用说了,UI资源指的是一些如菜单、对话框、位图、鼠标指针、图标等,我们必须在一个.rc文件中描述它们。 另外,程序要想成功编译运行,还需要加入一些函数库和头文件。Windows程序调用的函数可以分为C Runtimes和Windows API两种,LIBC.LIB是C Runtimes的静态连接版,MSVCRT.LIB是C Runtimes的动态连接版。GDI32.DLL、USER32.DLL和KERNEL32.DLL是32位Windows API的三大函数库。所有的windows程序都要包含windows.h,但windows.h只包含GDI32.DLL、USER32.DLL和KERNEL32.DLL中的函数,如果你还想加入别的dll,就需要加入相应的头文件。下面是win32程序开发的流程: Windows程序的运行是靠外部事件来驱动的,就是说,程序一直出于一个等待的状态,如果有一个事件发生,程序就会判断是什么事件,然后做出相应的处理。那么每一个windows程序都必须有一个回路才能实现一直等待。这个回路如下:MSG msg;while( GetMessage( &msg, NULL, 0, 0 ) ){TranslateMessage( &msg );DispatchMessage( &msg );}MSG结构在windows内部是这样定义的:typedef struct tagMsg{HWND hWnd;UINT message;WPARAM wParam;LPARAM lParam;DWORD time;POINT pt;}MSG; 其中message就是指各种消息,如WM_MOUSEMOVE、WM_DESTROY等。那么谁来接收这个消息并做出相应处理呢?就是窗口,这就需要我们为窗口设计一个函数,即所谓的窗口函数。窗口函数形如:LRESULT CALLBACK WndProc( HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam){switch ( message ) {case WM_LBUTTONDOWN:…case WM_MOUSEMOVE:…case WM_DESTROY:PostQuitMessage ( 0 );default:return DefWindowProc ( hWnd, message, wParam, lParam );}return ( 0 );} CALLBACK是一种函数调用习惯,被定义为__stdcall,说明此函数为回调函数,由系统自动调用的,当窗口接收到消息并DispatchMessage之后,系统就自动调用窗口函数WndProc了。注意窗口函数中消息的分支结构中default分支必须是return DefWindowProc ( hWnd, message, wParam, lParam );因为不论什么消息都必须被处理,DefWindowProc是windows内部预设的消息处理函数。以上就是windows程序的精要所在,弄懂了这些,才能为学MFC作好准备。Win32程序的运行图解如下: 那么窗口的产生和显示是怎么实现的呢?下面我们就来生成一个简单的win32SDK窗口,来看一下windows程序是如何把消息获取、分派并处理的,以及写一个win32窗口程序的主要步骤:一、程序进入点 windows程序的进入点是WinMain函数,它有四个参数,形式如下:int WINAPI WinMain ( HINSTANCE hInst,HINSTANCE hPrevInst,LPSTR lpCmdLine,int nCmdShow ) 参数说明:hInst 为当前实例句柄,Windows 环境下用于区别同一应用程序的不同实例;hPrevInst应用程序先前实例的句柄(如果有的话),否则为 NULL,可以用来确定当前实例是否为应用程序的第一个实例;lpCmdLine是以NULL结尾的命令行字符串长指针;nCmdShow指定窗口初始显示方式的整型常量(1 = 通常;7 = 最小化) 。二、注册窗口类 一个窗口在建立以前,必须进行一些初始化,比如窗口的大小、标题及边框的颜色等,完成这些工作我们还必须一个API函数RegisterClass来注册窗口类:WNDCLASS wndclass;//初始化窗口的属性…………….//注册窗口类if(!RegisterClass(&wndclass)){MessageBeep(0);return FALSE;}三、创建窗口 窗口属性设置好并且注册了窗口类之后,我们就要建立一个窗口了,CreateWindow函数会完成这个工作,如:hwnd=CreateWindow(lpszClassName,lpszTitle,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hInstance,NULL); 建立好窗口之后就要显示窗口了,如下: //显示窗口 ShowWindow(hwnd,nCmdShow);UpdateWindow(hwnd); 然后进步消息循环,程序出于一直等待的状态(除非你退出程序)。//消息循环while(GetMessage(&Msg,NULL,0,0)){TranslateMessage(&Msg);DispatchMessage(&Msg);}四、窗口函数 这是窗口的生命中枢,因为它是处理各种消息的地方。上面消息循环中DispatchMessage就是将消息分派到窗口处理函数,窗口处理函数前面已经作了说明,这里不再赘述。 这样,一个win32窗口程序就写好了,具体详见附加代码。后记:
这篇文章根据自己的理解写的,如有不恰当之处,肯请指正。最后,希望这篇文章对大家能有点帮助。更多编程技术文章,请访问我的网站 http://www.vczx.com
本文转自loose_went博客园博客,原文链接:http://www.cnblogs.com/michaelxu/archive/2006/09/20/509043.html,如需转载请自行联系原作者
C# WPF MVVM QQ密码管家项目(8,完结篇:自动输入QQ号、密码)
原文:C# WPF MVVM QQ密码管家项目(8,完结篇:自动输入QQ号、密码)
目录:
1,界面设计
2,数据模型的建立与数据绑定
3,添加QQ数据
4,修改QQ数据
5,删除QQ数据
6,密码选择输入界面数据绑定
7,对QQ登录界面的自动输入思路分析
8,完结篇:自动输入QQ号、密码
接上篇,获取QQ登录界面句柄、窗口位置、鼠标/键盘操作等都需要用到win32api
win32api是windows系统预留的接口,通过接口我们可以实现对系统更加深度地操作。
第1步:获取窗口句柄
win32api获取窗口句柄方法c#代码:
#region 查找窗口句柄
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
#endregion
有两个参数可以填,第一个lpClassName填类名,第二个lpWindowName填窗口名。也就是说可以通过类名或者窗口名获得指定窗口的句柄(不填的参数应为:null),也可以两个参数都填上(在某些情况下使用用于精准定位窗口)。这里的话我们只填一个窗口名参数即可。
FindWindow(null, "QQ");
QQ窗口名通过spy++可以获取。
重温一下自动输入的步骤:
1,获取登录窗口句柄;
2,获得窗口大小;
3,获得窗口坐标;
4,计算两个输入框的位置;
5,模拟鼠标选中输入框获得输入焦点;
6,模拟键盘输入,将qq号、密码输入。
第1步已完成,QQ登录窗口句柄我们已经得到了。接下来是第2步,获得窗口大小。
因为win32api中并没有直接提供一个获取窗口大小的方法,但提供了一个获取窗口尺寸的方法。你可能有点晕,我们先看一下获取窗口尺寸的win32api方法官方说明:
BOOL GetWindowRect(HWND hWnd,LPRECT lpRect);
该函数返回指定窗口的边框矩形的尺寸。该尺寸以相对于屏幕坐标左上角的屏幕坐标给出
该函数返回指定窗口的边框矩形的尺寸,该尺寸是相对于屏幕左上角的,而不是直接给你一个宽是多高是多少的直接数据,所以我们需要稍作计算。
#region 获得窗口位置
[DllImport("user32.dll")]
private static extern int GetWindowRect(IntPtr hwnd, out Rect lpRect);
public struct Rect
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
#endregion
使用的时候填入两个参数,第一个是窗口句柄,第二个是输出的数据变量。那么输出的Rect结构数据就是窗口相对于屏幕的尺寸了。
int Left是窗口左边框相对于屏幕最左边的距离;
int Top是窗口顶部边框相对于屏幕顶部的距离;
int Right是窗口右边框相对于屏幕最左边的距离;
int Bottom是窗口下边框相对于屏幕顶部的距离;
假设B为屏幕,A是QQ登录界面,那么GetWindowRect获取到的相应数据如图所示
将RIGHT减去LEFT得到的就是窗口的真实宽度,将BOTTOM-TOP得到的就是窗口的真实高度。到这里第2个步骤“获得窗口大小”就完成了。
我们整合成一个方法
#region 获得窗口大小
public class Size
{
public int width { get; set; }
public int height { get; set; }
}
public static Size GetWindowSize(IntPtr window)
{
Rect rect = new Rect();
GetWindowRect(window, out rect);
return new Size()
{
width = rect.Right - rect.Left,
height = rect.Bottom - rect.Top
};
}
#endregion
第3步获取窗口坐标其实在获取窗口尺寸的时候已经完成了,RECT int Left就是窗口所在的X坐标,int Top就是窗口的Y坐标。又省了一步。
第4步,计算两个输入框的位置:
有句话在魔术表演中经常能听到,眼睛会欺骗你。窗体也是,你所见的并不一定是真实的宽和高。试试打开QQ截图功能将鼠标移动到QQ登录窗口上试试,会有一个默认的框选区域(框选出窗口的完整区域),得到的是这样一个范围截图:
也就是说其实QQ登录窗口是多出很多透明区域的,如果你忽略了这些区域,将来计算是会出现偏差的,我是没办法负责任的哦。所以,我们在计算输入框位置时要把透明的区域计算进去。(用QQ截图的默认框选区域在某些情况下也不一定精准,请用我们刚才第2步写的GetWindowSize方法获取到宽高的数据来计算)
得到QQ登录窗口真实尺寸,可以将截图放入PS中,或者用你喜欢的办法量出几个数据。如下图:
上图我画出了3条线,A、B、C。要获得QQ号码输入框的输入焦点我们要让程序模拟鼠标点击一下输入框的位置,也就是坐标(A,B),也就是点击QQ号码输入框输入区域的位置(只要坐标在输入框内即可,不一定是跟我画的线坐标一样的);那么密码输入框点击的坐标就是(A,C),只有三条线是因为密码输入框的横坐标跟QQ号码输入框的横坐标是一样的,所以直接用A就行。
QQ号码输入框应该点击的坐标是:x=(A÷窗口宽度)×窗口宽度+LEFT(窗口距离屏幕左边的距离),y=(B÷窗口高度)×窗口高度+TOP(窗口距离屏幕顶部的距离);
QQ密码输入框应该点击的坐标是:x=(A÷窗口宽度)×窗口宽度+LEFT,y=(C÷窗口高度)×窗口高度+TOP;
窗口宽高和窗口距离屏幕的距离我们第2步已经给出了~
注意,这里的A、B、C的距离都需要手动量的(可以用PS拉个线条量或者下载个屏幕直尺软件什么的用你喜欢的就行)。
代码(省略了上面的计算步骤):
#region 获得qq号、密码输入框坐标
public class Point
{
public int x { get; set; }
public int y { get; set; }
}
public static Point GetQQNumberInputPoint(IntPtr window)
{
double A = 0.3656565656565657;
double B = 0.5978723404255319;
//获得窗口宽度
Size window_size = GetWindowSize(window);
//获得窗口尺寸
Rect rect = new Rect();
GetWindowRect(window, out rect);
//计算
int x = (int)(A * window_size.width + rect.Left);
int y = (int)(B * window_size.height + rect.Top);
return new Point()
{
x = x,
y = y
};
}
public static Point GetQQPassInputPoint(IntPtr window)
{
double A = 0.3656565656565657;
double C = 0.6617021276595745;
//获得窗口宽度
Size window_size = GetWindowSize(window);
//获得窗口尺寸
Rect rect = new Rect();
GetWindowRect(window, out rect);
//计算
int x = (int)(A * window_size.width + rect.Left);
int y = (int)(C * window_size.height + rect.Top);
return new Point()
{
x = x,
y = y
};
}
#endregion
接下来第5步,模拟鼠标点击输入框。win32api方法是:
#region 设置光标位置
[DllImport("user32.dll")]
private static extern bool SetCursorPos(int X, int Y);
#endregion
到第6步,非常关键的一步了。我们的qq密码中可能会存在很多种组合大小写字母+数字+符号,模拟键盘操作的时候需要注意大小写字母的判断和切换,还有某些符号是需要按住shift键才能按出来的,情况非常复杂。但是,没有什么是编程不能实现的。
首先看键盘操作的win32api方法:
#region 键盘操作
[DllImport("user32.dll", EntryPoint = "keybd_event")]
public static extern void keybd_event(
byte bVk, //虚拟键值
byte bScan,// 一般为0
int dwFlags, //这里是整数类型 0 为按下,2为释放
int dwExtraInfo //这里是整数类型 一般情况下设成为 0
);
#endregion
这个虚拟键值可以通过搜索引擎查到完整的,我这就不贴出来了,但是呢,我们不需要去查,为什么呢?有win32api可以直接将字符转为虚拟键值。
#region 将一个字符转换为虚拟键值
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern short VkKeyScan(char ch);
#endregion
注意哦,是字符,不是字符串,win32api没有方便到自动帮我们转换一整个字符串的键码。所以等会我们还需要稍加改进。
刚才说了有些符号需要按住shift键,还要区分大小写,所以我们需要一些win32api去判断和操作。
#region 大小写、shift等键状态获取和设置
//大写锁定是否启用
public static bool IsCapsLock = false;
const uint KEYEVENTF_EXTENDEDKEY = 0x1;
const uint KEYEVENTF_KEYUP = 0x2;
[DllImport("user32.dll")]
static extern short GetKeyState(int nVirtKey);
[DllImport("user32.dll")]
public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo);
public enum VirtualKeys : byte
{
VK_NUMLOCK = 0x90, //数字锁定键
VK_SCROLL = 0x91, //滚动锁定
VK_CAPITAL = 0x14, //大小写锁定
VK_A = 62
}
public static bool GetState(VirtualKeys Key)
{
return (GetKeyState((int)Key) == 1);
}
public static void SetState(VirtualKeys Key, bool State)
{
if (State != GetState(Key))
{
keybd_event((byte)Key, 0x45, KEYEVENTF_EXTENDEDKEY | 0, 0);
keybd_event((byte)Key, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
}
}
//设置大小写按钮是否启用(true启用,false禁用)
public static void SetCapLock(bool islock)
{
if (IsCapsLock != islock)
{
keybd_event((byte)VirtualKeys.VK_CAPITAL, 0x45, KEYEVENTF_EXTENDEDKEY | 0, 0);
keybd_event((byte)VirtualKeys.VK_CAPITAL, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
IsCapsLock = islock;
}
}
#endregion
整合上面的代码变成一个方法(注释比较多也不知道哪些地方需要解释,所以下面开始就不多说了直接贴代码,不懂的地方可以在评论提问吧):
#region 通过字符串模拟键盘输入
public static bool IsNeedShift(char c)
{
//需要按SHIFT的字符
char[] needlist = { '<', '>', '?', ':', '"', '|', '{', '}', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+' };
if (needlist.Contains(c))
{
return true;
}
else
{
return false;
}
}
public static void SendKeyOfString(string str)
{
foreach (char c in str)
{
bool isshiftdown = false;
//判断字符是否是符号
if (char.IsPunctuation(c) || char.IsSymbol(c))
{
//判断符号是否需要按住shift
if (IsNeedShift(c))
{
//需要按shift
//输入键值
keybd_event((byte)16, 0, 0, 0);
isshiftdown = true;
}
}
//判断字符是否是数字
if (char.IsNumber(c) == false)
{
//不是数字,判断是否是大写字母
if (char.IsUpper(c))
{
//大写字母
//开启caps lock锁定
SetCapLock(true);
}
else
{
//小写字母
//关闭caps lock锁定
SetCapLock(false);
}
}
//模拟键盘按下
keybd_event((byte)VkKeyScan(c), 0, 0, 0);
//模拟键盘释放
keybd_event((byte)VkKeyScan(c), 0, 2, 0);
if (isshiftdown)
{
//释放shift
keybd_event((byte)16, 0, 2, 0);
isshiftdown = false;
}
}
}
#endregion
到此自动输入的步骤代码我们都完成了,现在开始整合上面的代码完成我们的项目。
在项目根目录新建一个文件夹:Cores(核心代码),在cores文件夹新建一个类:Win32API.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace qqkeys.Cores
{
public class Win32API
{
//win32api
#region win32api
#region 查找窗口句柄
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
#endregion
#region 设置光标位置
[DllImport("user32.dll")]
public static extern bool SetCursorPos(int X, int Y);
#endregion
#region 鼠标操作
//移动鼠标
public const int MOUSEEVENTF_MOVE = 0x0001;
//模拟鼠标左键按下
public const int MOUSEEVENTF_LEFTDOWN = 0x0002;
//模拟鼠标左键抬起
public const int MOUSEEVENTF_LEFTUP = 0x0004;
//模拟鼠标右键按下
public const int MOUSEEVENTF_RIGHTDOWN = 0x0008;
//模拟鼠标右键抬起
public const int MOUSEEVENTF_RIGHTUP = 0x0010;
//模拟鼠标中键按下
public const int MOUSEEVENTF_MIDDLEDOWN = 0x0020;
//模拟鼠标中键抬起
public const int MOUSEEVENTF_MIDDLEUP = 0x0040;
//标示是否采用绝对坐标
public const int MOUSEEVENTF_ABSOLUTE = 0x8000;
[DllImport("user32.dll")]
private static extern void mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo);
#endregion
#region 键盘操作
[DllImport("user32.dll", EntryPoint = "keybd_event")]
public static extern void keybd_event(
byte bVk, //虚拟键值
byte bScan,// 一般为0
int dwFlags, //这里是整数类型 0 为按下,2为释放
int dwExtraInfo //这里是整数类型 一般情况下设成为 0
);
#endregion
#region 获得窗口位置
[DllImport("user32.dll")]
private static extern int GetWindowRect(IntPtr hwnd, out Rect lpRect);
public struct Rect
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
#endregion
#region 大小写、shift等键状态获取和设置
//大写锁定是否启用
public static bool IsCapsLock = false;
const uint KEYEVENTF_EXTENDEDKEY = 0x1;
const uint KEYEVENTF_KEYUP = 0x2;
[DllImport("user32.dll")]
static extern short GetKeyState(int nVirtKey);
[DllImport("user32.dll")]
public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo);
public enum VirtualKeys : byte
{
VK_NUMLOCK = 0x90, //数字锁定键
VK_SCROLL = 0x91, //滚动锁定
VK_CAPITAL = 0x14, //大小写锁定
VK_A = 62
}
public static bool GetState(VirtualKeys Key)
{
return (GetKeyState((int)Key) == 1);
}
public static void SetState(VirtualKeys Key, bool State)
{
if (State != GetState(Key))
{
keybd_event((byte)Key, 0x45, KEYEVENTF_EXTENDEDKEY | 0, 0);
keybd_event((byte)Key, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
}
}
//设置大小写按钮是否启用(true启用,false禁用)
public static void SetCapLock(bool islock)
{
if (IsCapsLock != islock)
{
keybd_event((byte)VirtualKeys.VK_CAPITAL, 0x45, KEYEVENTF_EXTENDEDKEY | 0, 0);
keybd_event((byte)VirtualKeys.VK_CAPITAL, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
IsCapsLock = islock;
}
}
#endregion
#region 将一个字符转换为键码
[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern short VkKeyScan(char ch);
#endregion
#endregion
//扩展
#region 扩展
#region 鼠标单击
public static void MouserClick()
{
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0); //模拟鼠标按下操作
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0); //模拟鼠标放开操作
}
#endregion
#region 获得窗口大小
public class Size
{
public int width { get; set; }
public int height { get; set; }
}
public static Size GetWindowSize(IntPtr window)
{
Rect rect = new Rect();
GetWindowRect(window, out rect);
return new Size()
{
width = rect.Right - rect.Left,
height = rect.Bottom - rect.Top
};
}
#endregion
#region 获得qq号、密码输入框坐标
public class Point
{
public int x { get; set; }
public int y { get; set; }
}
public static Point GetQQNumberInputPoint(IntPtr window)
{
double A = 0.3656565656565657;
double B = 0.5978723404255319;
//获得窗口宽度
Size window_size = GetWindowSize(window);
//获得窗口尺寸
Rect rect = new Rect();
GetWindowRect(window, out rect);
//计算
int x = (int)(A * window_size.width + rect.Left);
int y = (int)(B * window_size.height + rect.Top);
return new Point()
{
x = x,
y = y
};
}
public static Point GetQQPassInputPoint(IntPtr window)
{
double A = 0.3656565656565657;
double C = 0.6617021276595745;
//获得窗口宽度
Size window_size = GetWindowSize(window);
//获得窗口尺寸
Rect rect = new Rect();
GetWindowRect(window, out rect);
//计算
int x = (int)(A * window_size.width + rect.Left);
int y = (int)(C * window_size.height + rect.Top);
return new Point()
{
x = x,
y = y
};
}
#endregion
#region 通过字符串模拟键盘输入
//判断字符是否需要按住shift
public static bool IsNeedShift(char c)
{
//需要按SHIFT的字符
char[] needlist = { '<', '>', '?', ':', '"', '|', '{', '}', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+' };
if (needlist.Contains(c))
{
return true;
}
else
{
return false;
}
}
//模拟键盘操作输入指定字符串,不支持中文!!!!!!
public static void SendKeyOfString(string str)
{
foreach (char c in str)
{
bool isshiftdown = false;
//判断字符是否是符号
if (char.IsPunctuation(c) || char.IsSymbol(c))
{
//判断符号是否需要按住shift
if (IsNeedShift(c))
{
//需要按shift
//输入键值
keybd_event((byte)16, 0, 0, 0);
isshiftdown = true;
}
}
//判断字符是否是数字
if (char.IsNumber(c) == false)
{
//不是数字,判断是否是大写字母
if (char.IsUpper(c))
{
//大写字母
//开启caps lock锁定
SetCapLock(true);
}
else
{
//小写字母
//关闭caps lock锁定
SetCapLock(false);
}
}
//模拟键盘按下
keybd_event((byte)VkKeyScan(c), 0, 0, 0);
//模拟键盘释放
keybd_event((byte)VkKeyScan(c), 0, 2, 0);
if (isshiftdown)
{
//释放shift
keybd_event((byte)16, 0, 2, 0);
isshiftdown = false;
}
}
}
#endregion
#endregion
}
}
在cores文件夹下新建一个类:QQAutoInput.cs
using qqkeys.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static qqkeys.Cores.Win32API;
namespace qqkeys.Cores
{
public class QQAutoInput
{
public static void Start(QQModel qm)
{
//需要获取大小写锁定状态
IsCapsLock =Win32API.GetState(VirtualKeys.VK_CAPITAL);
//获取QQ窗口句柄
IntPtr qqptr = Win32API.FindWindow(null, "QQ");
//获得qq号码输入框坐标
Point qqnumberinputpoint = Win32API.GetQQNumberInputPoint(qqptr);
//获得qq密码输入框坐标
Point qqpassinputpoint = Win32API.GetQQPassInputPoint(qqptr);
//将鼠标指针移动到QQ号输入框位置
Win32API.SetCursorPos(qqnumberinputpoint.x, qqnumberinputpoint.y);
//双击鼠标
MouserClick();
MouserClick();
//鼠标点击后需要加延迟,不然反应跟不上导致漏输入或者不输入
Thread.Sleep(100);
SendKeyOfString(qm.qq.ToString());
Thread.Sleep(100);
SetCursorPos(qqpassinputpoint.x, qqpassinputpoint.y);
MouserClick();
MouserClick();
Thread.Sleep(100);
SendKeyOfString(qm.password);
}
}
}
在KeysViewModel.cs中新建一个命令:Command_Input
//命令
public Command Command_Input { get; set; }
public KeysViewModel()
{
............................
Command_Input = new Command(new Action<object>(Command_Input_Do));
}
private void Command_Input_Do(object obj)
{
QQAutoInput.Start(SelectQQ);
}
最后打开KeysWindow.xaml,修改设计代码的“输入”按钮:
<Button Command="{Binding Command_Input}" Width="100" Height="25" Content="输入" Margin="0,10,0,0"></Button>
到这里这个项目已经完成
运行程序,看下效果~
项目文件下载:
点我下载
VC Windows API应用之GetDesktopWindow ——获得桌面所有窗口句柄的方法
Windows API
Windows 这个多作业系统除了协调应用程序的执行、分配内存、管理资源…之外, 它同时也是一个很大的服务中心,调用这个服务中心的各种服务(每一种服务就是一个函数),可以帮应用程式达到开启视窗、描绘图形、使用周边设备等目的,由于这些函数服务的对象是应用程序(Application), 所以便称之为 Application Programming Interface,简称 API 函数。WIN32 API也就是Microsoft Windows 32位平台的应用程序编程接口。
GetDesktopWindow
函数功能:该函数返回桌面窗口的句柄。桌面窗口覆盖整个屏幕。桌面窗口是一个要在其上绘制所有的图标和其他窗口的区域。 函数原型:HWND GetDesktopWindow(VOID) 参数:无。 返回值:函数返回桌面窗口的句柄。 速查:Windows NT:3.1以上版本;Windows:95以上版本:; 头文件:Winuser.h;库文件:user32.lib。 【声明】 vb
Public Declare Function GetDesktopWindow Lib “user32” Alias “GetDesktopWindow” () As Long vb_net
Public Declare Function GetDesktopWindow Lib “user32” Alias “GetDesktopWindow” () As Integer c#
[DllImport(“user32.dll”, EntryPoint = “GetDesktopWindow”, CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr GetDesktopWindow();
【说明】
获得代表整个屏幕的一个窗口(桌面窗口)句柄 【返回值】
Long,桌面窗口的句柄
获得桌面所有窗口句柄的方法
创建项目
文件->新建->项目…
编写方法
// GetDesktopWindow.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#define _AFXDLL
#include <afxwin.h>
//错误 1 error C1189: #error : Building MFC application with /MD[d] (CRT dll version) requires MFC shared dll version.
//Please #define _AFXDLL or do not use /MD[d] e:\programfilesx86\microsoftvisualstudio10\vc\atlmfc\include\afx.h 24 1 GetDesktopWindow
int _tmain(int argc, _TCHAR* argv[])
{
//1.先获得桌面窗口
CWnd* pDesktopWnd = CWnd::GetDesktopWindow();
//2.获得一个子窗口
CWnd* pWnd = pDesktopWnd->GetWindow(GW_CHILD);
//3.循环取得桌面下的所有子窗口
while(pWnd != NULL)
{
//获得窗口类名
CString strClassName = _T("");
::GetClassName(pWnd->GetSafeHwnd(),strClassName.GetBuffer(256),256);
//获得窗口标题
CString strWindowText = _T("");
::GetWindowText(pWnd->GetSafeHwnd(),strWindowText.GetBuffer(256),256);
//继续下一个子窗口
pWnd = pWnd->GetWindow(GW_HWNDNEXT);
}
return 0;
}
Delphi防止同时出现多个应用程序实例--CreateMutex
多实例指同时有同一个应用程序的多个副本在运行。同一个应用程序的多个副本可以相互独立地同时运行,是Win32操作系统提供的一个功能。但有时,我们可能希望用户启动应用程序后就不再启动它的别的副本。比如某种设备资源的控制程序,像调制解调器和并行端口。这种情况下,用程序代码防止同时出现多个程序的副本在运行是非常必要的。 在16位的Windows中,要防止出现多个实例是很简单的,因为系统变量hPrevInst可以被用来判断是否有其他的实例存在。当hPrevInst变量不为0时,表示已经有别的应用程序实例在运行。 然而,在Win32系统中每个进程之间有R32绝缘层来彼此隔绝。因此,在Win32系统中变量hPrevInst的值总为0。另一种既适合Win32系统又适合于16位的Windows的技术,是调用FindWindow()API函数去搜索一个已激活的程序窗口。 Windows API 提供了函数FindWindow,可以是应用程序在启动时检查自己是否已经存在。 该函数在Delphi中的语法为: function FindWindow(lpClassName: PChar, lpWindowName: PChar): HWND; 其中,参数lpCalssName 是要查找的窗口的类的名称,参数lpWindowName是要查找的窗口的标题(Caption)。 如果找到了相应的窗口实例,将返回一个非0 的该窗口句柄的整型值,否则返回0 。因此,只要判断应用程序的主窗口(或者伴随着应用程序存在而存在的窗口)是否存在就可以判断是否已经有实例存在了。 例如:
H := FindWindow('TForm1', nil); if H = 0 then begin
ShowMessage('没有发现相同的应用程序实例。');
//加入加载应用程序的语句
// end else begin
ShowMessage('应用程序已经加载。');
SetActiveWindow(H);
end;
其中,参数lpWindowName的位置以Delphi保留字nil 代替,是因为窗口的标题可能在应用程序中是变化的。Windows API 函数SetActiveWindow 用于指定活动窗口。 但是,这种方法有两个缺陷:一是它只能基于窗口类名或标题来搜索窗口,但是在整个系统中窗口很可能会重复。所以,这样做是不可靠的。而利用窗口的标题的方法也有问题,因为窗口的标题有可能发生变化(以Delphi和Word为例,每次打开不同文件,它们的标题都会变化),所以这种方法不可取。另一个缺陷是它每次搜索都要遍历所有窗口,这样执行进来非常慢。 因此,在Win32系统中最好的解决方案是利用那些不依赖于进程的API对象,并且它们的使用也很简单,互斥对象就可以解决这个问题。当一个应用程序首次运行时,我们就使一个互斥对象被API函数CreateMutex()创建。这个函数的参数lpName是一个唯一标识互斥对象的字符串。当应用程序的实例要运行前,它首先要用OpenMutex()来打开互斥对象,如果已经有一个CreateMutex()创建的互斥对象则返回非零值。另外,当试图运行另一个程序实例时,使第一个实例被激活。 对于这个问题,最好的解决方法是在首次运行时,利用RegisterWindowMessage()函数注册一个消息,并在应用程序中创建唯一的消息标识符。然后,利用第一个实例对这个消息的响应使它被第二个实例激活。 这种方法阻止新实例的产生,但不能提前,不过较简便。 在Project的Program文件中
program Live;
uses
Windows,
Forms,
ShellApi,
SysUtils,
..;
{$R *.TLB}
{$R *.res}
var
HMutex:Hwnd;
Ret:Integer;
begin
Application.Initialize;
aTitle := 'LiveAuction';
Application.Title := 'LiveAuction';
HMutex:=CreateMutex(nil,False,Pchar(aTitle)); //建立互斥对象,名字为aTitle--'LiveAuction' Ret:=GetLastError;
If Ret<>ERROR_ALREADY_EXISTS Then
begin
//做我们正常该做的事情 end else
ReleaseMutex(hMutex); //防止创建多个程序实例
Application.Run;
end.
本文转自 OldHawk 博客园博客,原文链接:http://www.cnblogs.com/taobataoma/archive/2007/08/15/856415.html,如需转载请自行联系原作者
Win32 程序在启动时激活前一个启动程序的窗口
原文:Win32 程序在启动时激活前一个启动程序的窗口
版权声明:本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:http://blog.csdn.net/wpwalter/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系(walter.lv@qq.com)。 https://blog.csdn.net/WPwalter/article/details/82119769
UWP 程序天生单实例。当然,新 API (10.0.17134)开始也提供了多实例功能。不过,传统 Win32 程序可就要自己来控制单实例了。
本文介绍简单的几个 Win32 方法调用,使 Win32 程序也支持单实例。
激活之前进程的窗口
我们可以通过进程名称找到此前已经启动过的进程实例,如果发现,就激活它的窗口。
[STAThread]
static void Main(string[] args)
{
var current = Process.GetCurrentProcess();
var process = Process.GetProcessesByName(current.ProcessName).FirstOrDefault(x => x.Id != current.Id);
if (process != null)
{
var hwnd = process.MainWindowHandle;
ShowWindow(hwnd, 9);
return;
}
// 启动自己的主窗口,此部分代码省略。
}
[DllImport("user32.dll")]
private static extern int ShowWindow(IntPtr hwnd, uint nCmdShow);
你一定觉得那个 9 很奇怪,它是多个不同的 nCmdShow 的值:
0 Hide
1 Minimized
2 Maximized
9 Restore
另外,找到的窗口此时可能并不处于激活状态。例如在 Windows 10 中,此窗口可能在其他桌面上。那么我们需要添加额外的代码将其显示出来。
在前面的 ShowWindow 之后,再调用一下 SetForegroundWindow 即可将其激活到最前面来。如果在其他桌面,则会切换到对应的桌面。
[DllImport("USER32.DLL")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
var hwnd = process.MainWindowHandle;
ShowWindow(hwnd, 9);
SetForegroundWindow(hwnd);
找到并激活窗口
以上方法适用于普通的主窗口。然而当窗口并不是进程的主窗口,或者 ShowInTaskBar 设为了 false 的时候就不生效了(此时窗口句柄会改变)。
于是,我们需要改用其他的方式来查找窗口。
[STAThread]
static void Main(string[] args)
{
var hwnd = FindWindow(null, "那个窗口的标题栏文字");
if (hwnd != IntPtr.Zero)
{
ShowWindow(hwnd, 9);
return;
}
// 启动自己的主窗口,此部分代码省略。
}
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
参考资料
Controlling Window State Of Other Applications using C#
c# - How to show/hide an application with Visible and ShowInTaskBar as false - Stack Overflow
ShowWindowAsync function (Windows)
How do I maximize/minimize applications programmatically in C#?
C# Winform在win10里弹出无焦点的窗口
原文:C# Winform在win10里弹出无焦点的窗口
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wangmy1988/article/details/78617139
有个项目需要在右下角弹出广告窗口,但是又不能影响用户的使用,窗口不能有焦点。
在网上搜了一堆,都是以下的代码,在form中添加一段样式。
protected override CreateParams CreateParams
{
get
{
const int WS_EX_NOACTIVATE = 0x08000000;
CreateParams cp = base.CreateParams;
cp.ExStyle |= WS_EX_NOACTIVATE;
return cp;
}
}
当前用的是win10系统,怎么尝试都不行,没有任何效果,依旧焦点被弹出的窗口截取了。
然后网上又找到了一段代码,是利用系统的API,先获取之前的激活的窗口,弹出新窗口了再重置之前窗口为激活,这样的话还是会影响之前的操作,不够完美。
//引用系统API
[DllImport("user32.dll")]
public static extern IntPtr GetActiveWindow();
[DllImport("user32.dll")]
public static extern IntPtr SetActiveWindow(IntPtr hwnd);
private AdForm mf = null;
private void button_Click(object sender, EventArgs e)
{
IntPtr activeForm = GetActiveWindow();//先得到当前的活动窗体
if (mf == null)
{
mf = new AdForm();
mf.Show();
}
SetActiveWindow(activeForm); //在把焦点还给之前的活动窗体
}
多次尝试后得到一个结果,必须给窗体添加WS_CHILD样式才能生效。最终代码如下
protected override CreateParams CreateParams
{
get
{
const int WS_EX_NOACTIVATE = 0x08000000;
const int WS_CHILD = 0x40000000;
CreateParams cp = base.CreateParams;
cp.Style |= WS_CHILD;
cp.ExStyle |= WS_EX_NOACTIVATE;
return cp;
}
}
pywinauto 自动化框架调研
什么是Pywinauto?Pywinauto是基于Python开发的,用于自动化测试的脚本模块,主要操作于Windows标准图形界面。它可以允许你很容易的发送鼠标、键盘动作给Windows的对话框和控件。
其中,最主要功能为对windows标准控件的一系列动作可编程处理。包括对窗口的指定、鼠标或键盘操作、获得控件属性等等。程序的开始为对所需应用的启动或连接。获得程序连接后,pywinauto通过窗口的名称、类名、是否可见等属性寻找匹配的窗口,进而通过类似方法寻找用户所需的控件,并获得相应的句柄。之后,用户可通过易理解的编程方式对指定控件进行相关操作的编程,包括鼠标点击、键盘输入、判断控件间关系、获取控件属性等。
结构介绍外结构
Pywinauto是建立在CPython之上的python应用模块,实现原理由C做底层实现,因此与JPython不能兼容。
Python for windows 模块,使得windows api函数有了相应的python接口,其中的ctypes和win32api模块为api函数的对应应用封装了较为完善的接口集合。
内结构
程序的入口及对指定窗口或控件的操作,定义在application.py中。其中Application类进行对应用的开启和连接,WindowSpecification类自动映射到指定的窗口或控件上。
HwndWrapper?.py,win32_controls.py,menuwrapper.py,common_controls.py中为Pywinauto对标准控件操作的定义。控件都由HwndWrapper类继承而来,其中对控件共有的基本属性进行了定义。在控件自己的定义中,对HwndWrapper类的一些属性进行了重载,同时也定义了具有自己特色的属性和操作。对控件属性值的获取和操作定义在handleprops.py,controlsproperties.py文件中。
Win32defines.py,win32functions.py,win32structures.py文件中,为pywinauto对底层函数操作的定义和索引。其底层调用的是python的ctypes库函数所提供的接口。Ctypes库中封装了系统所使用的C语言数据类型和windows api函数操作等模块,并提供了相应的python语言接口,使用户通过python可对系统进行操作。
操作方法通常采用的使用格式为: app = application.Application()
app.start_( )
app.window.control.Click()
app[window][control].Click()
接下来,通过app[u‘无标题 – 记事本’][‘Edit’].Click()来介绍Pywinauto的控件操作流程:
通过app = application.Application() 创建应用,app.start_()启动或app.connect_()连接应用。之后向下层函数传递criteria,内容为[{‘process’ = 4320L, ‘ best_match’ = ‘无标题 - 记事本’}]。通过WindowSpecification._resolve_control( ) -> findwindows.py 寻找匹配的窗口。可通过process (或者class name, control_id等)找到handle,得到它是一个DialogWrapper类的结果。对于多个记事本已开启的情况,pywinauto会通过best_match进行匹配,使用Unique_dict数据结构,对各个窗口实行串匹配算法,选取匹配率最大的窗口句柄返回。
之后通过类似方法找到Edit控件,认定其为EditWrapper类,并找到类定义中的Click操作。 操作的具体实现还要依靠 handleprops.py的支持,底层调用 windows api函数实现。
功能分析优点: a. 方便实现对windows标准控件的各种鼠标、键盘操作。
b. 通过title、部分title、class name、是否可见等属性得到窗口或控件的句柄和指定。
c. 方便得到控件的各种属性,包括内容、title、大小、坐标、是否可用等。
d. 易于扩展。源于Python脚本语言的易扩展性,可以扩展pywinauto的功能,也可以将pywinauto作为一个模块引入更大的功能库。
局限性: a. 对非windows标准控件的操作无法实现。(为此,在测试中,我们为非标准控件的操作制作了C底层库及相应python函数接口,扩展了pywinauto)
b. 无法实现对系统时间的调整和性能评测。(e.g. 不能做time.sleep()的内部调整)
c. pywinauto仅局限于界面的自动化操作,其他系统操作(如网络操作)无法实现。
d. 对于已知流程的操作编程较方便,但对于不可知流程或需要判断的流程操作,功能有限。
对Pywinauto的扩展
自动化测试中,对Pywinauto的扩展主要在以下两个方面:
虚拟控件操作的增加
底层调用HiUILib库函数实现。在win32_controls中加入VConWrpper类和相关操作实现,并在窗口指定的过程中增加相应寻找分支,使得对虚拟控件的定位和操作与标准控件基本相同。
其他实用操作的增加
在调研的过程中,我们发现了一些其他实用的操作。因此在操作中,我们将需要的函数加入到application,hwndWrapper等文件中,并增加了多处对win32api等库的函数调用。例如注册表项的增删改操作。
case组织
我们采用Python的unittest模块的基本结构,来组织自动化case。每一个大的功能模块,为一个类,继承与unittest.TestCase。通过在测试类中添加函数的形式,对测试类中的每一个测试点(即case)进行代码编写。测试类中,一定要覆盖的是setUp()和tearDown()函数,作为每一个单元测试函数的入口和出口。Unittest提供TestSuite类来集合和组织测试类中的多个测试函数。同时,提供了TextTestRunner类来运行TestSuite里的类,并返回通过、错误、失败的测试数量以及运行时间。
(作者:liurong)
【本文转自百度测试技术空间】http://hi.baidu.com/baiduqa/blog/item/6619140b90c8e1336b60fba8.html
【关注百度技术沙龙】
本文转自百度技术51CTO博客,原文链接:http://blog.51cto.com/baidutech/743853,如需转载请自行联系原作者
使用JNA解决自动化测试无法做密码输入操作的问题
在做页面自动化(以使用selenium为例)的时候,很常见的一个场景就是输入密码。往往对于输入框都使用WebElement的sendKeys(CharSequence... keysToSend)的方法。
Java代码
1./**
Use this method to simulate typing into an element, which may set its value.
*/
void sendKeys(CharSequence... keysToSend); 一般情况下这个方法是可以胜任的,但是现在很多网站为了安全性的考虑都会对密码输入框做特殊的处理,而且不同的浏览器也不同。例如支付宝。
支付宝输入密码控件在Chrome浏览器下
支付宝输入密码控件在Firefox浏览器下
支付宝输入密码控件在IE(IE8)浏览器下
可见在不同的浏览器下是有差异的。那么现在存在两个问题。首先,selenium的sendKeys方法无法操作这样特殊的控件;其次,不同浏览器又存在差异,搞定了chrome,在IE下又不能用,这样又要解决浏览器兼容性问题。
如何解决这两个问题呢?
我们可以发现平时人工使用键盘输入密码的时候是没有这些问题的,那么我们是否可以模拟人工操作时的键盘输入方式呢?答案是肯定的,使用操作系统的API,模拟键盘发送消息事件给操作系统,可以避免所有浏览器等差异和安全性带来的问题。
我个人建议使用JNA(https://github.com/twall/jna),JNA是一种和JNI类似的技术,但是相对JNI来说更加易用。 JNA共有jna.jar和platform.jar两个依赖库,都需要引入,我们需要用到的在platform.jar中。从包结构可以看出,JNA中包含了mac、unix、win32等各类操作系统的系统API映射。如下图:
系统API映射关系在JNA的文章中有描述,如下:
数据类型的映射参见:https://github.com/twall/jna/blob/master/www/Mappings.md
本文中以windows为例演示下如何在支付宝的密码安全控件中输入密码。
JNA中关于windows平台的是com.sun.jna.platform.win32包中User32这个接口。这里映射了很多windows系统API可以使用。但是我们需要用到的SendMessage却没有。所以需要新建一个接口,映射SendMessage函数。代码如下:
1.import com.sun.jna.Native; 2.import com.sun.jna.platform.win32.User32; 3.import com.sun.jna.win32.W32APIOptions;
5.public interface User32Ext extends User32 {
User32Ext USER32EXT = (User32Ext) Native.loadLibrary("user32", User32Ext.class, W32APIOptions.DEFAULT_OPTIONS);
/**
查找窗口
@param lpParent 需要查找窗口的父窗口
@param lpChild 需要查找窗口的子窗口
@param lpClassName 类名
@param lpWindowName 窗口名
@return 找到的窗口的句柄
*/
HWND FindWindowEx(HWND lpParent, HWND lpChild, String lpClassName, String lpWindowName);
/**
获取桌面窗口,可以理解为所有窗口的root
@return 获取的窗口的句柄
*/
HWND GetDesktopWindow();
/**
发送事件消息
@param hWnd 控件的句柄
@param dwFlags 事件类型
@param bVk 虚拟按键码
@param dwExtraInfo 扩展信息,传0即可
@return
*/
int SendMessage(HWND hWnd, int dwFlags, byte bVk, int dwExtraInfo);
/**
发送事件消息
@param hWnd 控件的句柄
@param Msg 事件类型
@param wParam 传0即可
@param lParam 需要发送的消息,如果是点击操作传null
@return
*/
int SendMessage(HWND hWnd, int Msg, int wParam, String lParam);
/**
发送键盘事件
@param bVk 虚拟按键码
@param bScan 传 ((byte)0) 即可
@param dwFlags 键盘事件类型
@param dwExtraInfo 传0即可
*/
void keybd_event(byte bVk, byte bScan, int dwFlags, int dwExtraInfo);
/**
激活指定窗口(将鼠标焦点定位于指定窗口)
@param hWnd 需激活的窗口的句柄
@param fAltTab 是否将最小化窗口还原
*/
void SwitchToThisWindow(HWND hWnd, boolean fAltTab);
61.}
系统API映射好以后,利用这个接口写了如下的工具类,包含点击和输入各种操作。代码如下:
1.import java.util.concurrent.Callable; 2.import java.util.concurrent.ExecutorService; 3.import java.util.concurrent.Executors; 4.import java.util.concurrent.Future; 5.import java.util.concurrent.TimeUnit;
7.import com.sun.jna.Native;
8.import com.sun.jna.Pointer; 9.import com.sun.jna.platform.win32.WinDef.HWND; 10.import com.sun.jna.platform.win32.WinUser.WNDENUMPROC;
12./**
Window组件操作工具类
@author sunju
*/ 18.public class Win32Util {
private static final int N_MAX_COUNT = 512;
private Win32Util() {
}
/**
从桌面开始查找指定类名的组件,在超时的时间范围内,如果未找到任何匹配的组件则反复查找
@param className 组件的类名
@param timeout 超时时间
@param unit 超时时间的单位
@return 返回匹配的组件的句柄,如果匹配的组件大于一个,返回第一个查找的到的;如果未找到或超时则返回null
*/
public static HWND findHandleByClassName(String className, long timeout, TimeUnit unit) {
return findHandleByClassName(USER32EXT.GetDesktopWindow(), className, timeout, unit);
}
/**
从桌面开始查找指定类名的组件
@param className 组件的类名
@return 返回匹配的组件的句柄,如果匹配的组件大于一个,返回第一个查找的到的;如果未找到任何匹配则返回null
*/
public static HWND findHandleByClassName(String className) {
return findHandleByClassName(USER32EXT.GetDesktopWindow(), className);
}
/**
从指定位置开始查找指定类名的组件
@param root 查找组件的起始位置的组件的句柄,如果为null则从桌面开始查找
@param className 组件的类名
@param timeout 超时时间
@param unit 超时时间的单位
@return 返回匹配的组件的句柄,如果匹配的组件大于一个,返回第一个查找的到的;如果未找到或超时则返回null
*/
public static HWND findHandleByClassName(HWND root, String className, long timeout, TimeUnit unit) {
if(null == className || className.length() <= 0) {
return null;
}
long start = System.currentTimeMillis();
HWND hwnd = findHandleByClassName(root, className);
while(null == hwnd && (System.currentTimeMillis() - start < unit.toMillis(timeout))) {
hwnd = findHandleByClassName(root, className);
}
return hwnd;
}
/**
从指定位置开始查找指定类名的组件
@param root 查找组件的起始位置的组件的句柄,如果为null则从桌面开始查找
@param className 组件的类名
@return 返回匹配的组件的句柄,如果匹配的组件大于一个,返回第一个查找的到的;如果未找到任何匹配则返回null
*/
public static HWND findHandleByClassName(HWND root, String className) {
if(null == className || className.length() <= 0) {
return null;
}
HWND[] result = new HWND[1];
findHandle(result, root, className);
return result[0];
}
private static boolean findHandle(final HWND[] target, HWND root, final String className) {
if(null == root) {
root = USER32EXT.GetDesktopWindow();
}
return USER32EXT.EnumChildWindows(root, new WNDENUMPROC() {
@Override
public boolean callback(HWND hwnd, Pointer pointer) {
char[] winClass = new char[N_MAX_COUNT];
USER32EXT.GetClassName(hwnd, winClass, N_MAX_COUNT);
if(USER32EXT.IsWindowVisible(hwnd) && className.equals(Native.toString(winClass))) {
target[0] = hwnd;
return false;
} else {
return target[0] == null || findHandle(target, hwnd, className);
}
}
}, Pointer.NULL);
}
/**
模拟键盘按键事件,异步事件。使用win32 keybd_event,每次发送KEYEVENTF_KEYDOWN、KEYEVENTF_KEYUP两个事件。默认10秒超时
@param hwnd 被键盘操作的组件句柄
@param keyCombination 键盘的虚拟按键码(Virtual-Key Code),或者使用{@link java.awt.event.KeyEvent}
二维数组第一维中的一个元素为一次按键操作,包含组合操作,第二维中的一个元素为一个按键事件,即一个虚拟按键码
@return 键盘按键事件放入windows消息队列成功返回true,键盘按键事件放入windows消息队列失败或超时返回false
*/
public static boolean simulateKeyboardEvent(HWND hwnd, int[][] keyCombination) {
if(null == hwnd) {
return false;
}
USER32EXT.SwitchToThisWindow(hwnd, true);
USER32EXT.SetFocus(hwnd);
for(int[] keys : keyCombination) {
for(int i = 0; i < keys.length; i++) {
USER32EXT.keybd_event((byte) keys[i], (byte) 0, KEYEVENTF_KEYDOWN, 0); // key down
}
for(int i = keys.length - 1; i >= 0; i--) {
USER32EXT.keybd_event((byte) keys[i], (byte) 0, KEYEVENTF_KEYUP, 0); // key up
}
}
return true;
}
/**
模拟字符输入,同步事件。使用win32 SendMessage API发送WM_CHAR事件。默认10秒超时
@param hwnd 被输入字符的组件的句柄
@param content 输入的内容。字符串会被转换成char[]后逐个字符输入
@return 字符输入事件发送成功返回true,字符输入事件发送失败或超时返回false
*/
public static boolean simulateCharInput(final HWND hwnd, final String content) {
if(null == hwnd) {
return false;
}
try {
return execute(new Callable() {
@Override
public Boolean call() throws Exception {
USER32EXT.SwitchToThisWindow(hwnd, true);
USER32EXT.SetFocus(hwnd);
for(char c : content.toCharArray()) {
Thread.sleep(5);
USER32EXT.SendMessage(hwnd, WM_CHAR, (byte) c, 0);
}
return true;
}
});
} catch(Exception e) {
return false;
}
}
public static boolean simulateCharInput(final HWND hwnd, final String content, final long sleepMillisPreCharInput) {
if(null == hwnd) {
return false;
}
try {
return execute(new Callable() {
@Override
public Boolean call() throws Exception {
USER32EXT.SwitchToThisWindow(hwnd, true);
USER32EXT.SetFocus(hwnd);
for(char c : content.toCharArray()) {
Thread.sleep(sleepMillisPreCharInput);
USER32EXT.SendMessage(hwnd, WM_CHAR, (byte) c, 0);
}
return true;
}
});
} catch(Exception e) {
return false;
}
}
/**
模拟文本输入,同步事件。使用win32 SendMessage API发送WM_SETTEXT事件。默认10秒超时
@param hwnd 被输入文本的组件的句柄
@param content 输入的文本内容
@return 文本输入事件发送成功返回true,文本输入事件发送失败或超时返回false
*/
public static boolean simulateTextInput(final HWND hwnd, final String content) {
if(null == hwnd) {
return false;
}
try {
return execute(new Callable() {
@Override
public Boolean call() throws Exception {
USER32EXT.SwitchToThisWindow(hwnd, true);
USER32EXT.SetFocus(hwnd);
USER32EXT.SendMessage(hwnd, WM_SETTEXT, 0, content);
return true;
}
});
} catch(Exception e) {
return false;
}
}
/**
模拟鼠标点击,同步事件。使用win32 SendMessage API发送BM_CLICK事件。默认10秒超时
@param hwnd 被点击的组件的句柄
@return 点击事件发送成功返回true,点击事件发送失败或超时返回false
*/
public static boolean simulateClick(final HWND hwnd) {
if(null == hwnd) {
return false;
}
try {
return execute(new Callable() {
@Override
public Boolean call() throws Exception {
USER32EXT.SwitchToThisWindow(hwnd, true);
USER32EXT.SendMessage(hwnd, BM_CLICK, 0, null);
return true;
}
});
} catch(Exception e) {
return false;
}
}
private static T execute(Callable callable) throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
Future task = executor.submit(callable);
return task.get(10, TimeUnit.SECONDS);
} finally {
executor.shutdown();
}
} 240.}
其中用到的各种事件类型定义如下:
1.public class Win32MessageConstants {
public static final int WM_SETTEXT = 0x000C; //输入文本
public static final int WM_CHAR = 0x0102; //输入字符
public static final int BM_CLICK = 0xF5; //点击事件,即按下和抬起两个动作
public static final int KEYEVENTF_KEYUP = 0x0002; //键盘按键抬起
public static final int KEYEVENTF_KEYDOWN = 0x0; //键盘按键按下
13.}
下面写一段测试代码来测试支付宝密码安全控件的输入,测试代码如下:
1.import java.util.concurrent.TimeUnit;
3.import static org.hamcrest.core.Is.is;
4.import static org.junit.Assert.assertThat;
6.import static org.hamcrest.core.IsNull.notNullValue;
7.import org.junit.Test;
9.import com.sun.jna.platform.win32.WinDef;
10.import com.sun.jna.platform.win32.WinDef.HWND;
12.public class AlipayPasswordInputTest {
@Test
public void testAlipayPasswordInput() {
String password = "your password";
HWND alipayEdit = findHandle("Chrome_RenderWidgetHostHWND", "Edit"); //Chrome浏览器,使用Spy++可以抓取句柄的参数
assertThat("获取支付宝密码控件失败。", alipayEdit, notNullValue());
boolean isSuccess = Win32Util.simulateCharInput(alipayEdit, password);
assertThat("输入支付宝密码["+ password +"]失败。", isSuccess, is(true));
}
private WinDef.HWND findHandle(String browserClassName, String alieditClassName) {
WinDef.HWND browser = Win32Util.findHandleByClassName(browserClassName, 10, TimeUnit.SECONDS);
return Win32Util.findHandleByClassName(browser, alieditClassName, 10, TimeUnit.SECONDS);
} 27.}
测试一下,看看是不是输入成功了!
最后说下这个方法的缺陷,任何方法都有不可避免的存在一些问题,完美的事情很少。
1、sendMessage和postMessage有很多重载的函数,不是每种都有效,从上面的Win32Util中就能看出,实现了很多个方法,需要尝试下,成本略高;
2、输入时需要注意频率,输入太快可能导致浏览器中安全控件崩溃,支付宝的安全控件在Firefox下输入太快就会崩溃;
3、因为是系统API,所以MAC、UNIX、WINDOWS下都不同,如果只是在windows环境下运行,可以忽略;
4、从测试代码可以看到,是针对Chrome浏览器的,因为每种浏览器的窗口句柄不同,所以要区分,不过这个相对简单,只是名称不同;
5、如果你使用Selenium的RemoteDriver,并且是在远程机器上运行脚本,这个方法会失效。因为remoteDriver最终是http操作,对操作系统API的操作是客户端行为,不能被翻译成Http Command,所以会失效。
最新内容请见作者的GitHub页:http://qaseven.github.io/