Search code examples
c++winapidllgdi

BeginPaint fails when called from injected dll, even after EndPaint is called in target application


Basically what my title says. I'm trying to inject a dll in a target application in order to display things every time the target application receives a WM_PAINT message. There is the WNDPROC of my target:

#include <windows.h>
#include <stdio.h>

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT ps;

    switch(msg)
    {
        case WM_PAINT: 
            BeginPaint(hwnd, &ps); 
            TextOut(ps.hdc, 0, 0, "Hello, Windows!", 15); 
            EndPaint(hwnd, &ps); 
        break;
        case WM_CLOSE:
            DestroyWindow(hwnd);
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
        break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

And here is the dll I inject:

#include <Windows.h>
#include <stdio.h>

WNDPROC wpOrigProc;
HWND target_hwnd = (HWND)0x909E6; // HWND of the window I'm detouring

LRESULT APIENTRY MyWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT ps;

    LRESULT result = CallWindowProc(wpOrigProc, hwnd, msg, wParam, lParam);

    switch(msg)
    {
        case WM_PAINT: 
            BeginPaint(hwnd, &ps);
            TextOut(ps.hdc, 0, 50, "That was injected!", 18);
            EndPaint(hwnd, &ps);
        break;
    }

    return result; 
}

int APIENTRY DllMain(HMODULE module, DWORD reason, LPVOID reserved)
{
    HHOOK msgHook;
    FILE* stream;

    switch (reason)
    {
    case DLL_PROCESS_ATTACH:
        wpOrigProc = (WNDPROC)SetWindowLongPtr(target_hwnd, GWLP_WNDPROC, (LONG)MyWndProc);
        break;

    case DLL_PROCESS_DETACH:
        SetWindowLong(target_hwnd, GWL_WNDPROC, (LONG)wpOrigProc); 
        break;
    }

    return 1;
}

Now, I know the problem comes from BeginPaint because if I use GetDC and ReleaseDC instead, it works. It also works if I don't call CallWindowProc before BeginPaint.

It makes no sense to me as the original WNDPROC calls EndPaint at the end of his WM_PAINT, which means it shouldn't interfere with my injected WM_PAINT... Any idea?


Solution

  • EndPaint() validates the window, so the next BeginPaint() gets a DC that doesn't have any drawable (invalidated) area. Here's a discussion about GetDC()/ReleaseDC() vs. BeginPaint()/EndPaint().