本人原创加STEEMIT首发
不过原创的时间略为久远,至少十几年前了,那时候还是比较好学的。
《启动应用程序并获得应用程序的主窗体句柄》
摘要
通过使用CreateProcess、EnumWindows、GetWindowThreadProcessId等函数,实现启动应用程序并获取应用程序主窗体句柄。
常规的方法与问题
一般来讲,如果我们的程序要对其它程序进行操作,我们首先是通过应用程序的标题或者类名来查找应用程序(最顶层)窗体,并获得窗体句柄。然后通过发送消息来控制其它应用程序的行为。
Platform SDK中函数原型如下:
The FindWindow function retrieves a handle to the top-level window whose class name and window name match the specified strings. This function does not search child windows. This function does not perform a case-sensitive search.
通过这种方式进行查找,时常会遇到一些问题:
1) 首先必须知道窗体类或者窗体标题。
实际上,我们并不是总是事先知道窗体标题,或者知道窗体类。
2) 应用程序必须已经打开
有时候,应用程序并没有启动。
那么我们需要先启动应用程序,然后再使用FindWindow查找。
3) 如果应用程序已运行多个实例,则查找到的第一个实例未必是我们想要的实例。
我们需要查找到的,就是我们想要的。
4) 对于复杂的窗体标题,容易输入错误
我们需要一种简洁的方式,而不是每次去看窗体标题,然后再输入标题。
有没有一种方式能解决上述问题呢?
如果我们可以先启动应用程序,然后返回应用程序的窗体句柄,这样的话就会避免上述的种种问题。
CreateProcess启动应用程序
CreateProcess函数原型
我们可以通过CreateProcess来启动应用程序。
CreateProcess 函数原型如下:
有关此函数的相关信息,可以查询MSDN获取。
相关参数信息
我们主要用到的信息有:
LpApplicationName 用来指定应用程序的全路径。
LpStartupInfo 指STARTUPINFO结构体的指针,用来设置待启动的应用程序的信息。
lpProcessInformation 指向PROCESS_INFORMATION结构体的指针,用来返回新进程的信息。
参数简介
lpApplicationName
[in] Pointer to a null-terminated string that specifies the module to execute. The specified module can be a Windows-based application. It can be some other type of module (for example, MS-DOS or OS/2) if the appropriate subsystem is available on the local computer.
lpStartupInfo
[in] Pointer to a STARTUPINFO structure that specifies the window station, desktop, standard handles, and appearance of the main window for the new process.
lpProcessInformation
[out] Pointer to a PROCESS_INFORMATION structure that receives identification information about the new process.
STARTUPINFO结构体
STARTUPINFO结构体定义如下:
PROCESS_INFORMATION结构体
PROCESS_INFORMATION结构体定义如下:
由此我们可以看出,通过CreateProcess启动应用程序,我们可以获取应用程序的进程句柄,线程句柄,进程ID以及线程ID。
GetWindowThreadProcessId
那么我们如何才能进一步获取应用程序主窗体的句柄呢?
我们知道,如果知道了窗体的句柄(HWND),那么通过以下函数可以获取到dwProcessId,那么是否可以反过来通过线程ID来获取窗体的句柄呢?
我们知道,一个窗体对应一个线程。所以可以通过GetWindowThreadProcessId来获取对应的线程ID。但是一个线程可能对应有0到多个窗体,所以没有办法直接获取窗体的句柄。
EnumWindows function
那么如果我们能获取所有的顶层窗体,然后通过比较进程ID,是不是就可以确定相应进程ID所对应的顶成窗体呢?幸运的是Windows提供了这样一种机制,让我们枚举所有的顶层窗体,那就是EnumWindows。
EnumWindows原型&参数
lpEnumFunc
[in] Pointer to an application-defined callback function. For more information, see EnumWindowsProc.lParam
[in] Specifies an application-defined value to be passed to the callback function.
The EnumWindows function enumerates all top-level windows on the screen by passing the handle to each window, in turn, to an application-defined callback function. EnumWindows continues until the last top-level window is enumerated or the callback function returns FALSE.
EnumWindows枚举屏幕上的顶层窗体,轮流将窗体的句柄传入回调函数中,直到最后一个窗体被枚举完成,或者回调函数返回FALSE。
EnumWindowsProc定义
EnumWindowsProc定义如下:
整体思路&细节问题
整体思路
至此,我们的思路已然明晰,实现我们的目的需要如下步骤:
- 通过CreateProcess启动应用程序,并记录返回的进程ID
- 通过EnumWindows枚举屏幕的顶层窗体
- 在枚举回调函数中,调用GetWindowThreadProcessId获取窗体对应的进程ID并与CreateProcess获取的进程ID比较,如相同,则记录窗体的句柄,并返回。
细节问题
在实际实现过程中,我们遇到了一些问题。
应用程序初始化问题
应用程序启动之后,需要进行一系列的初始化等操作,包括创建窗体等。如果我们启动应用程序后直接就通过EnumWindows枚举屏幕的顶层窗体,那么可能由于窗体还没有创建导致枚举失败。
通过调用WaitForInputIdle函数,可以等待窗体初始化完毕。
The WaitForInputIdle function waits until the specified process is waiting for user input with no input pending, or until the time-out interval has elapsed.
Parameters
*hProcess
[in] Handle to the process. If this process is a console application or does not have a message queue, WaitForInputIdle returns immediately.
- dwMilliseconds
[in] Time-out interval, in milliseconds. If dwMilliseconds is INFINITE, the function does not return until the process is idle.
窗体标题
为了让我们的判断更加可靠,我们可以在枚举函数中使用GetWindowText判断一下窗体标题的长度是否为零。
总结
通过对MSDN中Platform SDK Window、Process等相关内容的学习和了解,并通过实际操作测试检验和总结,我们得到一种启动应用程序并获取应用程序主窗体句柄的使用并可靠的方法。对此此种方法进行简单的封装后,可以很方便的在我们的应用程序中使用,进而可以应用在控制其它应用程序等多种用途。
十多年前的我是多么努力啊
再看看十多年以后,哎。
你们猜到我要干什么了吗?不是干坏事,呵呵
This page is synchronized from the post: 发篇技术文档,大家猜猜我要干啥? / 启动应用程序并获得应用程序的主窗体句柄