wpf C# 操作DirectUI窗口 SendMessage+MSAA

原文:wpf C# 操作DirectUI窗口 SendMessage+MSAA

最近做一个抓取qq用户资料的工具,需要获取qq窗口上的消息,以前这种任务是用句柄获取窗口中的信息,现在qq的窗口用的是DirectUI,只有窗口句柄,没有控件句柄,句柄这条路走不通了。不过较新版的qq的部分控件实现了微软的IAccessible接口(称为Microsoft
Active Accessibility技术,简称MSAA),可以用另一套函数获取qq窗口的信息。不过要对窗口进行输入还是要靠句柄,上面说过,DirectUI的窗口只有一个句柄,因此模拟输入的时候不需要查找到具体的控件句柄,但要注意获取控件焦点,可能相对传统WinForm的窗口要简单点。

????????????

先介绍下和句柄操作相关的函数:

using?System.Runtime.InteropServices;

? ? ? ? [DllImport("user32.dll", SetLastError =true)]

????????static?extern?IntPtr?FindWindow(string?lpClassName,string?lpWindowName);

根据类名和窗口标题查找句柄

?

????????[DllImport("user32.dll", SetLastError =true)]

????????public?static?extern?IntPtr?FindWindowEx(IntPtr?parentHandle,IntPtr?childAfter,
string?className,
string?windowTitle);

根据父句柄,前一个句柄,类名和窗口标题查找句柄,这几个信息可以通过VS自带的spy++查询。

?

????????[DllImport("user32.dll", EntryPoint ="GetDesktopWindow", CharSet =
CharSet.Auto, SetLastError =
true)]

????????static?extern?IntPtr?GetDesktopWindow();

返回桌面窗口句柄,被我用来当前一个函数的父句柄。

?

????????[DllImport("user32.dll")]

????????public?extern?static?int?GetWindowText(IntPtr?hWnd,StringBuilder?lpString,
int?nMaxCount);

获取指定窗口的标题,WinForm里的控件都是window,但在我们讨论的情况下window就只是窗口了。需要结合StringBuider类使用,不熟悉的可以去预习下。

????????[DllImport("User32.dll")]

????????public?static?extern?int?GetClassName(int?hWnd,StringBuilder?lpClassName,
int?nMaxCount);

获取指定窗口的类名,也是用到StringBuider类。

????????[DllImport("USER32.DLL")]

????????public?static?extern?bool?SetForegroundWindow(IntPtr?hWnd);

将一个窗口显示到最前端。

?

????????[DllImport("user32.dll", CharSet =CharSet.Auto, ExactSpelling =
true)]

????????public static extern?IntPtr?GetForegroundWindow();

返回最前端窗口的句柄。

?

????????[DllImport("user32.dll", EntryPoint ="SendMessage")]

????????static?extern?int?SendMessage(IntPtr?hWnd,uint?Msg,
int?wParam,
int?lParam);

模拟键盘或鼠标的输入,最常用的就是它了,后面具体介绍。

?

????????[DllImport("user32.dll", EntryPoint ="PostMessage")]

????????public?static?extern?IntPtr?PostMessage(IntPtr?hwnd,uint?msg,int?wparam,
int?lparam);

作用和上一个类似,不过SendMessage是等窗口处理完事件后返回,这个是发送消息后立即返回。顺便一提,原型本来是LRESULT SendMessage( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);类型改了一下,并不影响功能,只是方便写,其它函数也类似。

?

????????[DllImport("user32.dll")]

????????[return:
MarshalAs(UnmanagedType.Bool)]

????????static?extern?bool?GetWindowRect(IntPtr?hWnd,ref?RECT?lpRect);

返回一个表示指定窗口位置大小的结构,结构声明如下:

????????[StructLayout(LayoutKind.Sequential)]

????????public?struct?RECT

????????{

????????????public?int?Left;//最左坐标

????????????public?int?Top;//最上坐标

????????????public?int?Right;//最右坐标

????????????public?int?Bottom;//最下坐标

????????}

这里要说一下DirectUI的窗口一般都是用微软的api建立一个窗口,然后隐藏窗口,自己画一个窗口出来,所以窗口的实际大小要比看到的要大,拖动窗口时看到的那个虚线框才是真实的大小。

