不是所有程序都可以通过系统来获取其句柄,进而拿到程序进程的数据,跨进程的通信其实很复杂,win32提供的api可以访问的窗口组件,以及可以获取的窗口组件属性有限,其实很多时候不能满足全部需求
第一次上手这种项目,大概知道是获取windows窗体的句柄,大概的方向是使用C++或者C#(虽然我这几年基本都全用python,C系列的忘得差不多,但是该捡起来还是要捡起来,哈哈哈)
win7下,比较简单:
任务管理器->选择 进程tab->菜单栏的查看->选择列->句柄数复选框选择,然后在进程tab页面就可以看到多了一列 句柄数 的显示。
win10下,比win7隐藏的深一点点
任务管理器->选择 详细信息tab->点击 名称/状态/用户名 这一个表头 右击->选择列->选择 句柄数复选框 OK 就可以在 详细信息tab中看到某个程序的句柄数了(注意,一个程序的句柄数其实是会变的,和该程序当前窗口中控件的个数有关,详见下面句柄的概念)
此外,还发现win10的任务管理器其实更加人性化了,进程tab中可以右击名称这一表头,然后可以选择想看的东西,像PID,命令行,进程名称这种都可以看得到,就很好
还有些注意事项
参考:
在搜索相关代码(如:如何获取窗口的句柄时),经常看到这三种东西,所以搜了下,不喜欢太糊里糊涂的。
搜索过程中发现其实很多人都分不清概念,但是不影响使用和完成任务,虽然我也不求甚解,但是还是不喜欢那么那么一无所知,而且这种专业术语乱用也容易给后来者造成困扰吧
API:Application Programming Interface。Windows操作系统提供给应用程序编程的接口, 简称 为API函数。所有主要的Windows函数都在Windows.h
头文件(这个文件好像需要安装一些开发包才有,暂时看不到在哪,有兴趣可以自己找找)中进行了声明。使用windows API创建的能在windows上运行的程序统称为windows程序。 Win32是一个子系统。Win32是Windows的一个子系统,还有另外的子系统如OS/2、POSIX、WOW等。不同的子系统系统提供了不同的编程接口,即API,一般说的API指的就是Win32 API.
以 WOW为例(经常可以在系统中看到的这个东西):百度百科上的解释:WOW64 (Windows-on-Windows 64-bit)是一个Windows操作系统的子系统, 它为现有的 32 位应用程序提供了 32 位的模拟,可以使大多数 32 位应用程序在无需修改的情况下运行在 Windows 64 位版本上。
此外,Win32程序用得最多的还是Win32子系统的DLL们,最核心的DLL包括:kernel32.dll、User32.dll、Gdi32.dll、Advapi32.dll。这些DLL包装了ntdll.dll的native API。其中Gdi32.dll比较特殊,它与核心态的win32k.sys直接保持联系,以提高NT系统的图形处理能力。Win32子系统的DLL们提供的接口函数在MSDN文档中被详细介绍,它们就是Win32 API。
MSDN(Microsoft Developer Network 微软开发者网络):官方-Microsoft Docs,大概界面如下。(由于API越来越多,为了更好帮助开发者,微软向开发人员提供了MSDN,其实一套帮助系统,包含大量的开发文档、技术文章和示例代码,对于初学者来说,学会使用MSDN并从中汲取知识,是必须要掌握的技能)
Win32 SDK:SDK(Software Development Kit)中文是软件开发包。则Win32 SDK是Windows 32位平台下的软件开发包,包括了API函数、帮助文档、微软 提供的一些辅助开发工具。SDK实际上就是开发所需资源的一个集合。(能更方便我们的开发)
提示:API和SDK是一种广泛使用的专业术语,并没有专指某一种特定的API和SDK,例如,语音卡API、语音卡SDK、Java API、Java SDK等。自己公开的DLL函数也可以叫API!!!
MFC:Microsoft Foundation Classes微软基础类。用于在C++环境下编写应用程序的一个框架和引擎。(也可以说,MFC是Win API与C++的结合后的再一次封装)。MFC是MS对API的一个封装,也就是一个C++类库,当然MFC比一般类库庞大,所以有人称之为应用程序框架。但其本质还是一个类库
提示: SDK是基于C语言的,而MFC是基于C++的,这是最根本的区别。MFC主要封装的是界面、文件、WinInet和线程等函数。MFC除了封装API,最重要的是它的体系结构,它所使用的Doc/View结构是SDK中没有的,这种架构是比较特殊的。MFC是微软的基本类库,对很多东西已经进行了封装,因此使用起来简单、方便。SDK是采用较一般的C语言,但很灵活。一般编写简单的程序,使用MFC应该能达到要求。但如果编写功能强大的程序,则使用SDK较多,尤其是底层的开发。
PS:根据网上搜索到的资料,windows操作系统的内核是c写的,外围的一些内容是c++写的,所以windows API应该是使用C语言和汇编语言写的。SDK + C 完全可以进行所有的windows程序开发,也可以采用MFC + C++,当然,你要用SDK + C++ 也是你的自由,但MFC + C不可能,因为MFC是C++写的,C不支持类,所以三者的关系如下图(自己随便画的,仅供示意)
简单说,API是接口,SDK是包含API声明的开发包,MFC是封装API的类库.
参考:
如果选择Windows API和SDK开发,就要用C语言,没有对象和类。。。甚是头疼,我觉得我不行,so我还是选择MFC进行开发这种方式来搞比较好
如果有想看SDK和API的,可以参考:
二者直观的代码区别及总结性比较(API/SDK vs MFC),参考
这里可以看到的一个概念是,单纯的句柄概念,其实只出现在Windows API/SDK编程中,MFC里其实使用的就是对象指针。(百度发现: MFC中有大量的句柄包装类。所谓句柄包装类,指的是这些类是封装了系统对象的句柄,并提供了一组成员函数作为访问系统对象的接口。)
目测MFC在网上的资料最多,也是使用最广泛的,除了参考链接之外,用的最多的应该还是MSDN上关于MFC的使用介绍,如果最开始搜到的是英文,把连接中的en-us改成zh-cn就好了(有时报错,没有这个页面/有概率是机器翻译,不是人编辑的)。
这个工具很方便,可以用来查看程序的句柄,窗口,进程,线程等,微软Visual studio的文档说明中给的很清楚,
这里考虑到之后编译后的exe程序要运行在不同系统类型的电脑上(32位/64位 win10/win7),所以经过同事大佬的建议,最好是使用C++/C# 在VScode编译中选择,得到不同系统类型的发布版本。
参考:
两个方向:python来写(我比较熟悉,但是编译成exe之后可能会出现问题,尤其涉及到句柄的使用),C++来写(最优选择,性能高,适配方便)
关于C++,如果也是像我一样四五年没再用过,建议去看我另一篇文章(待填坑)稍微上手一下。
搜了一波,做应该也可以做,只是可能在后续遇到问题时,由于不明白更准确的术语描述,可能后续解决问题会有一定的障碍
参考:
整合上述资料之后得到的样例代码demo:
import win32gui
import win32api
classname = "MozillaWindowClass"
titlename = "百度一下,你就知道 - Mozilla Firefox"
#获取句柄
hwnd = win32gui.FindWindow(classname, titlename)
# 很奇怪 官方文档这里 已经没有这个FindWindow了 全是后面有后缀的 FindWindowA FindWindowExA等等
# 如果没有类名 可以改成 None
# 直接 print(hwnd) 会得到一串数字(以16进制形式打印 可以看到和spy++中显示的句柄号一致)
print('%#x' % hwnd )
# 如果窗口无标题栏或文本,或标题栏为空,或窗口或控制的句柄无效,则返回值为零。函数不能返回在其他应用程序中的编辑控件的文本
print(win32gui.GetWindowText(hwnd))
# 获取某个窗口的类名
print(win32gui.GetClassName(hwnd))
#获取窗口左上角和右下角坐标
left, top, right, bottom = win32gui.GetWindowRect(hwnd)
# 获取当前大窗体中某个已知类名 不知窗体名的组件/子窗体
child_class='AfxWnd40'
child_handle = win32gui.FindWindowEx(handle, None, child_class, None)
print('%#x' %child_handle)
# 但是FindWindowEx只会返回第一个满足条件的子窗体句柄
# 遍历输出当前父窗体下所有子窗体的句柄
child_class_list = []
win32gui.EnumChildWindows(handle, lambda handle,param:param.append(handle), child_class_list)
print(child_class_list)
# 使用GetClassName查看是否有符合条件的句柄
class_set=[]
for hwnd in child_class_list:
hwnd_class=win32gui.GetClassName(hwnd)
class_set.append(hwnd_class)
print(list(set(class_set)))
# 获得想要的子窗体句柄后,主要是MSFlexGridWndClass这个窗体,就可以对其进行操作
# 首先 要根据句柄获取这个对象 然后才可以对其进行调用
参考:
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, 则函数查找所有的顶层窗口及消息窗口.
}
鉴于FindWindowEx()函数只能返回第一个满足条件的句柄,无法满足我的需求,so决定看看其他函数
BOOL EnumChildWindows(
HWND hWndParent,
WNDENUMPROC lpEnumFunc,
LPARAM lParam
);
参数说明:
+ hWndParent Type: HWND
要进行遍历的子窗口的父窗口的句柄,如果父窗口的句柄hWndParent 为None,则这个函数就等价于EnumWindows()
+ lpEnumFunc 指向应用程序定义的回调函数的指针。 有关更多信息,请参见EnumChildProc。
+ lParam Type: LPARAM
应用程序定义的值,将传递给回调函数。
+ 如果子窗口创建了自己的子窗口,则EnumChildWindows也会枚举这些窗口。在枚举过程中以Z顺序移动或重新定位的子窗口将被正确枚举。 该函数不会枚举在枚举之前销毁的子窗口或在枚举过程中创建的子窗口。
len = win32gui.SendMessage(form_handle, win32con.WM_GETTEXTLENGTH, 0, 0) + 1000 # 获取控件中内容的文本长度 这里有个问题
# SendMessage函数用于獲取不含截尾空位元組的文字長度的長度
# 返回值:返回複製字元的數量,不包括截尾的空位元組(这里有一种 截尾的空位元素)
# 头文件:winuser.h;输入库:user32.lib;Unicode:在Windows NT环境下以Unicode和ANSI方式实现
buffer = win32gui.PyMakeBuffer(len)
win32gui.SendMessage(form_handle, win32con.WM_GETTEXT, len, buffer)
print(buffer)
address, length = win32gui.PyGetBufferAddressAndLen(buffer)
text = win32gui.PyGetString(address, length)
print('获取内容的长度', length)
# sys.stdout = io.TextIOWrapper(sys.stdout.buffer, errors = 'replace', line_buffering = True)
sys.stdout = io.TextIOWrapper(sys.stdout.buffer,encoding='utf-8')
print(text)
根据
MSFlexGridWndClass好象不开放服务
搜索了一波,这个好像也很复杂,而且python好像无法直接
import psutil
import win32process
processId= win32process.GetWindowThreadProcessId(hwnd)
print(processId)
// 返回了一个列表 [8732, 9612]
Returns a tuple consisting of 2 ints:
Thread ID (tid) 线程id
Process ID (pid) 进程id(一般用这个)
//所以更正确的写法是
tid, pid = win32process.GetWindowThreadProcessId(hwnd)
active_window_path = psutil.Process(pid).exe()
还是要了解下常见的控件
使用Spy++来查看窗体的相关信息,以下都是(我所面对的程序的)窗口类名,肯定也有一些是 第三方自定义类
ThunderRT6Frame
MSFlexGridWndClass
感觉这个应该是我要重点关注的感谢ThunderForm,在得到进程的窗口句柄中,经常用到,不知道这个是什么?
ThunderRT6FormDC 是 VB6 窗体的 class name。这个是固定的,在调用API中有时用到。Thunder 是当年的一款 编程软件。 微软收购下来, 修改后改名为 Visual Basic 1.0
可以看到,最左边一栏就是当前监视的控件/窗口的句柄,右边有 WM_SETTEXT设置文本(设置文本后面一行还有 WM_SETEXT fSucceeded:Fasle、True 来表明状态是否改变), 可以看到有 WM_MOUSEMOVE 鼠标移动 WM_CAPTURECHANGED 捕获改变 都是一些预定义的动作(还好之前在C#编程的时候看过窗体。。。)
其他一些按钮改变同理
product document
就是了