根据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
。)在its NtUserPrintWindow
function中,参数被验证,然后它调用内部函数,IntPrintWindow
,根据PW_CLIENTONLY
标志的规格设置坐标,然后 - 如果它没有提前返回 - 将强制更新窗口并简单地从窗口的DC blit到指定的DC。
Wine项目(Windows API的另一个开源克隆)以不同的方式实现。在那里,the PrintWindow
function(完全由用户端实现)只需通过WM_PRINT
向指定窗口发送SendMessage
消息。这是implemented by Luke Benstead in December of 2009。我的猜测是Luke只是阅读MSDN文档并编写遵循其规范的代码,而不是复制Microsoft操作系统的实际行为。
现在,我最初认为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文档一致:
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
,因为它是由默认的窗口程序处理的。不仅必须这样(逻辑上),因为此消息的实现必须处理绘制非客户区域,窗口通常不会自行绘制,但the MSDN documentation明确确认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中尚不存在。