本人原创加STEEMIT首发
不过原创的时间略为久远,至少十几年前了,那时候还是比较好学的。
《启动应用程序并获得应用程序的主窗体句柄》
通过使用CreateProcess、EnumWindows、GetWindowThreadProcessId等函数,实现启动应用程序并获取应用程序主窗体句柄。
一般来讲,如果我们的程序要对其它程序进行操作,我们首先是通过应用程序的标题或者类名来查找应用程序(最顶层)窗体,并获得窗体句柄。然后通过发送消息来控制其它应用程序的行为。
Platform SDK中函数原型如下:
HWND FindWindow(
LPCTSTR lpClassName,
LPCTSTR lpWindowName
);
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.
通过这种方式进行查找,时常会遇到一些问题:
有没有一种方式能解决上述问题呢?
如果我们可以先启动应用程序,然后返回应用程序的窗体句柄,这样的话就会避免上述的种种问题。
我们可以通过CreateProcess来启动应用程序。
CreateProcess 函数原型如下:
BOOL CreateProcess(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
有关此函数的相关信息,可以查询MSDN获取。
我们主要用到的信息有:
LPCTSTR lpApplicationName,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
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结构体定义如下:
typedef struct _STARTUPINFO {
DWORD cb;
LPTSTR lpReserved;
LPTSTR lpDesktop;
LPTSTR lpTitle;
DWORD dwX;
DWORD dwY;
DWORD dwXSize;
DWORD dwYSize;
DWORD dwXCountChars;
DWORD dwYCountChars;
DWORD dwFillAttribute;
DWORD dwFlags;
WORD wShowWindow;
WORD cbReserved2;
LPBYTE lpReserved2;
HANDLE hStdInput;
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;
PROCESS_INFORMATION结构体定义如下:
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
} PROCESS_INFORMATION, *LPPROCESS_INFORMATION;
由此我们可以看出,通过CreateProcess启动应用程序,我们可以获取应用程序的进程句柄,线程句柄,进程ID以及线程ID。
那么我们如何才能进一步获取应用程序主窗体的句柄呢?
我们知道,如果知道了窗体的句柄(HWND),那么通过以下函数可以获取到dwProcessId,那么是否可以反过来通过线程ID来获取窗体的句柄呢?
DWORD GetWindowThreadProcessId(
HWND hWnd,
LPDWORD lpdwProcessId
);
我们知道,一个窗体对应一个线程。所以可以通过GetWindowThreadProcessId来获取对应的线程ID。但是一个线程可能对应有0到多个窗体,所以没有办法直接获取窗体的句柄。
那么如果我们能获取所有的顶层窗体,然后通过比较进程ID,是不是就可以确定相应进程ID所对应的顶成窗体呢?幸运的是Windows提供了这样一种机制,让我们枚举所有的顶层窗体,那就是EnumWindows。
BOOL EnumWindows(
WNDENUMPROC lpEnumFunc,
LPARAM lParam
);
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定义如下:
BOOL CALLBACK EnumWindowsProc(
HWND hwnd,
LPARAM lParam
);
至此,我们的思路已然明晰,实现我们的目的需要如下步骤:
在实际实现过程中,我们遇到了一些问题。
应用程序启动之后,需要进行一系列的初始化等操作,包括创建窗体等。如果我们启动应用程序后直接就通过EnumWindows枚举屏幕的顶层窗体,那么可能由于窗体还没有创建导致枚举失败。
通过调用WaitForInputIdle函数,可以等待窗体初始化完毕。
DWORD WaitForInputIdle(
HANDLE hProcess,
DWORD dwMilliseconds
);
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.
为了让我们的判断更加可靠,我们可以在枚举函数中使用GetWindowText判断一下窗体标题的长度是否为零。
通过对MSDN中Platform SDK Window、Process等相关内容的学习和了解,并通过实际操作测试检验和总结,我们得到一种启动应用程序并获取应用程序主窗体句柄的使用并可靠的方法。对此此种方法进行简单的封装后,可以很方便的在我们的应用程序中使用,进而可以应用在控制其它应用程序等多种用途。
十多年前的我是多么努力啊
再看看十多年以后,哎。
你们猜到我要干什么了吗?不是干坏事,呵呵