Introduction
This article describes the implementation of a remote Windows Mobile screen grabber. Depending on the target platform, the device screen is captured using either GAPI or DirectDraw. A sample desktop application is provided to display the captured bitmap and to copy the bitmap to the Clipboard so it can be used in other applications.
Background
Capturing the device screen contents has always been a mystery to me. Using some of my weekend's idle time, I decided to have a go at this problem when my internet searches turned up nothing. The result, although clearly not complete, does illustrate two possible approaches, and provides you with a free RAPI-based tool for your screen capture sessions.
Using the Code
The screen capturing component is implemented as a RAPI extension DLL, and deployed on the target device, typically in the \Windows folder. The sample code includes compiled versions for Pocket PC 2002, 2003, Windows Mobile 5 (Pocket PC and SmartPhone), and Windows Mobile 6 Professional.
Depending on the target device platform, the device screen contents are captured using GAPI for Windows CE 3.0 and 4.2, and Direct Draw for later versions.
GAPI Screen Capture
Capturing the screen with GAPI involves getting the address of the screen's frame buffer and converting it to a GDI bitmap (a DIB section). The sample code uses a class named CGC
to encapsulate all the GAPI function calls (it was originally developed for another article [^]).
The screen capture process starts by retrieving the physical screen coordinates:
int nScreenX = GetSystemMetrics(SM_CXSCREEN),
nScreenY = GetSystemMetrics(SM_CYSCREEN);
Next, we open the GAPI display with gx.Open(NULL)
, and put it in drawing mode with gx.BeginDraw()
. Now, we can access the raw pixel data on the screen, but we must store it somewhere. I chose a DIB section because you can easily serialize it, which is an important feature for this project (we must send the bitmap data to the desktop somehow).
The DIB section code was taken from Chris Maunder's article comments [^]. As you can see, the implementation differs a little bit on the Pocket PC 2002 version (eVC3 folder on the zip), but that is not relevant for this article.
Filling in the DIB section with the raw pixel data is quite straightforward:
CDIBSection dib;
if(dib.CreateBitmap(nScreenX, nScreenY, (int)gx.GetBitsPerPixel()))
{
int x, y;
WORD* pBits = (WORD*)dib.GetDIBits();
for(y = nScreenY - 1; y >= 0; --y)
{
for(x = 0; x < nScreenX; ++x)
{
*pBits++ = gx.GetPixel(x, y);
}
}
}
Now, we must make sure that the DIB section has the correct color information. We get the color bitmask information from GAPI itself:
DWORD dwSize;
BYTE* pBuffer;
BITMAPINFO* pInfo = dib.GetBitmapInfo();
pInfo->bmiHeader.biCompression = BI_BITFIELDS;
DWORD dw[3];
if(gx.GetDisplayFormat() & kfDirect555)
{
dw[0] = 31744; dw[1] = 992; dw[2] = 31; }
else if(gx.GetDisplayFormat() & kfDirect565)
{
dw[0] = 0xF800; dw[1] = 0x7E0; dw[2] = 0x1F; }
CopyMemory(dib.GetColorTable(), dw, sizeof(DWORD) * 3);
Finally, we must marshal the DIB section data back to the desktop:
dwSize = sizeof(DIBINFO) + dib.GetImageSize();
pBuffer = (BYTE*)LocalAlloc(LPTR, dwSize);
if(pBuffer)
{
memcpy(pBuffer, dib.GetBitmapInfo(), sizeof(DIBINFO));
memcpy(pBuffer + sizeof(DIBINFO), dib.GetDIBits(), dib.GetImageSize());
*pcbOutput = dwSize;
*ppOutput = pBuffer;
}
As you can see, this code is limited to 16 bit pixels and portrait orientation. It should be easily extended to cope with other resolutions, though.
Marshalling the DIB section is as simple as it gets: just allocate a buffer large enough to hold the DIBINFO
structure and the bitmap itself. On the desktop, this data stream will be read back into another instance of a CDIBSection
class and displayed (see below).
DirectDraw Screen Capture
The DirectDraw code follows the same principles as the GAPI code, but instead of manually ripping the display pixels into a DIB section, the screen bitmap is rendered into the DIB section using the BitBlt
API. As a matter of fact, a DirectDraw surface can expose an HDC
handle, and it works beautifully as a BitBlt
source.
Let's review the code, starting with the DirectDraw initialization:
int nScreenX = GetSystemMetrics(SM_CXSCREEN),
nScreenY = GetSystemMetrics(SM_CYSCREEN);
CComPtr<IDirectDraw> spDD;
hr = DirectDrawCreate(NULL, &spDD, NULL);
Note that instead of a raw COM interface pointer, I am using the CComPtr
smart pointer because it automatically manages the underlying COM interface life cycle.
Now, we must establish how our DirectDraw session will cooperate with the regular GDI application:
hr = spDD->SetCooperativeLevel(NULL, DDSCL_NORMAL);
This level is enough for screen capturing. Now, we have to get a pointer to the primary surface (the screen memory):
CComPtr<IDirectDrawSurface> spSurface;
DDSURFACEDESC ddsd;
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
hr = spDD->CreateSurface(&ddsd, &spSurface, NULL);
If this call succeeds, we now have a smart pointer (spSurface
) to the primary surface with which we can easily render the screen into a bitmap. Let's see how to do it:
CDIBSection dib;
HDC hCaptureDC = CreateCompatibleDC(hDC);
dib.CreateBitmap(nScreenX, nScreenY, 16);
if(dib.GetSafeHandle() != NULL)
{
SelectObject(hCaptureDC, dib.GetSafeHandle());
if(BitBlt(hCaptureDC, 0, 0, nScreenX, nScreenY, hDC, 0, 0, SRCCOPY))
{
}
}
The code to marshal the DIB section is exactly the same as in the GAPI case.
Now that we have seen how to capture the device screen, package it in a DIB section, and send it over to the desktop for display, we can now look at the desktop application code.
Desktop
The desktop sample application is a WTL 8.0 SDI application with a very simple set of features: requests the screen bitmap from the device, displays it in a scrollable pane, and allows copying the bitmap to the Clipboard for use in other applications.
All the relevant desktop code lives in the CeScreenGrabView.h file, where the SDI view window is implemented.
First, let's see how to ask for a screen bitmap from the device (please note that RAPI initialization and shutdown are not performed here):
void GrabCeScreen()
{
if(m_bConnect)
{
DWORD dwOutput;
BYTE* pBuffer;
HRESULT hr;
hr = m_rapi.Invoke(_T("CeScreenCapture.dll"),
_T("CeScreenCapture"), 0, NULL,
&dwOutput, &pBuffer, NULL, 0);
if(SUCCEEDED(hr))
{
if(dwOutput > sizeof(DIBINFO))
{
BITMAPINFO* pBitmap = (DIBINFO*)pBuffer;
BYTE* pBits = pBuffer + sizeof(DIBINFO);
SetScrollSize(pBitmap->bmiHeader.biWidth,
pBitmap->bmiHeader.biHeight);
m_dib.SetBitmap(pBitmap, pBits);
Invalidate();
UpdateWindow();
}
m_rapi.FreeBuffer(pBuffer);
}
}
}
The m_rapi
object is of type CRemoteAPI
(see RemoteAPI.h and RemoteAPI.cpp), and wraps most of the RAPI function calls in a version-independent fashion.
After initializing the RAPI connection to the device, the above code dynamically calls the CeScreenCapture
function in the CeScreenCapture.dll module. Note that the DLL must reside in the device's \Windows folder in order for this code to work.
Upon return, the pBuffer
contains the serialized DIB section which is reassembled into a CDIBSection
object (m_dib
) and then displayed:
void DoPaint(CDCHandle dc)
{
HBITMAP hBmp = m_dib;
if(hBmp)
{
CDC dcMem;
dcMem.CreateCompatibleDC(dc);
dcMem.SelectBitmap(hBmp);
dc.BitBlt(0, 0, m_dib.GetWidth(), m_dib.GetHeight(),
dcMem, 0, 0, SRCCOPY);
}
}
Couldn't be simpler... What about the copy to Clipboard feature? Here it is:
void CopyToClipboard()
{
if(m_dib.GetSafeHandle() == NULL)
return;
if(OpenClipboard())
{
CDC dcMemSrc,
dcMemTgt;
HDC hDC = GetDC();
HBITMAP hBmp;
dcMemSrc.CreateCompatibleDC(hDC);
dcMemTgt.CreateCompatibleDC(hDC);
hBmp = CreateCompatibleBitmap(hDC, m_dib.GetWidth(),
m_dib.GetHeight());
dcMemSrc.SelectBitmap(m_dib.GetSafeHandle());
dcMemTgt.SelectBitmap(hBmp);
dcMemTgt.BitBlt(0, 0, m_dib.GetWidth(), m_dib.GetHeight(),
dcMemSrc, 0, 0, SRCCOPY);
EmptyClipboard();
SetClipboardData(CF_BITMAP, hBmp);
CloseClipboard();
}
}
As you can see, the code merely copies the existing DIB section to an HBITMAP
and sets it as the Clipboard data. The Clipboard will dispose off the bitmap when it is no longer needed.
Limitations
This code has not been widely tested nor exposed to a large number of devices. As I stated above, the code is limited to devices with 16-bit pixels, although this should not be very hard to extend to other pixel formats.
History
- 2008-02-11 - First publication.