程式碼
介紹:
這裡描述的是一個Windows Mobile 的案頭遠端控制程式。用這個程式,你可以通過鍵盤和滑鼠遠端控制你的Windows Mobile裝置。
背景:
代碼實在我以前的文章類曾經提到的:一個遠端Windows Mobile螢幕截取工具。除了調用RAPI,該軟體可以通過一個RAPI服務流,允許傳統型程式建立裝置之間不間斷的串連。當然,在該程式中。我放棄了GAPI和DirectDraw的螢幕截取方式,使用了一個簡單的基於GDI螢幕截取方式。為了曾強通訊能力,代碼在串連兩端使用了ZLIB的壓縮庫。
案頭代碼:
請在解壓該程式前建立一個CeRemoteClient的目錄來儲存案頭項目。
傳統型程式代碼的大部分包含在CeRemoteClientView.h中,事實上WTL 8.0架構子視窗實現了所有案頭用戶端的功能。
裝置的串連通過Connect() 和 Disconnect()方法實現。在菜單和工具條中可以使用該命令,這些都包含在主架構視窗類別中(MainFrm.h)。當案頭成功的串連到裝置,一個200微秒的時鐘被建立,用來壓縮螢幕位元影像。
螢幕裝置是由GetScreen()方法控制的。它先關掉計時器,然後發送資訊到服務裝置請求當前螢幕圖象:
KillTimer(SCREEN_TIMER);
hr = Write(RCM_GETSCREEN);
服務裝置返回一個資訊包含同樣的資訊代碼,螢幕緩衝的壓縮大小,然後解壓流資料。在讀取類三個字大小的資料後,代碼判斷壓縮和解壓緩衝的空間是否夠用,然後讀取壓縮的資料流:
//讀取壓縮緩衝區
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在伺服器上使用的原因。
現在注意,讓我們進一步看一下伺服器裝置的代碼。
裝置代碼:
請建立裝置項目的CeRemSrv路徑。
大部分裝置代碼是CRemoteControl類中執行的。所有由案頭客戶發送的資訊在迴圈啟動並執行方法中被處理。
裝置螢幕可以被SendScreen方法撲捉。它有一個和案頭副本很相似的結構。看一下螢幕裝置是怎麼被方便的撲捉的:
hDC = GetWindowDC(NULL);
當獲得了裝置螢幕的HDC後,你可以非常容易的拷貝到一個位元影像檔案中並和案頭聯絡。這裡不需要在懷念 GAPI或DirectDraw技術:
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);
Handling input from the desktop is even simpler:
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裝置上工作?
雙擊必鬚髮送訊息到裝置。這樣是因為視窗將合并四個滑鼠事件(down - up - down - up)到一個單一的資訊WM_LBUTTONDBLCLK中。如果你看了My Code,你就回知道這是怎樣失敗的。
在WM5和WM6裝置中,你可能必須用RAPI的連續性,以便服務裝置DLL響應用戶端。我一次寫了一個簡單的裝置工具以解決著類複雜問題。你在這就能找到,拷貝EXE檔案到裝置上然後運行它。