This is something that the original designers of Windows made difficult, as a byproduct of the way they did their drawing code. When a window receives a WM_PAINT message, it is supposed to go redraw all the parts of its window. The operating system will make sure to skip drawing whatever parts do not need drawing, just for speed.
To do drawing in Windows, a program has to have a "device context". That's the magic structure that tells, among other things, where drawing will be going (that is, to the screen, to a printer, to a bitmap, etc.). The drawing code doesn't care where it is drawing; the DC takes care of that.
So, if there were a way to trigger the WM_PAINT processing, but substitute a DC that pointed to a bitmap, you could fool a window into repainting itself onto a bitmap. If Microsoft had allowed the DC to be used to be specified with the WM_PAINT message, it would be easy. However, they didn't do that. Instead, there is a pair of APIs called BeginPaint and EndPaint that are used for exactly this purpose, and no other: inside a WM_PAINT message, BeginPaint returns to the app the DC that it should use for drawing. That means I cannot substitute my own DC unless I intercept the application's calls to BeginPaint and EndPaint.
Microsoft realized this "flaw", but too late. They recently added a WM_PRINT message, which is the same as WM_PAINT, except that it accepts a DC as one of the message parameters. That would be great if applications obeyed it, but they don't. The standard Windows controls all honor WM_PRINT, but that's all. Applications ignore it. Plus, you cannot send WM_PRINT to a different process; an application can only ask it of itself.
Today, I managed to get this all mostly working. It's quite a hack job.
I started with some old code by
Here's the basics of what I tried. I have a DLL. I call an entry point
in that DLL which installed a WH_CALLWNDPROC window hook. This means
that my hook routine will get called every time any window receives a
message. The cool thing about this is that, in order to do so, my DLL
has to be running as a part of those processes. The operating system
basically "injects" my DLL into all other processes.
My hook lies in wait until it sees a special message that says "I want
a snapshot of your window." The hook then patches BeginPaint and EndPaint
on the fly in this remote process,
so that they jump into code in my DLL. I then send a WM_PRINT
to the top-level window. WM_PRINT draws all of the window decorations
(borders, title bars, menus), and then sends a WM_PRINTCLIENT message to
all of the child windows of the application. This message eventually
makes its way to my WH_CALLWNDPROC hook again. At that point, I send
a WM_PAINT to the window instead. When the window calls BeginPaint, it
triggers my patch, and receives MY DC. When the capture is done, I
unpatch BeginPaint and EndPaint, and continue on.
This does some things quite well. I can always capture the window decorations
and the standard Windows controls (and remember, this works even if the window
is completely hidden by other windows). However, for many apps, the main
window area always comes back blank. This turns out to be because those
apps simply violate the rules.
Gvim is one example. I get everything except the text being edited.
It turns out that gvim does not use the BeginPaint DC to do its drawing.
It creates its own DC when it creates the text box, and uses that DC to
respond to WM_PAINT. It's doing the painting, but it's not giving it
to me.
Word is another example. I get everything except the text of the document.
I have not traced through it yet, but I suspect it will be the exact
same explanation: a CS_OWNDC.
So, I have code which does exactly what I wanted it to do, and it is
almost worthless.