PrintWindow发送消息WM_PAINT还是WM_PRINT?

问题描述 投票:3回答:2

根据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被命中。

根据this article

如果这是真的,则无法捕获未实现WM_PAINT的分层窗口,因为UpdateWindow只发送WM_PAINT

所以最后,我只是想知道msdn是错还是我的代码错了? PrintWindow发送消息WM_PAINT还是WM_PRINT?如果它真的发送消息WM_PRINT,消息WM_PRINT如何工作?

c++ windows winapi gdi
2个回答
4
投票

简单回答:是的,我重现了您在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文档一致:

Print­Window函数将自定义设备上下文作为参数传递给WM_PRINT消息...

这是在2012年左右编写的,他当然可以访问Windows源代码,所以要么我在分析中遗漏了一些内容,要么Raymond也将他的文章基于官方文档,而不是实际查看实现的内容,因为它并没有真正影响文章的要点。

说到这一点,我对你的问题不太了解的是为什么这一点很重要。当然,研究操作系统的实际工作方式很有趣,但是不应该根据逆向工程时的内容编写代码。我无法想象为什么PrintWindow是通过发送WM_PAINT消息或WM_PRINT消息在内部实现的重要原因。在任何一个理智版本中,效果都是相同的:您将指定窗口的请求部分绘制到指定的设备上下文中。就那么简单。换句话说,App B既不需要了解也不关心PrintWindow是如何实现的。

正确编写的应用程序A(换句话说,所有Windows GUI应用程序)将具有WM_PAINTWM_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_ERASEBKGNDWM_PRINTCLIENT


0
投票

Windows API PrintWindow使用WM_PAINT是有原因的:它适用于不同的进程。像其他GDI句柄一样的DC句柄仅在创建它的同一进程中有效。因此,将WM_PRINT或WM_PRINTCLIENT发送到另一个进程的窗口失败。

但是...... PrintWindow成功获取另一个进程的内容,因为它首先通过BeginPaint在被调用进程的上下文中创建DC,然后将内容复制回调用进程的DC。当然,他们也可以使用另一个句柄来存根WM_PRINT(CLIENT),但我认为当前的解决方案更简单。

顺便说一句,PrintWindow API是在XP中引入的,它在W2K中尚不存在。

© www.soinside.com 2019 - 2024. All rights reserved.