Click here to Skip to main content
Click here to Skip to main content

A Remote Windows Mobile Screen Grabber

, 11 Feb 2008 CPOL
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

João Paulo Figueira
Software Developer Frotcom International
Portugal Portugal
I work on R&D for Frotcom International, a company that develops web-based fleet management solutions.
Follow on   Twitter   LinkedIn

Comments and Discussions

 
GeneralSQL Server DataBase Pinmembermahmoud ankeer8-May-09 5:33 
GeneralDirectDraw Pinmembermahmoud ankeer8-May-09 5:26 
GeneralCodecH263CE.lib could not be found Pinmembersaloni shah20-Mar-09 5:29 
QuestionBitmap screen capture format 16-&gt;24 ? PinmemberMember 281009228-Dec-08 14:02 
QuestionCannot capture when the video file is played. PinmemberVAIOnian Anold15-Sep-08 22:57 
AnswerRe: Cannot capture when the video file is played. PinmemberJoao Paulo Figueira15-Sep-08 23:59 
GeneralRe: Cannot capture when the video file is played. PinmemberVAIOnian Anold16-Sep-08 0:43 
QuestionRe: Cannot capture when the video file is played. PinmemberJoao Paulo Figueira16-Sep-08 1:25 
GeneralRe: Cannot capture when the video file is played. PinmemberVAIOnian Anold16-Sep-08 14:39 
GeneralRe: Cannot capture when the video file is played. PinmemberPSupriya5-Nov-09 0:53 
QuestionHow Can I capture hidden window with window handle? Pinmembersangyoon9310-Jul-08 16:41 
AnswerRe: How Can I capture hidden window with window handle? PinmemberJoao Paulo Figueira10-Jul-08 23:53 
GeneralWindows Mobile Remote Control PinmemberJoao Paulo Figueira25-Feb-08 2:12 
GeneralWindows CE 5.0 Pinmembernelson lopes18-Feb-08 8:37 
GeneralRe: Windows CE 5.0 PinmemberJoao Paulo Figueira18-Feb-08 9:36 
GeneralNice! Pinmembertrevor.hart14-Feb-08 14:27 
GeneralRe: Nice! PinmemberJoao Paulo Figueira14-Feb-08 23:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.1411019.1 | Last Updated 11 Feb 2008
Article Copyright 2008 by João Paulo Figueira
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid