65.9K
CodeProject 正在变化。 阅读更多。
Home

远程 Windows Mobile 屏幕抓取器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.68/5 (9投票s)

2008 年 2 月 11 日

CPOL

5分钟阅读

viewsIcon

90085

downloadIcon

918

通过 ActiveSync 或 WMDC 捕获你的 Windows Mobile 设备屏幕。

引言

本文介绍了一个远程 Windows Mobile 屏幕截图器的实现。根据目标平台的不同,设备屏幕可以使用 GAPI 或 DirectDraw 进行捕获。提供了一个示例桌面应用程序来显示捕获的位图,并将位图复制到剪贴板,以便可以在其他应用程序中使用。

背景

设备屏幕内容的捕获对我来说一直是个谜。利用我周末的一些闲暇时间,我决定解决这个问题,因为我的网络搜索一无所获。结果虽然不完整,但清楚地说明了两种可能的方法,并为你提供了一个免费的基于 RAPI 的工具,用于屏幕捕获会话。

Using the Code

屏幕捕获组件实现为一个 RAPI 扩展 DLL,并部署在目标设备上,通常在 \Windows 文件夹中。示例代码包括 Pocket PC 2002、2003、Windows Mobile 5(Pocket PC 和 SmartPhone)以及 Windows Mobile 6 Professional 的编译版本。

根据目标设备平台的不同,设备屏幕内容使用 GAPI(适用于 Windows CE 3.0 和 4.2)和 Direct Draw(适用于更高版本)进行捕获。

GAPI 屏幕捕获

使用 GAPI 捕获屏幕涉及获取屏幕帧缓冲区的地址并将其转换为 GDI 位图(DIB 段)。示例代码使用一个名为 CGC 的类来封装所有 GAPI 函数调用(它最初是为另一篇文章 [^] 开发的)。

屏幕捕获过程首先检索物理屏幕坐标

int nScreenX = GetSystemMetrics(SM_CXSCREEN),
    nScreenY = GetSystemMetrics(SM_CYSCREEN);

接下来,我们使用 gx.Open(NULL) 打开 GAPI 显示,并使用 gx.BeginDraw() 进入绘制模式。现在,我们可以访问屏幕上的原始像素数据,但必须将其存储在某处。我选择了 DIB 段,因为你可以轻松地对其进行序列化,这对于本项目很重要(我们必须以某种方式将位图数据发送到桌面)。

DIB 段代码摘自Chris Maunder 的文章评论 [^]。如你所见,Pocket PC 2002 版本(zip 中的 eVC3 文件夹)的实现略有不同,但这与本文无关。

用原始像素数据填充 DIB 段非常简单

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);
        }
    }
    // ...
}

现在,我们必须确保 DIB 段具有正确的颜色信息。我们从 GAPI 本身获取颜色位掩码信息

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);

最后,我们必须将 DIB 段数据封送回桌面

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;
}

正如你所看到的,此代码仅限于 16 位像素和纵向方向。不过,应该可以轻松地扩展以处理其他分辨率。

封送 DIB 段非常简单:只需分配一个足够大的缓冲区来容纳 DIBINFO 结构和位图本身。在桌面上,此数据流将被读回另一个 CDIBSection 类实例并显示(见下文)。

DirectDraw 屏幕捕获

DirectDraw 代码遵循与 GAPI 代码相同的原理,但不是手动将显示像素提取到 DIB 段中,而是使用 BitBlt API 将屏幕位图渲染到 DIB 段中。事实上,DirectDraw 曲面可以暴露一个 HDC 句柄,它作为 BitBlt 源效果非常好。

让我们从 DirectDraw 初始化开始回顾代码

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);

请注意,我使用的是 CComPtr 智能指针,而不是原始 COM 接口指针,因为它会自动管理底层 COM 接口的生命周期。

现在,我们必须确定我们的 DirectDraw 会话将如何与常规 GDI 应用程序协作

hr = spDD->SetCooperativeLevel(NULL, DDSCL_NORMAL);

此级别足以进行屏幕捕获。现在,我们必须获取主曲面(屏幕内存)的指针

CComPtr<IDirectDrawSurface> spSurface;

DDSURFACEDESC ddsd;
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;

hr = spDD->CreateSurface(&ddsd, &spSurface, NULL);

如果此调用成功,我们就拥有了主曲面的智能指针(spSurface),我们可以轻松地使用它将屏幕渲染到位图中。让我们看看如何做到这一点

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
    }
}

封送 DIB 段的代码与 GAPI 情况完全相同。

现在我们已经了解了如何捕获设备屏幕、将其打包到 DIB 段中,以及将其发送到桌面进行显示,现在我们可以查看桌面应用程序代码了。

桌面

桌面示例应用程序是一个 WTL 8.0 SDI 应用程序,具有非常简单的功能集:从设备请求屏幕位图、在可滚动窗格中显示它,并允许将位图复制到剪贴板以供其他应用程序使用。

所有相关的桌面代码都位于 CeScreenGrabView.h 文件中,该文件实现了 SDI 视图窗口。

首先,让我们看看如何从设备请求屏幕位图(请注意,此处未执行 RAPI 初始化和关闭)

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);
        }
    }
}

m_rapi 对象类型为 CRemoteAPI(参见 RemoteAPI.hRemoteAPI.cpp),并以与版本无关的方式封装了大多数 RAPI 函数调用。

初始化与设备的 RAPI 连接后,上述代码将动态调用 CeScreenCapture.dll 模块中的 CeScreenCapture 函数。请注意,为了使此代码正常工作,DLL 必须位于设备上的 \Windows 文件夹中。

返回后,pBuffer 包含序列化的 DIB 段,该段被重新组装到一个 CDIBSection 对象(m_dib)中,然后显示

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);
    }
}

再简单不过了……那么复制到剪贴板功能呢?在这里

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();
    }
}

如你所见,代码只是将现有的 DIB 段复制到一个 HBITMAP 并将其设置为剪贴板数据。当不再需要该位图时,剪贴板将释放它。

限制

此代码未经广泛测试,也未在大量设备上暴露。正如我上面所说,该代码仅限于具有 16 位像素的设备,尽管扩展到其他像素格式应该不难。

历史

  • 2008-02-11 - 首次发布。
© . All rights reserved.