?

接下来是MSAA的相关函数:需要引用Accessibility,并using?Accessibility;

????????[DllImport("Oleacc.dll")]

????????public?static?extern?int?AccessibleObjectFromWindow(

????????IntPtr?hwnd,

????????int?dwObjectID,

????????ref?Guid?refID,

????????ref?IAccessible?ppvObject);

通过句柄获取窗口对于的IAccessible对象,知道我为什么要先介绍句柄相关的函数了吧。用的时候需要包装一下:

????????private?void?GetAccessibleWindow(System.IntPtr?imWindowHwnd,out?IAccessible?IACurrent)

????????{

????????????Guid?guidCOM =
new?Guid(0x618736E0, 0x3C3D, 0x11CF, 0x81, 0xC, 0x0, 0xAA, 0x0, 0x38, 0x9B, 0x71);

????????????AccessibleObjectFromWindow(imWindowHwnd, -4,
ref?guidCOM, ref?IACurrent); ????

????????}

?

????????[DllImport("Oleacc.dll")]

????????public?static?extern?int?AccessibleChildren(

????????Accessibility.IAccessible?paccContainer,

????????int?iChildStart,

????????int?cChildren,

????????[Out]
object[] rgvarChildren,

????????out?int?pcObtained);

获取IAccessible对象的子对象数组,一般包装成下面的形式:

????????private?IAccessible[] GetAccessibleChildren(IAccessible?paccContainer)

????????{

????????????IAccessible[] rgvarChildren =new?IAccessible[paccContainer.accChildCount];

????????????int?pcObtained;

????????????AccessibleChildren(paccContainer, 0, paccContainer.accChildCount, rgvarChildren,out?pcObtained);

????????????return?rgvarChildren;

????????}

????????

????????public?IAccessible?GetAccessibleChild(IAccessible?paccContainer,int[] array)

????????{

????????????if?(!array.Length.Equals(0))

????????????{

????????????????IAccessible[] children=GetAccessibleChildren(paccContainer);

????????????????IAccessible?result = children[array[0]];

????????????????if?(result.accChildCount == 0)

????????????????{

????????????????????lb.Text += "error: parent:"?+ ((IAccessible)result.accParent).accChildCount +" role:"?+ result.accRole +
" state:"?+ result.accState;

????????????????????return?null;

????????????????}

????????????????int[] array_1 =
new?int[array.Length - 1];

????????????????for?(int?i = 0; i < array.Length - 1; i++)

????????????????{

????????????????????array_1[i] = array[i + 1];

????????????????}

????????????????return?GetAccessibleChild(result, array_1);

????????????}

????????????else

????????????{

????????????????return?paccContainer;

????????????}

????????}

按层级找到某个对象,array代表需要查找的层级,就是调用了前面的函数。

?

介绍几个IAccessible类的属性和函数:

accessible.accChildCount ? ? 子控件数

accessible.accValue; accessible.accName ? ?控件数值和名字这两个属性vs提示是有的,不过不知为何运行时会报错,要换成下面两个函数才行,还有其它几个属性应该也是这样。

result.get_accValue(0);result.get_accName(0) ? ?VS对参数的提示是[object VarChild =Type.Missing],然而填Type.Missing报错,这参数好像表示子空间的序号,0表示查询本控件,嗯,填0就好。

Inspect.exe和AccExplorer32.exe都可以查询窗口的IAccessible层级结构和IAccessible控件的具体信息,第一个功能比较多,后一个有汉化版,大家自己取舍。

再介绍详细介绍一下SendMessage的用法。static?extern?int?SendMessage(IntPtr?hWnd,uint?Msg,
int?wParam,
int?lParam);

Msg 代表操作类型,很好查的,常用的有:

????????//按下按钮

????????const?int?WM_KEYDOWN = 0x100;

????????//放开按钮

????????const?int?WM_KEYUP = 0x101;

????????//发送字符

????????const?int?WM_CHAR = 0x102;

????????//应用程序发送此消息来设置一个窗口的文本 ??

????????const?int?WM_SETTEXT = 0x0C;

????????//当一个窗口或应用程序要关闭时发送一个信号 ??

????????const?int?WM_CLOSE = 0x10;

