远程控制Windows Mobile设备的桌面应用

在现代的移动设备使用中,远程控制功能变得越来越重要。本文将介绍如何开发一个桌面应用,通过该应用可以远程控制Windows Mobile设备。将使用鼠标和键盘来操作设备,实现远程控制。

本文的代码基于之前的文章《远程Windows Mobile屏幕抓取器》。与之前不同的是,这个应用实现了一个流式RAPI服务器,允许桌面应用与设备保持永久连接。此外,本文中放弃了GAPI和DirectDraw屏幕抓取技术,转而使用更简单的基于GDI的屏幕抓取技术。为了提高通信性能,代码在两端都使用了ZLIB压缩库。

桌面端代码

桌面端的代码主要集中在CeRemoteClientView.h文件中。这是一个WTL 8.0框架子窗口,实现了所有桌面客户端功能。

设备连接是通过公共方法Connect()Disconnect()实现的,这些方法由框架窗口类的菜单和工具栏处理程序调用。当桌面成功连接到设备时,会创建一个200毫秒的定时器,用于轮询设备的压缩屏幕位图。

私有方法GetScreen()用于获取设备屏幕。首先停止定时器,然后向设备服务器发送消息请求当前屏幕:

KillTimer(SCREEN_TIMER); hr = Write(RCM_GETSCREEN);

设备服务器返回的消息包含相同的消息代码、屏幕缓冲区的压缩大小、展开大小和压缩字节流。读取前三个DWORD后,代码确保压缩和展开缓冲区有足够的空间,然后读取压缩字节流:

hr = m_pStream->Read(m_pZipBuf, cbZipBuf, &ulRead);

如果一切顺利,压缩缓冲区将被解压缩,生成的DIB将查询位图尺寸。如果尺寸与上次不同,那么很可能设备屏幕被旋转了,因此整个窗口将被无效化以清除任何垃圾:

zr = uncompress(m_pScrBuf, &nDestLen, m_pZipBuf, cbZipBuf); if (zr == Z_OK) { DIBINFO* pDibInfo = (DIBINFO*)m_pScrBuf; BYTE* pBmpData = (BYTE*)(m_pScrBuf + sizeof(DIBINFO)); BOOL bErase = FALSE; if (m_xDevScr != pDibInfo->bmiHeader.biWidth || m_yDevScr != pDibInfo->bmiHeader.biHeight) { m_xDevScr = pDibInfo->bmiHeader.biWidth; m_yDevScr = pDibInfo->bmiHeader.biHeight; SetScrollSize(m_xDevScr, m_yDevScr); bErase = TRUE; } m_dib.SetBitmap((BITMAPINFO*)pDibInfo, pBmpData); InvalidateRect(NULL, bErase); UpdateWindow(); }

在强制窗口更新后,定时器重新启动,以便可以获取下一个屏幕。

向设备发送键盘和鼠标输入非常简单:处理相应的窗口消息,将它们的内容转换为INPUT结构,并将它们发送到服务器进行处理。以下是WM_KEYDOWN处理程序:

LRESULT OnKeyDown(TCHAR vk, UINT cRepeat, UINT flags) { HRESULT hr; INPUT input; if (!m_bConnected) return 0; input.type = INPUT_KEYBOARD; input.ki.wVk = MapKey(vk); input.ki.wScan = 0; input.ki.dwFlags = 0; input.ki.time = 0; input.ki.dwExtraInfo = 0; hr = Write(RCM_SETINPUT); if (SUCCEEDED(hr)) { hr = Write(&input, sizeof(input)); if (SUCCEEDED(hr)) GetScreen(); } return 0; }

MapKey()函数执行桌面和设备键盘之间的基本键位映射。使用F1键作为左功能按钮,F2键作为右功能按钮。F3和F4键自然映射到电话键。

发送鼠标动作类似:

LRESULT OnLButtonDown(UINT Flags, CPoint pt) { HRESULT hr; INPUT input; if (!m_bConnected) return 0; m_bLeftBtn = true; input.type = INPUT_MOUSE; input.mi.dwFlags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_LEFTDOWN; input.mi.dx = pt.x * 65536 / m_xDevScr; input.mi.dy = pt.y * 65536 / m_yDevScr; input.mi.mouseData = 0; input.mi.time = 0; input.mi.dwExtraInfo = 0; hr = Write(RCM_SETINPUT); if (SUCCEEDED(hr)) { hr = Write(&input, sizeof(input)); if (SUCCEEDED(hr)) hr = GetScreen(); } return 0; }

注意鼠标屏幕坐标是如何为设备屏幕标准化的。这是设备服务器上使用的SendInput API的要求。

设备端代码

设备端的代码主要集中在CRemoteControl类中。所有由桌面客户端发送的消息都在Run方法中实现的执行循环中处理和分派。

设备屏幕的捕获由SendScreen方法完成,其结构与桌面端类似。注意设备屏幕是如何轻松被捕获的:

hDC = GetWindowDC(NULL);

获取设备屏幕的HDC后,可以很容易地将其复制到位图中,并序列化到桌面。无需使用之前使用的GAPI或DirectDraw技术。

将设备屏幕复制到DIB后,整个内容被压缩并发送回桌面客户端:

memcpy(m_pScrBuf + i, m_dib.GetBitmapInfo(), sizeof(DIBINFO)); i += sizeof(DIBINFO); memcpy(m_pScrBuf + i, m_dib.GetDIBits(), m_dib.GetImageSize()); i += m_dib.GetImageSize(); ULONG len = m_cbZipBuf; int zr = compress(m_pZipBuf, &len, m_pScrBuf, cbNew); if (zr != Z_OK) len = 0; hr = m_pStream->Write(&dwMsg, sizeof(DWORD), &ulWritten); hr = m_pStream->Write(&len, sizeof(ULONG), &ulWritten); hr = m_pStream->Write(&cbNew, sizeof(DWORD), &ulWritten); hr = m_pStream->Write(m_pZipBuf, len, &ulWritten);

处理桌面输入非常简单:

HRESULT CRemoteControl::GetInput() { INPUT input; HRESULT hr; DWORD dwRead; hr = m_pStream->Read(&input, sizeof(input), &dwRead); if (FAILED(hr)) return hr; if (dwRead != sizeof(input)) return E_FAIL; SendInput(1, &input, sizeof(input)); return S_OK; }

注意事项

有两个有趣的点可能想知道:是如何模拟双击鼠标事件的,以及为什么这段代码在WM5和WM6设备上不能直接工作。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485