发篇技术文档,大家猜猜我要干啥? / 启动应用程序并获得应用程序的主窗体句柄

in cn •  7 years ago  (edited)

本人原创加STEEMIT首发
不过原创的时间略为久远,至少十几年前了,那时候还是比较好学的。
《启动应用程序并获得应用程序的主窗体句柄》


Image source

摘要

通过使用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.

通过这种方式进行查找,时常会遇到一些问题:

  1. 首先必须知道窗体类或者窗体标题。
    实际上,我们并不是总是事先知道窗体标题,或者知道窗体类。
  2. 应用程序必须已经打开
    有时候,应用程序并没有启动。
    那么我们需要先启动应用程序,然后再使用FindWindow查找。
  3. 如果应用程序已运行多个实例,则查找到的第一个实例未必是我们想要的实例。
    我们需要查找到的,就是我们想要的。
  4. 对于复杂的窗体标题,容易输入错误
    我们需要一种简洁的方式,而不是每次去看窗体标题,然后再输入标题。

有没有一种方式能解决上述问题呢?

如果我们可以先启动应用程序,然后返回应用程序的窗体句柄,这样的话就会避免上述的种种问题。

CreateProcess启动应用程序

CreateProcess函数原型

我们可以通过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结构体

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结构体

PROCESS_INFORMATION结构体定义如下:

typedef struct _PROCESS_INFORMATION {
  HANDLE hProcess;
  HANDLE hThread;
  DWORD dwProcessId;
  DWORD dwThreadId;
} PROCESS_INFORMATION, *LPPROCESS_INFORMATION;

由此我们可以看出,通过CreateProcess启动应用程序,我们可以获取应用程序的进程句柄,线程句柄,进程ID以及线程ID。

GetWindowThreadProcessId

那么我们如何才能进一步获取应用程序主窗体的句柄呢?

我们知道,如果知道了窗体的句柄(HWND),那么通过以下函数可以获取到dwProcessId,那么是否可以反过来通过线程ID来获取窗体的句柄呢?

DWORD GetWindowThreadProcessId(          
HWND hWnd,
    LPDWORD lpdwProcessId
);

我们知道,一个窗体对应一个线程。所以可以通过GetWindowThreadProcessId来获取对应的线程ID。但是一个线程可能对应有0到多个窗体,所以没有办法直接获取窗体的句柄。

EnumWindows function

那么如果我们能获取所有的顶层窗体,然后通过比较进程ID,是不是就可以确定相应进程ID所对应的顶成窗体呢?幸运的是Windows提供了这样一种机制,让我们枚举所有的顶层窗体,那就是EnumWindows。

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定义

EnumWindowsProc定义如下:

BOOL CALLBACK EnumWindowsProc(
HWND hwnd,
    LPARAM lParam
);

整体思路&细节问题

整体思路

至此,我们的思路已然明晰,实现我们的目的需要如下步骤:

  • 通过CreateProcess启动应用程序,并记录返回的进程ID
  • 通过EnumWindows枚举屏幕的顶层窗体
  • 在枚举回调函数中,调用GetWindowThreadProcessId获取窗体对应的进程ID并与CreateProcess获取的进程ID比较,如相同,则记录窗体的句柄,并返回。

细节问题

在实际实现过程中,我们遇到了一些问题。

应用程序初始化问题

应用程序启动之后,需要进行一系列的初始化等操作,包括创建窗体等。如果我们启动应用程序后直接就通过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.

  • 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等相关内容的学习和了解,并通过实际操作测试检验和总结,我们得到一种启动应用程序并获取应用程序主窗体句柄的使用并可靠的方法。对此此种方法进行简单的封装后,可以很方便的在我们的应用程序中使用,进而可以应用在控制其它应用程序等多种用途。


十多年前的我是多么努力啊
再看看十多年以后,哎。

你们猜到我要干什么了吗?不是干坏事,呵呵

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  
  ·  7 years ago 

看不懂。哈哈

震惊!老程序员 十年前竟然写出这样的代码

哪看到代码了 😄
都是msdn 里的windows API

o婶这是准备干嘛呢 好得说说嘛

  ·  7 years ago (edited)

你的记忆力真好,我都忘干净了,回忆代码逻辑很伤脑细胞,哈哈,
记得很早前,做远程控制玩窗体时对这东西兴趣了一番,现在还有许多软件没有过时, 你一定干过许多坏事.用这东西偷看你初恋洗澡 :)

十多年前写的
藏私了十几年啊

Hi friends..

你是一个大小伙子!

很久远的记忆里做过类似的东西:)

nice...

新裝的窗戶要裝個把手而已吧,用得著搞這麼大動靜嗎?

哈哈哈
你的千万台币的庄园玻璃都不想要了是吧?一会我雇人去砸 💢

我住台灣,可幫忙砸xd

中,去帮我消灭你楼上的家伙

偷偷告訴你,他樓上是你耶~~~ 為你默哀~~~~

image

  ·  7 years ago 

这。。。台湾还有吗?

呃。。

看不懂 :(

  ·  7 years ago (edited)

你說看不懂,但...你看不懂的就是你。搜尋通篇文章,有18次提到rea.... 隱藏在code裡。

怎麼找不到 deanliu 第一霉女勒?哼!

哈哈哈,開開玩笑啦!你們別介意啊!我可不想害你們各自家庭吵架喔~~~~~ LOL

haha...

😢

Hi @rea,

Congratulations! You have been chosen to appear on another amazing edition of "Who to Follow Daily". Thank you for adding so much value to the Steemit community. Steem on!

Amazing, thank you @cem!!
Steem on!!!

You are welcome 😎

对这不内行。看不懂。

A very good post very good achievement greetings from me @ abupasi.alachy

Very informative and useful to the reader. Good work and good content. Will continue to follow.

  ·  7 years ago 

十几年的 API 到了今天还是能用得上。
太牛了。 O哥每篇贴都很不错啊(必赞)

哇, 想起了上大学时啃 Windows API 和 MFC 框架的时候 :D