Tim's Web Log #3
Thoughts and opinions of an opinionated person

Tue, 08 Nov 2005

Hidden Window Capture
This week, I had an opportunity to look in depth at the problem of capturing the contents of a Windows window, even when that window is partially or totally covered.

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 Feng Yuan that shows how to support WM_PRINT in your own applications.

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.


# From Emmanuel at Thu Mar 16 12:28:51 2006:
I stumbled upon your blog while trying to achieve the same thing. Would you mind sharing your code with me, even though it does not work in all cases?

# From Paul Caton at Fri Apr 28 15:39:40 2006:
My problem with Yuan's code is that it frequently crashes the captured app after capture. I presume it isn't cleaning up its patch properly... but I don't know. Any ideas?

# From Tim Roberts at Fri Apr 28 16:26:47 2006:
Hmmm; I haven't seen it crash, although I haven't used it very much.  It does clean up during EndPaint.  I suppose if an application was ill-behaved and didn't call EndPaint, you might see this.

Is there one particular app that crashes?

# From James Moss at Thu May 10 04:25:44 2007:
I know its been a while since this was written but im investigating other ways of capturing a windows contents without using bitblt.

Would this method work with an openGL or DirectX window? (im trying to capture a game).

# From Tim Roberts at Thu May 10 09:37:53 2007:
No, this captures GDI calls only, not OpenGL or DirectX.

There is no general way to do what you want.  That's partly by design; game screen designs are intellectual property.  You can try using DirectDraw to fetch a pointer to the primary surface and read the bytes directly, but in many cases in OGL and D3D, the image you see on the monitor doesn't actually exist anywhere in memory.  It is composed on the fly during refresh.

This problem becomes even more severe in Vista; with Aero, all windows are drawn into offscreen textures and composed at refresh time.

# From abiggerdog at Tue Jan 29 08:39:21 2008:
but don't give up now! you can hook all of the GDI functions ... TextOut, BitBlt, etc, hook'em all!  and then, during the WM_PAINT, if the app isn't using the DC returned by BeginPaint, do the operation twice, once for the DC submitted by the app, once for the DC it "should" be using, the one returned by BeginPaint.  I would think this method would have to work, UNLESS the app uses some method other than GDI to set the pixels, say DirectX.

there is a technique for doing this kind of hooking that is general, that is it doesn't require any knowledge of the entry code for the routine.  basically you alter the routine so it immediately jumps to your new one, in your new one you rewrite back to the original routine the code that was overwritten, call it as needed, and on exit from yoru new one you finally rewrite that jmp statement ... basically.

# From ugg boots sale at Mon Sep 6 21:47:46 2010:
Am pleased to come to you, I am a new member of Oh just arrived. Hope you can give more concern. Thank you.


Name:


E-mail:


URL:


Comment:


Please enter "blog" here (without the quotes) so I know you are human and not a spam script:


About Me
E-mail Tim
Work info
Personal info
My big dog!
My little dog!
RSS feed

Archives
2010-Jan
2008-Nov
2008-Feb
2007-Oct
2007-Sep
2007-Jul
2007-May
2007-Feb
2006-Oct
2006-Sep
2006-Aug
2006-Jun
2006-May
2006-Apr
2006-Mar
2006-Feb
2006-Jan
2005-Nov
2005-Oct
2005-Sep
2005-Aug
2005-Jul
2005-Jun
2005-May
2005-Apr
2005-Mar
2005-Jan
2004-Dec
2004-Nov
2004-Aug
2004-Jul
2004-Jun
2004-Apr
2004-Feb
2004-Jan
2003-Dec
2003-Nov
2003-Oct
2003-Sep
2003-Aug
2003-Jul
2003-Jun
2003-May
2003-Mar
2003-Feb
2003-Jan
2002-Dec

Categories


Web Sites
P&B company site
Python language site