繁体   English   中英

PrintWindow发送消息WM_PAINT还是WM_PRINT?

[英]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_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 ,因为它由默认窗口过程处理。 这不仅必须如此(逻辑上),因为此消息的实现必须处理绘制非客户区域,窗口通常不会自行绘制,但MSDN文档明确确认在“备注”部分下DefWindowProc处理此问题消息,根据指定的标志发送窗口相应的子消息,包括WM_ERASEBKGNDWM_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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM