Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Remote Windows Mobile Screen Grabber

0.00/5 (No votes)
11 Feb 2008 1  
Capture your Windows Mobile device screen via ActiveSync or WMDC.

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;    // RED bitmask   Bits: 0 11111 00000 00000
    dw[1] = 992;      // GREEN bitmask Bits: 0 00000 11111 00000
    dw[2] = 31;       // BLUE bitmask  Bits: 0 00000 00000 11111
}
else if(gx.GetDisplayFormat() & kfDirect565)
{
    dw[0] = 0xF800;  // RED bitmask   Bits: 11111 000000 00000
    dw[1] = 0x7E0;   // GREEN bitmask Bits: 00000 111111 00000
    dw[2] = 0x1F;    // BLUE bitmask  Bits: 00000 000000 11111
}

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;

// Get a pointer to the Direct Draw object
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))
    {
        // Marshal the DIB section
    }
}

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here