PrintWindow 发送消息 WM_PAINT 或 WM_PRINT?
PrintWindow send message WM_PAINT or WM_PRINT?
根据 msdn PrintWindow(检索日期 2017 年 5 月 5 日)
The application that owns the window referenced by hWnd processes the PrintWindow call and renders the image in the device context that is referenced by hdcBlt. The application receives a WM_PRINT message or, if the PW_PRINTCLIENT flag is specified, a WM_PRINTCLIENT message. For more information, see WM_PRINT and WM_PRINTCLIENT.
MSDN 从未声明过消息 WM_PAINT。
但是我的测试证明上面关于 WM_PRINT 消息的说法是错误的。
应用程序 A:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_PAINT:
DefWindowProc(hWnd, message, wParam, lParam);
break;
case WM_PRINT:
OutputDebugStringA("WM_PRINT");
break;
case WM_PRINTCLIENT:
OutputDebugStringA("WM_PRINTCLIENT");
break;
//other cases ...
}
return 0;
}
应用程序 B(有关 App B 的更多详细信息)
HWND hwnd = FindWindow(NULL, lpString);
//...
//PrintWindow(hwnd, hdc, PW_CLIENTONLY);
PrintWindow(hwnd, hdc, 0);
当我调用应用程序 B 捕获应用程序 A 时。根据 msdn PrintWindow,案例 WM_PRINT 应该命中,但案例 WM_PAINT 被命中。
If that's true then layered windows not implementing WM_PAINT can't be captured because UpdateWindow just sends WM_PAINT
所以最后我只想知道是msdn错了还是我代码错了? PrintWindow 发送消息 WM_PAINT 或 WM_PRINT?如果它确实发送消息 WM_PRINT,消息 WM_PRINT 是如何工作的?
简单回答: 是的,我在 Windows 10 和 Windows XP 上重现了您描述的行为。当我调用 PrintWindow
时,目标 window 收到 WM_PAINT
消息, 而不是 WM_PRINT
消息.
我不仅可以使用断点和跟踪输出重现它,而且我还可以通过使用调试器逐步执行 PrintWindow
(埋在 Windows 操作系统内部)来确认它本身)。与几乎所有用户和 GDI 函数一样,它是转发到服务器端系统函数的客户端存根 NtUserPrintWindow
。从这里开始,执行更多的系统函数和错误检查,最终将值 15(对应 WM_PAINT
消息)加载到 EDX
寄存器中,然后通过内部调度此消息名为 DispatchClientMessage
.
的函数
这基本上就是 PrintWindow
所做的一切——向指定的 window 发送一条 WM_PAINT
消息,要求它打印到指定的设备上下文中。所以是的,MSDN 文档提出了错误的声明。 PrintWindow
的实现 不 发送 WM_PRINT
消息。
查看 ReactOS 源代码(Windows 操作系统的开源克隆,旨在实现二进制 API 兼容),您可以看到它PrintWindow
的实现方式略有不同,但在道德上仍然是等价的。 (或者,更准确地说,它开始以道德上等同的方式实施PrintWindow
。它的实施似乎不完整,只是returns FALSE
.) 在 its NtUserPrintWindow
function, the parameters are verified, and then it calls down to an internal function, IntPrintWindow
中,它根据 PW_CLIENTONLY
标志的规范设置坐标,然后——如果它没有提前 return——将强制更新window 并简单地从 window 的 DC 到指定的 DC。
Wine 项目(Windows APIs 的另一个开源克隆)的做法不同。在那里,the PrintWindow
function (implemented entirely user-side) simply sends the specified window a WM_PRINT
message via SendMessage
. This was implemented by Luke Benstead in December of 2009。我的猜测是 Luke 只是阅读了 MSDN 文档并按照其规范编写了代码,而不是复制 Microsoft OS.
的实际行为
现在,我最初认为 MSDN 只是过时了,而不是完全错误。 Windows Vista 引入的 DWM 促使各种绘图 API 的实现方式发生了一些变化,我认为 PrintWindow
的文档仍然指的是事情是如何发生的在遗留绘图模型中工作。 (记录实现细节而不是行为的结果。)但事实上,在 Windows XP 上的测试反驳了这个假设。 XP 的行为方式与 Windows 10 完全相同,PrintWindow
发送 WM_PAINT
消息而不是 WM_PRINT
消息。更改可能是在更早的时间进行的,并且 MSDN 文档甚至已经过时了。例如,也许 Windows 9x 以这种方式实现了 PrintWindow
,但 NT 从未这样做过。我目前无法访问带有编译器的此类系统,因此无法验证。如果我记得的话,我稍后会更新这个答案。
令我感到奇怪的是 Raymond Chen described in passing PrintWindow
函数的行为与 MSDN 文档一致:
The PrintWindow
function passes a custom device context as a parameter to the WM_PRINT
message…
这是在 2012 年左右写的,他当然可以访问 Windows 源代码,所以要么我在分析中遗漏了一些东西,要么 Raymond also 他的文章基于官方文档,而不是实际查看实现的作用,因为它并没有真正影响文章的要点。
说到这里,我不太明白你的问题是为什么这些问题很重要。当然,研究 OS 的实际工作方式很有趣,但您不应该根据逆向工程时发现的内容编写代码。我无法想象 PrintWindow
是通过发送 WM_PAINT
消息还是 WM_PRINT
消息在内部实现会很重要的任何原因。在任何一个理智的版本中,效果都是一样的:您将把指定 window 的请求部分绘制到指定的设备上下文中。就那么简单。也就是说,App B既不需要知道也不关心PrintWindow
是如何实现的。
一个正确编写的应用程序 A(换句话说,所有 Windows GUI 应用程序)都会有 WM_PAINT
和 WM_PRINTCLIENT
消息的处理程序。 WM_PAINT
应该以显而易见的方式处理,而 WM_PRINTCLIENT
应该简单地利用这个实现——例如:
case WM_PAINT:
{
PAINTSTRUCT ps;
BeginPaint(hWnd, &ps);
OnPaintContent(ps);
EndPaint(hWnd, &ps);
return 0;
}
case WM_PRINTCLIENT:
{
PAINTSTRUCT ps;
ps.hdc = reinterpret_cast<HDC>(wParam);
GetClientRect(hWnd, &ps.rcPaint);
OnPaintContent(ps);
return 0;
}
...
void PaintContent(const PAINTSTRUCT& ps)
{
// Paint the window's content here.
}
您根本没有理由处理 WM_PRINT
,因为这是由默认的 window 过程处理的。不仅 必须 如此(逻辑上),因为此消息的实现必须处理非客户区的绘制,window 通常不会绘制自身,但是 the MSDN documentation 在 "Remarks" 部分下明确确认 DefWindowProc
处理此消息,根据指定的标志向 window 发送适当的子消息,包括 WM_ERASEBKGND
和WM_PRINTCLIENT
.
windows API PrintWindow 使用 WM_PAINT 是有原因的:它适用于不同的进程。与其他 GDI 句柄一样,DC 句柄仅在创建它的同一进程内有效。因此,从另一个进程向 window 发送 WM_PRINT 或 WM_PRINTCLIENT 失败。
但是... PrintWindow 成功地获取了另一个进程的内容,因为它首先通过 BeginPaint 在被调用进程的上下文中创建一个 DC,然后将内容复制回调用进程的 DC。当然,他们也可以用另一个句柄对 WM_PRINT(CLIENT) 进行存根,但我认为当前的解决方案更简单。
顺便说一句,PrintWindow API 是在 XP 中引入的,它在 W2K 中还不存在。
根据 msdn PrintWindow(检索日期 2017 年 5 月 5 日)
The application that owns the window referenced by hWnd processes the PrintWindow call and renders the image in the device context that is referenced by hdcBlt. The application receives a WM_PRINT message or, if the PW_PRINTCLIENT flag is specified, a WM_PRINTCLIENT message. For more information, see WM_PRINT and WM_PRINTCLIENT.
MSDN 从未声明过消息 WM_PAINT。 但是我的测试证明上面关于 WM_PRINT 消息的说法是错误的。
应用程序 A:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_PAINT:
DefWindowProc(hWnd, message, wParam, lParam);
break;
case WM_PRINT:
OutputDebugStringA("WM_PRINT");
break;
case WM_PRINTCLIENT:
OutputDebugStringA("WM_PRINTCLIENT");
break;
//other cases ...
}
return 0;
}
应用程序 B(有关 App B 的更多详细信息)
HWND hwnd = FindWindow(NULL, lpString);
//...
//PrintWindow(hwnd, hdc, PW_CLIENTONLY);
PrintWindow(hwnd, hdc, 0);
当我调用应用程序 B 捕获应用程序 A 时。根据 msdn PrintWindow,案例 WM_PRINT 应该命中,但案例 WM_PAINT 被命中。
If that's true then layered windows not implementing WM_PAINT can't be captured because UpdateWindow just sends WM_PAINT
所以最后我只想知道是msdn错了还是我代码错了? PrintWindow 发送消息 WM_PAINT 或 WM_PRINT?如果它确实发送消息 WM_PRINT,消息 WM_PRINT 是如何工作的?
简单回答: 是的,我在 Windows 10 和 Windows XP 上重现了您描述的行为。当我调用 PrintWindow
时,目标 window 收到 WM_PAINT
消息, 而不是 WM_PRINT
消息.
我不仅可以使用断点和跟踪输出重现它,而且我还可以通过使用调试器逐步执行 PrintWindow
(埋在 Windows 操作系统内部)来确认它本身)。与几乎所有用户和 GDI 函数一样,它是转发到服务器端系统函数的客户端存根 NtUserPrintWindow
。从这里开始,执行更多的系统函数和错误检查,最终将值 15(对应 WM_PAINT
消息)加载到 EDX
寄存器中,然后通过内部调度此消息名为 DispatchClientMessage
.
这基本上就是 PrintWindow
所做的一切——向指定的 window 发送一条 WM_PAINT
消息,要求它打印到指定的设备上下文中。所以是的,MSDN 文档提出了错误的声明。 PrintWindow
的实现 不 发送 WM_PRINT
消息。
查看 ReactOS 源代码(Windows 操作系统的开源克隆,旨在实现二进制 API 兼容),您可以看到它PrintWindow
的实现方式略有不同,但在道德上仍然是等价的。 (或者,更准确地说,它开始以道德上等同的方式实施PrintWindow
。它的实施似乎不完整,只是returns FALSE
.) 在 its NtUserPrintWindow
function, the parameters are verified, and then it calls down to an internal function, IntPrintWindow
中,它根据 PW_CLIENTONLY
标志的规范设置坐标,然后——如果它没有提前 return——将强制更新window 并简单地从 window 的 DC 到指定的 DC。
Wine 项目(Windows APIs 的另一个开源克隆)的做法不同。在那里,the PrintWindow
function (implemented entirely user-side) simply sends the specified window a WM_PRINT
message via SendMessage
. This was implemented by Luke Benstead in December of 2009。我的猜测是 Luke 只是阅读了 MSDN 文档并按照其规范编写了代码,而不是复制 Microsoft OS.
现在,我最初认为 MSDN 只是过时了,而不是完全错误。 Windows Vista 引入的 DWM 促使各种绘图 API 的实现方式发生了一些变化,我认为 PrintWindow
的文档仍然指的是事情是如何发生的在遗留绘图模型中工作。 (记录实现细节而不是行为的结果。)但事实上,在 Windows XP 上的测试反驳了这个假设。 XP 的行为方式与 Windows 10 完全相同,PrintWindow
发送 WM_PAINT
消息而不是 WM_PRINT
消息。更改可能是在更早的时间进行的,并且 MSDN 文档甚至已经过时了。例如,也许 Windows 9x 以这种方式实现了 PrintWindow
,但 NT 从未这样做过。我目前无法访问带有编译器的此类系统,因此无法验证。如果我记得的话,我稍后会更新这个答案。
令我感到奇怪的是 Raymond Chen described in passing PrintWindow
函数的行为与 MSDN 文档一致:
The
PrintWindow
function passes a custom device context as a parameter to theWM_PRINT
message…
这是在 2012 年左右写的,他当然可以访问 Windows 源代码,所以要么我在分析中遗漏了一些东西,要么 Raymond also 他的文章基于官方文档,而不是实际查看实现的作用,因为它并没有真正影响文章的要点。
说到这里,我不太明白你的问题是为什么这些问题很重要。当然,研究 OS 的实际工作方式很有趣,但您不应该根据逆向工程时发现的内容编写代码。我无法想象 PrintWindow
是通过发送 WM_PAINT
消息还是 WM_PRINT
消息在内部实现会很重要的任何原因。在任何一个理智的版本中,效果都是一样的:您将把指定 window 的请求部分绘制到指定的设备上下文中。就那么简单。也就是说,App B既不需要知道也不关心PrintWindow
是如何实现的。
一个正确编写的应用程序 A(换句话说,所有 Windows GUI 应用程序)都会有 WM_PAINT
和 WM_PRINTCLIENT
消息的处理程序。 WM_PAINT
应该以显而易见的方式处理,而 WM_PRINTCLIENT
应该简单地利用这个实现——例如:
case WM_PAINT:
{
PAINTSTRUCT ps;
BeginPaint(hWnd, &ps);
OnPaintContent(ps);
EndPaint(hWnd, &ps);
return 0;
}
case WM_PRINTCLIENT:
{
PAINTSTRUCT ps;
ps.hdc = reinterpret_cast<HDC>(wParam);
GetClientRect(hWnd, &ps.rcPaint);
OnPaintContent(ps);
return 0;
}
...
void PaintContent(const PAINTSTRUCT& ps)
{
// Paint the window's content here.
}
您根本没有理由处理 WM_PRINT
,因为这是由默认的 window 过程处理的。不仅 必须 如此(逻辑上),因为此消息的实现必须处理非客户区的绘制,window 通常不会绘制自身,但是 the MSDN documentation 在 "Remarks" 部分下明确确认 DefWindowProc
处理此消息,根据指定的标志向 window 发送适当的子消息,包括 WM_ERASEBKGND
和WM_PRINTCLIENT
.
windows API PrintWindow 使用 WM_PAINT 是有原因的:它适用于不同的进程。与其他 GDI 句柄一样,DC 句柄仅在创建它的同一进程内有效。因此,从另一个进程向 window 发送 WM_PRINT 或 WM_PRINTCLIENT 失败。
但是... PrintWindow 成功地获取了另一个进程的内容,因为它首先通过 BeginPaint 在被调用进程的上下文中创建一个 DC,然后将内容复制回调用进程的 DC。当然,他们也可以用另一个句柄对 WM_PRINT(CLIENT) 进行存根,但我认为当前的解决方案更简单。
顺便说一句,PrintWindow API 是在 XP 中引入的,它在 W2K 中还不存在。