????????//当用户选择结束对话框或程序自己调用ExitWindows函数 ??

????????const?int?WM_QUERYENDSESSION = 0x11;

????????//用来结束程序运行,会关闭窗口所属的整个程序

????????const?int?WM_QUIT = 0x12;

????????//按下鼠标左键 ??

????????const?int?WM_LBUTTONDOWN = 0x201;

????????//释放鼠标左键 ??

????????const?int?WM_LBUTTONUP = 0x202;

????????//双击鼠标左键 ??

????????const?int?WM_LBUTTONDBLCLK = 0x203;

????????//使用鼠标滚轮

????????const?int?WM_MOUSEWHEEL = 0x020A;

wParam和lParam是32位数。

按钮事件,wParam代表键值,具体可以查;lParam代表点击的点击次数、组合键等信息,msdn上有张表介绍各个位的作用,不过我没用过。

发送字符,每次发送一个char,wParam代表char的值转换成int类型就行,lParam为0。

对于鼠标点击事件,wParam代表组合键,没有的话为0;lParam代表点击的位置,低字为x坐标,高字为y坐标,即x+(y<<16),这个坐标是相对于屏幕而言的。

对于鼠标滚轮事件,wParam高字代表滚动距离,向上为正,向下为负,低字代表组合键,没有的话为0;lParam代表点击的位置,低字为x坐标,高字为y坐标,即x+(y<<16),这个坐标是相对于窗口而言的。

由于只有窗口句柄,使用滚轮事件,发送字符和按钮事件时,需要获取相应区域的焦点,我是用鼠标点击事件做的。

向DirectUI发送文本这件事困扰了我好久,试过SetText事件,没有作用,只能将文本拆成char数组发送字符。但发送中文就会乱码,SendMessage发送中文用的是GBK编码,String是Unicode编码,需要进行转换,相应的函数是有的,要先把String转换成Byte数组再转换成Char数组,解码器有好几种,试了好久发现下面这种组合是可以的。

using?System.Text;

char[] charsC =
UnicodeEncoding.Unicode.GetChars(UnicodeEncoding.GetEncoding("GBK").GetBytes(strText));

高兴了好久发现,这样字母和数字乱码了。所以把正常的Unicode字符放入另一个数组,两个混着用。

char[] chars = strText.ToCharArray();

英文字符是用八位表示的,中文字符用16位,c#里char是16位的,在charsC里,相邻2个英文字符被放到了一个char里。所以,两个数组的长度是不一样的。GBK编码的中文字符从0x8140到0xfea0,键盘上的英文字符最大为0x7e,用0x7e7e作为分界区分中文和英文字符。啊,这样中英文都能发了。

不过后来的测试中发现有些汉字这样发送也会乱码,于是我决定自己把Byte数组转换成Char数组,以0x7e为界,大于的就属于中文字符,和下一个字节一切放到一个Char里,否则就是英文字符,直接转换成Char类型。

最后我在测试的时候发现直接把Byte数组发过去就行了。。以下是最终的函数:

private?void?sendText(IntPtr?hwnd,String?strText)

{

????byte[] charCByte =
UnicodeEncoding.GetEncoding("GBK").GetBytes(strText);

????for?(int?i = 0; i < charCByte.Length; i++)

????{

????????SendMessage(hwnd, WM_CHAR, (int)charCByte[i], 0);

????}

}

本文由本人查询资料整理加工而得,既为方便自己查阅,也为方便他人搜索。水平有限,如有错漏,希望大神能指点,既是帮我,也是帮了其他看贴的同志。

原文地址:https://www.cnblogs.com/lonelyxmas/p/9473592.html

时间: 2024-08-29 10:09:18

wpf C# 操作DirectUI窗口 SendMessage+MSAA的相关文章

【.net 深呼吸】WPF 中的父子窗口

与 WinForm 不同,WPF 并没有 MDI 窗口,但 WPF 的窗口之间是可以存在“父子”关系的. 我们会发现,Window 类公开了一个属性叫 Owner,这个属性是可读可写的,从名字上我们也能猜到,应该是用来设置窗口的父子关系的.这个属性看起来不难用,只要赋个窗口实例即可,而真正的难点是你得搞清楚“谁是谁的父窗口”,“谁是谁的子窗口”,一旦你搞混了,有可能会应用程序带来一些小麻烦. 这个问题是一位妹子问我的,不然怎么说女孩子特别细心呢,这个小玩意儿估计很多时候我们都不会注意到. 下面,

