3
votes

I'm actually trying to read a specific pixel on a window which is hidden by others. I want to use the GetPixel function from GDI library but it seems it only works with the global device context. I can't read pixel from a specific window and I don't understand why.. I found this article which uses the PrintWindow function to copy a specific window content to a temporary device context which can be read. But I can't reproduce it.

EDIT

Thank you all my problem is solved :)
This script give you the RGB color of the pointer on the choosen window, even though the window is hidden. Remind that this program must be launch with admin privileges to get the pixels of processes launched with admin privileges.

#define STRICT
#define WINVER 0x0501
#define _WIN32_WINNT 0x0501
// 0x0501 for PrintWindow function
// You must be at least running Windows XP
// See http://msdn.microsoft.com/en-us/library/6sehtctf.aspx

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

#define WINDOW_LIST_LIMIT 32
#define WINDOW_NAME_LIMIT 1024

void FatalError(char* error)
{
    printf("%s", error);
    exit(-1);
}

HWND window_list[WINDOW_LIST_LIMIT];
unsigned int window_list_index = 0;

BOOL EnumWindowsProc(HWND window_handle, LPARAM param)
{
    char window_title[WINDOW_NAME_LIMIT];

    if(!IsWindowVisible(window_handle)) return TRUE;

    RECT rectangle = {0};
    GetWindowRect(window_handle, &rectangle);
    if (IsRectEmpty(&rectangle)) return TRUE;

    GetWindowText(window_handle, window_title, sizeof(window_title));
    if(strlen(window_title) == 0) return TRUE;
    if(!strcmp(window_title, "Program Manager")) return TRUE;

    window_list[window_list_index] = window_handle;
    window_list_index++;

    printf("%u - %s\n", window_list_index, window_title);

    if(window_list_index == WINDOW_LIST_LIMIT) return FALSE;
    return TRUE;
}

int main(int argc, char** argv)
{
    unsigned int i, input;

    EnumWindows((WNDENUMPROC) EnumWindowsProc, (LPARAM) NULL);

    printf("\nChoose a window: ");
    scanf("%u", &input);
    printf("\n");
    if(input > window_list_index) FatalError("Bad choice..\n");

    HDC window_dc = GetWindowDC(window_list[input - 1]), global_dc = GetDC(0), temp_dc;
    if(!window_dc && !global_dc) FatalError("Fatal Error - Cannot get device context.\n");

    POINT cursor, previous_cursor;

    while(1)
    {
        temp_dc = CreateCompatibleDC(window_dc);
        if(!temp_dc) FatalError("Fatal Error - Cannot create compatible device context.\n");

        RECT window_rectangle;
        GetWindowRect(window_list[input - 1], &window_rectangle);

        HBITMAP bitmap = CreateCompatibleBitmap(window_dc,
            window_rectangle.right - window_rectangle.left,
            window_rectangle.bottom - window_rectangle.top);

        if (bitmap)
        {
            SelectObject(temp_dc, bitmap);
            PrintWindow(window_list[input - 1], temp_dc, 0);
            DeleteObject(bitmap);
        }

        GetCursorPos(&cursor);
        if(cursor.x != previous_cursor.x && cursor.y != previous_cursor.y)
        {
            COLORREF color = GetPixel(temp_dc, cursor.x - window_rectangle.left, cursor.y - window_rectangle.top);
            int red = GetRValue(color);
            int green = GetGValue(color);
            int blue = GetBValue(color);

            printf("\rRGB %02X%02X%02X", red, green, blue);

            cursor = previous_cursor;
        }

        DeleteDC(temp_dc);
        Sleep(50); // for lags
    }

    ReleaseDC(window_list[input - 1], window_dc);
    return 0;
}

I've changed some things, now User32 isn't dynamically loaded.
It compiles with

gcc main.c -o main.exe -lGid32 -lUser32

Have a great day !

4
You can't make PrintWindow() work across processes.Hans Passant
@Hans are you sure? The PrintWindow documentation implies otherwise.Ben Voigt
Yes. The painting code lives in the other process. HDCs have process affinity: stackoverflow.com/questions/2499487/…Hans Passant
@Hans: So HDCs have process affinity. But window messages defined by the OS handle parameter marshaling.Ben Voigt

4 Answers

4
votes

You are passing a process handle to GetDC. That's not right. Processes don't have device contexts, windows do. Remember a process can have many windows, or even none at all.

You need to get hold of the window handle, the HWND, for the window in question, and pass that to GetDC. I'd look to using FindWindow or EnumWindows to find your target top-level window.

Of course, there may be other problems with your code, but that's the one that jumps out at me.

4
votes
 HDC process_dc = GetDC(process_handle)

Well that's all kinds of wrong. GetDC accepts a window handle, not a process handle.

In order to find such errors, recompile with

#define STRICT

placed before your includes.

2
votes

This is a bit of a confusing topic, so let's see if I can clarify a few things.

First things first: as both David and Ben have already answered, you're passing a process handle to the GetDC function, which is wrong. GetDC accepts a handle to a window (the HWND type), and it returns a device context (DC, the HDC type) corresponding to that window. You need to get that fixed before anything else will work.

Now, as the article you've read indicates, windows (assuming they've been correctly programmed) respond to the WM_PRINT or WM_PRINTCLIENT messages by rendering an image of themselves into the specified device context (HDC). This is a simple and effective way of capturing an "image" of a window, whether an overlapping window or the window of an individual control.

The rub comes in, as Hans mentioned in a comment, because handles to device contexts have process affinity, which means that the HDC you pass to the window in a separate process, into which it is supposed to render itself, will not be valid from that other process. Handles to device contexts cannot be passed across process boundaries. That's the primary reason that your code fails (or is going to fail, once you fix the handle type problems). The MSDN entry on GDI Objects makes this explicitly clear:

Handles to GDI objects are private to a process. That is, only the process that created the GDI object can use the object handle.

Fixing or getting around that is going to be a bit of an uphill battle. The only solution that I know of is to inject code into the other application's process that first creates a DC in memory, then sends the WM_PRINT or WM_PRINTCLIENT message to a window owned by that process to draw into that in-memory device context, and then transfers the resulted bitmap back to your own application. This is going to require that you implement some type of inter-process communication mechanism.

I've seen some anecdotal evidence that passing device context handles between processes via the WM_PRINT and WM_PRINTCLIENT messages "works", but it's unclear to me whether this is an artifact of the current implementation (and therefore subject to breaking in future versions of Windows), or if this is because Windows is actually handling the marshaling between processes. I haven't seen any documentation one way or the other. If this is a one-off project you're doing for fun or for a limited use, you might try it and get away with it. For other purposes, you probably want to investigate using IPC to really do this the right way.

1
votes

Don't use GetDC for the DC to pass to PrintWindow. You need to create a compatible DC as you're doing (though you can pass it NULL to get a generic screen DC), then create a compatible bitmap the size of the window you're trying to capture and select it into the DC. Then pass that DC handle to PrintWindow.

Windows aren't required to respond properly to WM_PRINT or WM_PRINTCLIENT, so there may be some glitches even when you get this to work.