現在的大型軟件啓動之後,很可能存在多個進程。如瀏覽器,每打開一個新的頁面,就會啓動一個新的進程。爲什麼會使用多進程,而不是全部使用多線程呢?因爲多進程,可以做到完全的隔離,這樣的好處是:如果一個頁面卡死了,不會干擾到其他頁面;在代碼層,也少了多頁面之間線程變量安全考慮的顧忌了,不用考慮同步異步等操作。
我們在MainUI中嵌入ThirdUI窗口,主要有以下步驟:
1. 打開第三方進程窗口
主要通過CreateProcess函數來打開第三方進程,並獲取進程ID。
HWND CNestWndDlg::OpenProcess()
{
// 進程啓動信息
STARTUPINFO si;
memset(&si, 0, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOW;
// 進程信息
PROCESS_INFORMATION pi = {0};
// 創建進程
if (CreateProcess("C:\\WINDOWS\\system32\\notepad.exe", NULL, NULL, NULL, false, 0, NULL, NULL, &si, &pi))
{
PROCESS_INFO procwin;
procwin.dwProcessId = pi.dwProcessId;
procwin.hWnd = NULL;
// 等待新進程初始化完畢
WaitForInputIdle(pi.hProcess, 5000);
EnumWindows(EnumWindowCallBack, (LPARAM)&procwin);
if (NULL == procwin.hWnd)
{
Sleep(200);
EnumWindows(EnumWindowCallBack, (LPARAM)&procwin);
}
return procwin.hWnd;
}
return NULL;
}
2. 通過進程ID找到對應窗口句柄
主要通過EnumWindows來枚舉所有窗口,並通過GetWindowThreadProcessId來獲取窗口的進程ID,並與1中打開的進程ID作比較來獲取對應窗口的句柄。
// 查找進程主窗口的回調函數
BOOL CALLBACK EnumWindowCallBack(HWND hWnd, LPARAM lParam)
{
PROCESS_INFO *pProcessWindow = (PROCESS_INFO *)lParam;
DWORD dwProcessId;
GetWindowThreadProcessId(hWnd, &dwProcessId);
if (pProcessWindow->dwProcessId == dwProcessId && IsWindowEnabled(hWnd) && GetParent(hWnd) == NULL)
{
pProcessWindow->hWnd = hWnd;
return FALSE;
}
return TRUE;
}
3. 設置第三方窗口相關屬性
爲了讓thirdUI內嵌入MainUI中,需要以下設置:
- 修改thirdUI的屬性,通過SetWindowLong清除WS_POPUP風格。
- 修改thirdUI的屬性,通過SetWindowLong添加WS_CHILD風格。
- 通過SetParent來將MainUI設置爲thirdUI的父窗口,設置完之後,thirdUI的消息循環就進入到了MainUI的消息隊列,這樣thirdUI就和MainUI的一個子窗口類似。
- 通過GetClientRect來獲取MainUI的客戶區大小,通過MoveWindow將thirdUI移到MainUI的客戶區。
- 調用InvalidateRect將thirdUI設置爲整個區域無效區域。
- UpdateWindow強制刷新thirdUI,因爲InvalidateRect只是將消息放入消息隊列並不定立即響應,所以可能導致thirdUI更新不正常,此時需要UpdateWindow來強制刷新無效區域。
4. 切換窗口之後的更新設置
當窗口最小化或者切換到主進程的其他頁面後,再切換回來,此時也需要刷新設置。否則會更新錯誤。重點,必須再次設置SetParent,否則無法正確傳遞消息。(具體原因未知)
- 通過SetParent來將MainUI設置爲thirdUI的父窗口,設置完之後,thirdUI的消息循環就進入到了MainUI的消息隊列,這樣thirdUI就和MainUI的一個子窗口類似。
- 調用InvalidateRect將thirdUI設置爲整個區域無效區域。
- UpdateWindow強制刷新thirdUI,因爲InvalidateRect只是將消息放入消息隊列並不定立即響應,所以可能導致thirdUI更新不正常,此時需要UpdateWindow來強制刷新無效區域。
5. 示例
示例是一個多tab窗口,調用記事本(notepad),顯示如下:
Demo