[英]PrintWindow send message WM_PAINT or WM_PRINT?
根据msdn PrintWindow (2017年5月5日检索日期)
拥有hWnd引用的窗口的应用程序处理PrintWindow调用,并在hdcBlt引用的设备上下文中呈现图像。 应用程序接收WM_PRINT消息,或者,如果指定了PW_PRINTCLIENT标志,则接收WM_PRINTCLIENT消息。 有关更多信息,请参阅WM_PRINT和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;
}
App 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被命中。
根据这篇文章
如果这是真的,则无法捕获未实现WM_PAINT的分层窗口,因为UpdateWindow只发送WM_PAINT
所以最后,我只是想知道msdn是错还是我的代码错了? PrintWindow发送消息WM_PAINT还是WM_PRINT? 如果它真的发送消息WM_PRINT,消息WM_PRINT如何工作?
简单回答:是的,我重现了您在Windows 10和Windows XP上描述的行为。 当我调用PrintWindow
,目标窗口获取WM_PAINT
消息, 而不是 WM_PRINT
消息。
我不仅可以使用断点和跟踪输出重现它,而且我还可以通过使用调试器逐步执行PrintWindow
(隐藏在Windows操作系统内部)来确认它。 与几乎所有User和GDI函数一样,它是一个客户端存根,它转发到服务器端系统函数NtUserPrintWindow
。 从这一点开始,执行后会执行一些系统函数和错误检查,并最终将值15(对应于WM_PAINT
消息)加载到EDX
寄存器中,然后通过名为DispatchClientMessage
的内部函数调度此消息。
这基本上就是PrintWindow
所做的全部 - 向指定窗口发送WM_PAINT
消息,要求它打印到指定的设备上下文中。 所以是的,MSDN文档正在做出错误的声明。 PrintWindow
的实现不会发送WM_PRINT
消息。
查看ReactOS源代码(Windows操作系统的开源克隆,旨在与二进制API兼容),您可以看到它以不同的方式实现PrintWindow
,但仍然在道德上等同。 (或者,更准确地说,它开始以一种在道德上等同的方式实现PrintWindow
。它的实现似乎是不完整的,并且只返回FALSE
。)在其NtUserPrintWindow
函数中 ,参数被验证,然后它调用内部函数, IntPrintWindow
,它根据PW_CLIENTONLY
标志的规范设置坐标,然后 - 如果它没有提前返回 - 将强制更新窗口并简单地从窗口的DC到指定的DC。
Wine项目(Windows API的另一个开源克隆)以不同的方式实现。 在那里, PrintWindow
函数 (完全由用户端实现)只是通过SendMessage
向指定的窗口发送WM_PRINT
消息。 这是由Luke Benstead于2009年12月实施的 。 我的猜测是Luke只是阅读MSDN文档并编写遵循其规范的代码,而不是复制Microsoft操作系统的实际行为。
现在,我最初认为MSDN已经过时了,而不是完全错误。 随Windows Vista引入的DWM引发了各种绘图API实现方式的一些变化,我认为PrintWindow
的文档仍然指的是传统绘图模型中的工作原理。 (记录实现细节而非行为的结果。)事实上,在Windows XP上进行的测试反驳了这一假设。 XP的行为方式与Windows 10完全相同, PrintWindow
发送WM_PAINT
消息, PrintWindow
发送WM_PRINT
消息。 更改可能在更早的时间进行,并且MSDN文档甚至更新。 例如,也许Windows 9x以这种方式实现了PrintWindow
,但NT从未这样做过。 我目前无法使用编译器访问这样的系统,所以我无法验证。 如果我记得,我稍后会更新这个答案。
令我感到奇怪的是, Raymond Chen以与MSDN文档一致的方式传递了PrintWindow
函数的行为:
PrintWindow
函数将自定义设备上下文作为参数传递给WM_PRINT
消息...
这是在2012年左右编写的,他当然可以访问Windows源代码,所以要么我在分析中遗漏了一些内容,要么Raymond 也将他的文章基于官方文档,而不是实际查看实现的内容,因为它并没有真正影响文章的要点。
说到这一点,我对你的问题不太了解的是为什么这一点很重要 。 当然,研究操作系统的实际工作方式很有趣,但是不应该根据逆向工程时的内容编写代码。 我无法想象为什么PrintWindow
是通过发送WM_PAINT
消息还是WM_PRINT
消息在内部实现的。 在任何一个理智版本中,效果都是相同的:您将指定窗口的请求部分绘制到指定的设备上下文中。 就那么简单。 换句话说,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
,因为它由默认窗口过程处理。 这不仅必须如此(逻辑上),因为此消息的实现必须处理绘制非客户区域,窗口通常不会自行绘制,但MSDN文档明确确认在“备注”部分下DefWindowProc
处理此问题消息,根据指定的标志发送窗口相应的子消息,包括WM_ERASEBKGND
和WM_PRINTCLIENT
。
Windows API PrintWindow使用WM_PAINT是有原因的:它适用于不同的进程。 像其他GDI句柄一样的DC句柄仅在创建它的同一进程中有效。 因此,将WM_PRINT或WM_PRINTCLIENT发送到另一个进程的窗口失败。
但是...... PrintWindow成功获取另一个进程的内容,因为它首先通过BeginPaint在被调用进程的上下文中创建DC,然后将内容复制回调用进程的DC。 当然,他们也可以使用另一个句柄来存根WM_PRINT(CLIENT),但我认为当前的解决方案更简单。
顺便说一句,PrintWindow API是在XP中引入的,它在W2K中尚不存在。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.