RealVNC原始碼學習筆記 一
RealVNC原始碼學習筆記 一
1、VNC簡介
VNC是一款優秀的遠端控制軟體,其英文全拼為 Virtual NetworkComputer。VNC 是在基於UNIX和Linux作業系統的免費的開源軟體。 目前VNC不僅僅支援UNIX、Linux系統,其是一款跨平臺的開源軟體,處理支援UNIX、Linux系統外,還支援WINDOWS、MAC、Solaris、HP-UX等作業系統。
VNC運用RFB協議,RFB為remoteframe buffer 遠端幀快取的簡寫,是一個遠端圖形使用者的簡單協議,因為它工作在幀快取級別上,所以它可以應用於所有的視窗系統,例如:X11,Windows
2、VNC原始碼分析
以windows系統的RealVNC的原始碼為基礎來分析VNC在windows平臺的具體
實現。vnc-4_1_3-winsrc目錄下有兩個目錄common和win目錄,其中common目錄下放著各個平臺公用的模組,win目錄下放著為windows系統特的開發的模組。其目錄結構如下圖:
common目錄下的network
win目錄下的logmessages為訊息日誌模組;rfb_win32為common目錄下rfb模組的windows化,該模組下的很多類與rfb下的類同名,或繼承自rfb下的類以實現某些類的
RealVNC是用C++語言開發的,其中大量運用了名字空間,可以說其是對c++名稱空間運用的典範。如rdr下的所有類、全域性變數、函式等多包含在該rdr名稱空間下,在原始碼中可以發現很多using namespace rdr、using namespace rfb、using namespace win32的地方。RealVNC的windows原始碼運用了訊息和事件處驅動機制,有訊息、事件的到來、觸發來驅動VNC伺服器來完成各種任務。在RealVNC伺服器端主要可以分為兩條訊息、事件主線:windows訊息、網路套接字事件。後面將這兩個主線為基礎來分析RealVNC的windows原始碼。RealVNC在windows平臺上截圖的實現運用了hook技術。先來看一下wm_hook.dll
3、wm_hook全域性鉤子dll的分析
在wm_hook中安裝了攔截系統SendMessage和PostMessage訊息的鉤子,但對攔截到的訊息並不做處理,而是將其轉化為自定義的系統訊息並將轉換得到的訊息傳送給指定的執行緒,由該執行緒對相應的訊息做出處理。
在wm_hook中windows APIRegisterWindowMessage函式註冊了自定義訊息,這些訊息反映了桌面的改變情況,如UINTWM_HK_WindowClientAreaChanged =RegisterWindowMessage(_T("RFB.WM_Hooks.WindowClientAreaChanged"))等。接下來,來看一下hook dll的處理過程。
首先在WM_Hooks_Install(DWORDowner, DWORD thread)函式中安裝了四類hook,並未四類hook分別設定了回撥函式,在各個回撥函式中呼叫ProcessWindowMessage(UINTmsg, HWND wnd, WPARAM wParam, LPARAM lParam)函式對截獲的系統訊息進行處理,在ProcessWindowMessage函式中根據訊息的型別呼叫不同的函式將對應的自定義訊息傳送給hook的擁有者執行緒,當擁有者執行緒得到訊息進行處理(獲得更新的區域,在接到客戶端訊息後就將更新區域的桌面影象編碼傳送給客戶端)
4、wm_hook全域性dll的呼叫
上層應用通過WHooks.cxx中的全域性靜態函式StartHookThread()載入wm_hook.dll,並建立開始hook執行緒:
static bool StartHookThread() {
//WMHooksThread* hook_mgr
//執行緒已開啟返回true
if (hook_mgr)
return true;
vlog.debug("creatingthread");
//建立一個新的hook執行緒 WMHooksThread繼承自Thread,在此建構函式中會載入wm_hook.dll
hook_mgr = new WMHooksThread();
//判斷wm_hook.dll是否載入成功
if(!hook_mgr->WM_Hooks_Install.isValid() ||
!hook_mgr->WM_Hooks_Remove.isValid()) {
vlog.debug("hooks notavailable");
return false;
}
vlog.debug("installinghooks");
//安裝wm_hook 監視桌面的所有執行緒 第二個引數為0 表示監視一切執行緒,在該函式中呼叫了//SetWindowsHookEx函式安裝了四類hook
if(!(*hook_mgr->WM_Hooks_Install)(hook_mgr->getThreadId(), 0)) {
vlog.error("failed toinitialise hooks");
delete hook_mgr->join();
hook_mgr = 0;
return false;
}
vlog.debug("startingthread");
//開始執行緒,執行WMHooksThread類的run函式
hook_mgr->start();
return true;
}
其中hook_mgr為WHooks.cxx中定義的WMHooksThread*型全域性變數,WMHooksThread的父類Thread為rfb_win32模組實現的一個執行緒基類。該基類建立的執行緒是一個狀態為SUSPEND的懸掛執行緒,執行緒建立後並不立即執行,而是懸掛在那裡,只到呼叫ResumeThread函式後,執行緒才開始執行,ResumeThread函式的呼叫在Thread類的start()成員函式中。因此在構造執行緒類之後,只有呼叫了該類族的start()函式,該執行緒才會啟動,線上程函式中將會呼叫該類的run函式,也就是執行緒啟動後將呼叫執行緒類的run函式。下面來看一下WMHooksThread類的run函式:
//執行緒函式,線上程函式中響應底層hook.dll截獲轉發的訊息
void
WMHooksThread::run() {
//DynamicFn是一個模板類,用來獲取指定dll中指定函式名的函式的指標
DynamicFn<WM_Hooks_WMVAL_proto> WM_Hooks_WindowChanged(_T("wm_hooks.dll"),"WM_Hooks_WindowChanged");
DynamicFn<WM_Hooks_WMVAL_proto>WM_Hooks_WindowBorderChanged(_T("wm_hooks.dll"),"WM_Hooks_WindowBorderChanged");
DynamicFn<WM_Hooks_WMVAL_proto>WM_Hooks_WindowClientAreaChanged(_T("wm_hooks.dll"),"WM_Hooks_WindowClientAreaChanged");
DynamicFn<WM_Hooks_WMVAL_proto>WM_Hooks_RectangleChanged(_T("wm_hooks.dll"),"WM_Hooks_RectangleChanged");
DynamicFn<WM_Hooks_WMVAL_proto>WM_Hooks_CursorChanged(_T("wm_hooks.dll"),"WM_Hooks_CursorChanged");
//獲得dll中所註冊的訊息ID GetMsgVal中呼叫以上獲得的函式指標,獲得對應的註冊的訊息//的ID
UINT windowMsg =GetMsgVal(WM_Hooks_WindowChanged);
UINT clientAreaMsg =GetMsgVal(WM_Hooks_WindowClientAreaChanged);
UINT borderMsg =GetMsgVal(WM_Hooks_WindowBorderChanged);
UINT rectangleMsg =GetMsgVal(WM_Hooks_RectangleChanged);
UINT cursorMsg =GetMsgVal(WM_Hooks_CursorChanged);
#ifdef _DEBUG
DynamicFn<WM_Hooks_WMVAL_proto>WM_Hooks_Diagnostic(_T("wm_hooks.dll"),"WM_Hooks_Diagnostic");
UINT diagnosticMsg =GetMsgVal(WM_Hooks_Diagnostic);
#endif
//變數定義
MSG msg;
RECT wrect;
HWND hwnd;
int count = 0;
// Update delay handling
//We delay updates by 40-80ms, so that thetriggering application has time to
//actually complete them before we notify thehook callbacks & they go off
//capturing screen state.
const int updateDelayMs = 40;
//建立一個視窗
MsgWindowupdateDelayWnd(_T("WMHooks::updateDelay"));
//建立一個定時器,IntervalTimer類中呼叫SetTimer實現定時
IntervalTimerupdateDelayTimer(updateDelayWnd.getHandle(), 1);
//顯示區域
Region updates[2];
int activeRgn = 0;
vlog.debug("starting hookthread");
//響應hook.dll中傳送的訊息 呼叫GetMessage來獲得訊息
while (active &&GetMessage(&msg, NULL, 0, 0)) {
count++;
//定時器訊息,該訊息有IntervalTimer類定時到時發出
if (msg.message == WM_TIMER) {
// Actually notify callbacksof graphical updates
//通知hook類,將獲取的新區域新增到更新區域中,並觸發hook類的事件物件為有訊號
NotifyHooksRegion(updates[1-activeRgn]);
if(updates[activeRgn].is_empty())
updateDelayTimer.stop();
activeRgn = 1-activeRgn;
updates[activeRgn].clear();
} else if (msg.message ==windowMsg) {
// An entire window has(potentially) changed
hwnd = (HWND) msg.lParam;
if (IsWindow(hwnd) &&IsWindowVisible(hwnd) && !IsIconic(hwnd) &&
GetWindowRect(hwnd,&wrect) && !IsRectEmpty(&wrect)) {
//在此獲得視窗區域,並開始定時,定時到時將此區域加入到改變的區域中
updates[activeRgn].assign_union(Rect(wrect.left,wrect.top,
wrect.right, wrect.bottom));
updateDelayTimer.start(updateDelayMs);
}
} else if (msg.message ==clientAreaMsg) {
// The client area of a windowhas (potentially) changed
hwnd = (HWND) msg.lParam;
/在此獲得客戶區域,並開始定時,定時到時將此區域加入到改變的區域中
if (IsWindow(hwnd) &&IsWindowVisible(hwnd) && !IsIconic(hwnd) &&
GetClientRect(hwnd,&wrect) && !IsRectEmpty(&wrect))
{
POINT pt = {0,0};
//進行座標轉換,將客戶區座標轉換為全屏座標
if (ClientToScreen(hwnd,&pt)) {
updates[activeRgn].assign_union(Rect(wrect.left+pt.x, wrect.top+pt.y,
wrect.right+pt.x, wrect.bottom+pt.y));
updateDelayTimer.start(updateDelayMs);
}
}
} else if (msg.message ==borderMsg) {
hwnd = (HWND) msg.lParam;
if (IsWindow(hwnd) &&IsWindowVisible(hwnd) && !IsIconic(hwnd) &&
GetWindowRect(hwnd,&wrect) && !IsRectEmpty(&wrect))
{
Regionchanged(Rect(wrect.left, wrect.top, wrect.right, wrect.bottom));
RECT crect;
POINT pt = {0,0};
if (GetClientRect(hwnd,&crect) && ClientToScreen(hwnd, &pt) &&
!IsRectEmpty(&crect))
{
changed.assign_subtract(Rect(crect.left+pt.x, crect.top+pt.y,
crect.right+pt.x, crect.bottom+pt.y));
}
if (!changed.is_empty()) {
updates[activeRgn].assign_union(changed);
updateDelayTimer.start(updateDelayMs);
}
}
} else if (msg.message ==rectangleMsg) {
Rect r = Rect(LOWORD(msg.wParam),HIWORD(msg.wParam),
LOWORD(msg.lParam), HIWORD(msg.lParam));
if (!r.is_empty()) {
updates[activeRgn].assign_union(r);
updateDelayTimer.start(updateDelayMs);
}
} else if (msg.message == cursorMsg){
NotifyHooksCursor((HCURSOR)msg.lParam);
#ifdef _DEBUG
} else if (msg.message ==diagnosticMsg) {
vlog.info("DIAGmsg=%x(%d) wnd=%lx", msg.wParam, msg.wParam, msg.lParam);
#endif
}
}
vlog.debug("stopping hookthread - processed %d events", count);
(*WM_Hooks_Remove)(getThreadId());
}
在該run函式中利用while迴圈,處理wm_hook.dll傳送的各種訊息,其中while迴圈中的if語句中對訊息的判斷值都是wm_hook.dll中註冊的訊息ID。這裡需要重點理解的是while迴圈中第一個if語句中的NotifyHooksRegion(updates[1-activeRgn]),該函式遍歷hook物件佇列,並呼叫佇列中每一個hook物件的NotifyHooksRegion函式,來新增更新區域和觸發事件物件:
//hooks為WHooks.cxx中定義的WMHooks類的物件佇列std::list<WMHooks*> hooks;
//std::list<WMCursorHooks*> cursor_hooks; 滑鼠hook類
//Mutex hook_mgr_lock;
static void NotifyHooksRegion(const Region& r) {
Lock l(hook_mgr_lock);
std::list<WMHooks*>::iteratori;
for (i=hooks.begin();i!=hooks.end(); i++)
(*i)->NotifyHooksRegion(r);
}
void rfb::win32::WMHooks::NotifyHooksRegion(const Region& r) {
// hook_mgr_lock is already heldat this point
updates.add_changed(r);
updatesReady = true;
SetEvent(updateEvent);
}
通過上面的介紹可以wm_hook.dll訊息到來,程式將桌面改變區域儲存起來,觸發hook類的事件物件。這樣程式就將訊息的處理轉化為了對事件的處理。所有的hooks物件共用一個hook執行緒。下面再來看下WMHook類,WHooks的定義在rfb_win32模組的WMHooks.h檔案中,該檔案定義了WMHooks、WMCursorHooks,來支援桌面改變hook和滑鼠改變hook。進一步再來看一下這些hook類的例項化。
5、WMHooks、WMCursorHooks的例項化
WHooks類被封裝到了SDisplayCoreWMHooks類中,在SDisplayCoreWMHooks中定義了一個WHooks變數,並在SDisplayCoreWMHooks的初始化過程中將SDisplay類例項物件的UpdatEvent事件傳給WHooks物件,這樣一來觸發WHooks物件的事件物件,就是觸發了SDisplay類的事件物件,也就是說,每當wm_hook.dll截獲有關桌面變化的訊息,就會觸發SDisplay類物件的事件物件updateEvvent,該變數由API CreateEvent函式建立。接下來分析一下SDisplay類的作用。
SDisplay類在rfb_win32模組中實現,該類對系統顯示進行了封裝。該類的功能相當強大,這裡只關注其對updateEvvent事件的處理。在SDisplay的processEvent(HANDLE event)函式中實現對事件的處理,在該函式中core->flushUpdates()語句將WMHooks物件中儲存的更新區域傳給SDisplay的updates變數。到此為止,桌面更新的區域已經儲存到了SDisplay的updates變數中,獲取、編碼、傳送updates區域的桌面影象,就是處理了桌面更新變化區域。Updates變數是SimpleUpdateTracker類物件,SimpleUpdateTracker類在rfb模組中實現,該類就是用來實現對桌面更新區域的具體操作。