selenium操作win窗口

最近测试的项目中涉及到上传文件的功能,自动化脚本中需要使用python win32模块来操控,记录下使用心得吧! 1.首先引入python win32gui模块 import win32gui 2.安装spyxx.exe,用于定位win窗口 准备工作做好了,开工吧 3.点击类似“上传”等按钮后会弹出win窗口,那就先定位它吧,如下 定位代码如下: uploadwindowname = u'打开' #CHROME窗口名称是打开 uploadwindow = win32gui.FindWindow(

如何监视 WPF 中的所有窗口,在所有窗口中订阅事件或者附加 UI

原文:如何监视 WPF 中的所有窗口,在所有窗口中订阅事件或者附加 UI 由于 WPF 路由事件(主要是隧道和冒泡)的存在,我们很容易能够通过只监听窗口中的某些事件使得整个窗口中所有控件发生的事件都被监听到.然而,如果我们希望监听的是整个应用程序中所有的事件呢?路由事件的路由可并不会跨越窗口边界呀? 本文将介绍我编写的应用程序窗口监视器,来监听整个应用程序中所有窗口中的路由事件.这样的方法可以用来无时无刻监视 WPF 程序的各种状态. 其实问题依旧摆在那里,因为我们依然无法让路由事件跨越窗口边界

初识句柄操作(控制台窗口小实验)

今日学习了控制台使用句柄操作的方法. 我们都知道,使用iostream也可以向屏幕中输出语句. 但它们只能实现基本的输入输出 操作,对于控制台窗口界面的控制却无能为力,而且不能与stdio.h和conio.h友好相处,因为iostream和它们是C++两套不同的输入. 因此,我们需要句柄类来帮助我们完成这个操作. 下面直接上练习小代码,为贪食蛇清屏的小片段. 经一番查找,习得基本用法. 1 void clrscr(void) { 2 //控制台窗口信息类型 存有缓冲区大小 当前光标位置 窗口显示

window.open()方法用于子窗口数据回调至父窗口,即子窗口操作父窗口

window.open()方法用于子窗口数据回调至父窗口,即子窗口操作父窗口 项目中经常遇到一个业务逻辑:在A窗口中打开B窗口,在B窗口中操作完以后关闭B窗口,同时自动刷新A窗口(或局部更新A窗口)(或将数据传回A窗口) 以下是从实际项目中截取出来和window.open()方法相关的代码,业务逻辑如下: 1. 点击父窗口的div标签(id="addMatchSchedule"),出发点击事件,打开子窗口: 2. 点击子窗口的button按钮,触发点击时间,即调用addSchduleI

js子窗口操作父窗口的标签

======================================父窗体 <input id="aaaa" type="button"/> function upfile()         {                         resultValue = window.showModelessDialog("ceshi.aspx?file=DownFile", window, "dialogWidt

C# WPF 让你的窗口始终钉在桌面上

原文:C# WPF 让你的窗口始终钉在桌面上 IntPtr hWnd = new WindowInteropHelper(Application.Current.MainWindow).Handle; IntPtr hWndProgMan = FindWindow("Progman", "Program Manager"); SetParent(hWnd, hWndProgMan); [DllImport("user32.dll", SetLas

C# 在winform或者wpf中显示控制台窗口

这儿需要使用两个系统函数: BOOL WINAPI FreeConsole(void); //// 关闭控制台窗口,参考:http://msdn.microsoft.com/en-us/library/ms683150%28VS.85%29.aspx BOOL WINAPI AllocConsole(void); //// 打开控制台窗口,参考:http://msdn.microsoft.com/en-us/library/ms681944%28VS.85%29.aspx对应DLL:Kernel

JS打开新窗口,子窗口操作父窗口

<!--父窗口弹窗代码开始--> <script type="text/javascript"> function OpenWindow() { window.open('WebForm1.aspx', '_blank', 'width=400,height=100,menubar=no,toolbar=no,location=no,directories=no,status=no,scrollbars=yes,resizable=yes'); } functi