都9102年了,还要用弄Win32 API--记某IC卡读卡器使用Win32 API模拟操作开发心得
都云时代了,前段时间,因为公司突然来了一大批IC卡读卡器要制作,而且只有1个星期时间进行制作,某部门的苦苦哀求下答应帮他们做一个IC卡读卡器的辅助工具。
这个读卡器原厂的附带的制作工具,操作步骤居然有12步(寒)!并且既有WinXP的exe端(只能WinXP,不支持Win7……),又有Web端。其实难点在于WinXP的exe端,要模拟菜单点击、键盘输入、鼠标点击。而Web端嘛,再不行就直接用python bs4嘛(不过最后也用C#成功弄了爬虫)。记录一下整个开发调试过程。
首先理解一个概念:句柄
句柄是Windows用来标志应用程序中建立的或是使用的唯一整数(相当于唯一的ID),Windows大量使用了句柄来标识对象,即一个4字节(64位程序中为8字节)长的数值,来标识应用程序中的不同对象和同类中的不同的实例,诸如,一个窗口,按钮,图标,滚动条,输出设备,控件或者文件等。应用程序能够通过句柄访问相应的对象的信息,但是句柄不是指针,程序不能利用句柄来直接阅读文件中的信息。
获取句柄通常采用2个Win32 API函数:
FindWindow(
lpClassName, {窗口的类名}
lpWindowName: PChar {窗口的标题}
): HWND; {返回窗口的句柄; 失败返回 0}
//FindWindowEx 比 FindWindow 多出两个句柄参数:
FindWindowEx(
Parent: HWND; {要查找子窗口的父窗口句柄}
Child: HWND; {子窗口句柄}
ClassName: PChar; {}
WindowName: PChar {}
): HWND;
{
如果 Parent 是 0, 则函数以桌面窗口为父窗口, 查找桌面窗口的所有子窗口;
如果 是 HWND_MESSAGE, 函数仅查找所有消息窗口;
子窗口必须是 Parent 窗口的直接子窗口;
如果 Child 是 0, 查找从 Parent 的第一个子窗口开始;
如果 Parent 和 Child 同时是 0, 则函数查找所有的顶层窗口及消息窗口.
}
1.查找主窗口的句柄:
IntPtr mainHandle = FindWindow(null, "UKeyManage"); if (mainHandle != IntPtr.Zero) { 下续对菜单进行点击操作 }
2.对菜单进行点击操作
IntPtr menu = GetMenu(mainHandle); Console.WriteLine("menu" + menu); int subMenuCount = 0; for (int s = 0; s < 65535; s++) { IntPtr subMenu = GetSubMenu(menu, s); if (subMenu != IntPtr.Zero) { subMenuCount++; //用户证书在第2个菜单(证书管理)下 if (subMenuCount < 2) { continue; } Console.WriteLine("subMenu" + subMenu); int itemCount = 0; for (int i = 0; i < 65535; i++) { IntPtr item = GetMenuItemID(subMenu, i); if (item != IntPtr.Zero && (int)item != -1) { itemCount++; //证书管理下面的第2个就是导出用户证书 if (itemCount == 2) { //导出用户证书 Console.WriteLine("item" + item); //发送点击菜单命令 PostMessage(mainHandle, WM_COMMAND, item, 0); break; } } } break; } }
3.模拟键盘输入操作
//弹出来的对话框先取窗口句柄 IntPtr initWinHandle = FindWindow(null, "UsrPinDlg"); Console.WriteLine("UsrPinDlg" + initWinHandle); //先设置最前端 SetForegroundWindow(initWinHandle); Thread.Sleep(500); SendKeys.SendWait("********");//密码 Thread.Sleep(500); //查找Button的句柄 IntPtr initOkHandle = FindWindowEx(initWinHandle, 0, "Button", null); Console.WriteLine("b:" + initOkHandle); //点击Button SendMessage(initOkHandle, BM_CLICK, IntPtr.Zero, IntPtr.Zero);
4.获取ListBox的信息
//回到主界面,查看ListBox的消息 IntPtr mainHandle2 = FindWindow(null, "UKeyManage"); Console.WriteLine("mainHandle2" + mainHandle2); IntPtr listBoxHandle = FindWindowEx(mainHandle2, 0, "ListBox", null); Console.WriteLine("listBoxHandle" + listBoxHandle); IntPtr lbHandle = SendMessage(listBoxHandle, LB_GETCOUNT, IntPtr.Zero, IntPtr.Zero); Console.WriteLine("lbHandle" + lbHandle); List<string> lbContent = new List<string>(); StringBuilder sb = new StringBuilder(); //逐行获取 for (int lb = 0; lb < (int)lbHandle; lb++) { StringBuilder sblb = new StringBuilder(); IntPtr getText = SendMessage(listBoxHandle, LB_GETTEXT, lb, sblb); sb.Append(sblb); } Console.WriteLine("lbText:" + sb.ToString()); if (sb.ToString().Contains("成功")) { return true; } else { return false; }
其它菜单操作类似,在此不进行阐述。
接下来是Web端
1.浏览到主页
webBrowser1.Navigate("https://xxxxxxxxx/xxxx");
2.通过HtmlElementCollection解释并模拟点击
//查找,在input下的j_id_id23:j_id_id381 HtmlElementCollection link = this.webBrowser1.Document.GetElementsByTagName("input").GetElementsByName("j_id_id23:j_id_id381"); for (int i = 0; i < link.Count; i++) { Console.WriteLine(i + link[i].Name); //点击 link[i].InvokeMember("click"); break; }
技术难点都搞定了,就弄出第1版成品。
由于第1版只有2天的开发时间,其实后续可以利用DevCon对设备进行禁用/启用,这样可以一次性插好几个读卡器,然后自动实现批量(将全部禁用,做那一个时就开那个)。
Win32API初尝到此结束。其实AI的实际产品经常要与硬件打交道(像摄像头、语音输入之类),作为一位主管不仅仅是懂得算法、调参,还得懂得整个产品涉及的技术进行把控,关键时刻自己要出手。
- 点赞
- 收藏
- 关注作者
评论(0)