C++內嵌第三方EXE程式視窗
內嵌第三方EXE程式視窗
近日,在做一個將一個第三方應用視窗嵌入自己的程式視窗內部的功能,經過幾次摸索,終於能比較好的嵌入進去了,為方便敘述,以下稱需要被嵌入應用視窗為目標視窗,承載嵌入應用視窗的為宿主視窗,目標視窗為任意第三方應用,宿主視窗為自己開發。
將目標視窗完美嵌入,關鍵在於將目標視窗做為宿主視窗的子視窗,這樣作業系統就會幫我們完成移動,繪製等操作,為目標視窗設定父視窗,也很簡單,呼叫一個API即可:
HWND SetParent( HWND hWndChild, HWND hWndNewParent );第一個引數是子視窗的控制代碼,也即目標視窗的控制代碼,第二個視窗為我們想設定的目標視窗的父視窗,即宿主視窗,由於宿主視窗的程式為自己開發,要拿到這個控制代碼就很簡單,關鍵在於如何找到目標視窗的控制代碼。
找到目標視窗的控制代碼,也有一個API:
HWND FindWindow( LPCTSTR lpClassName, LPCTSTR lpWindowName );第一個引數為視窗的類名,建立視窗之前,首先需要使用RegisterClass向系統註冊一個視窗類WNDCLASS,這個結構體中有個lpszClassName欄位,就代表這個視窗類的名稱。第二個引數為目標視窗的標題。具體這兩個值多少,可以使用vs自帶的spy++工具,檢視下目標視窗,就能獲得。
下面分步驟,結合程式碼,詳細展示下整體流程。
1.首先準備好宿主視窗的窗體,並且使其透明。
這個可以通過響應WM_CTLCOLOR訊息,並返回一個透明畫刷完成,程式碼很簡單如下:
2.拿到第三方應用的視窗控制代碼
這裡又分為兩種情景,一種是第三方應用尚未啟動,需要我們自己去啟動,並等待其啟動完成後,查詢其視窗控制代碼,另一種情形是第三方應用已啟動,這樣可以直接去查詢視窗控制代碼。如何區分這兩種情形,很簡單,先去查詢下視窗,如果能找到就證明已啟動,否則就去啟動下。
啟動應用程序可以使用CreateProcess,這裡有個問題,如何判斷這個程序已經啟動好了呢?可以使用
DWORD WINAPI WaitForInputIdle( __in HANDLE hProcess, __in DWORD dwMilliseconds );不過這個僅僅是判斷程序啟動好,有可能主視窗還沒建立完成,也有可能第三方程式崩了,壓根拿不到。試過幾種方法,都不太理想,我採用了最簡單有效的方法,就是迴圈探測等待,並設定超時時間,具體看程式碼,我封裝了一個函式,可以拿到第三方程序的控制代碼及主視窗控制代碼。
HWND GetIVAWindowAndProcess(HANDLE& hProcess) { HWND hWnd = NULL; hWnd = ::FindWindow("XXXXClient3",NULL); //是否能找到,找不到就去啟動下 if(hWnd == NULL) { int nNumberDely = 10; STARTUPINFO si; PROCESS_INFORMATION pi; ZeroMemory( &si, sizeof(si) ); si.cb = sizeof(si); ZeroMemory( &pi, sizeof(pi) ); // Start the child process. if( !CreateProcess( NULL, // No module name (use command line) "XXX\\XXX.exe", // Command line NULL, // Process handle not inheritable NULL, // Thread handle not inheritable FALSE, // Set handle inheritance to FALSE 0, // No creation flags NULL, // Use parent's environment block NULL, // Use parent's starting directory &si, // Pointer to STARTUPINFO structure &pi ) // Pointer to PROCESS_INFORMATION structure ) { AfxMessageBox(_T("啟動XXX程序失敗!")); return NULL; } CloseHandle(pi.hThread); WaitForInputIdle(pi.hProcess,INFINITE); hProcess = pi.hProcess; while((hWnd = ::FindWindow("XXXXClient3",NULL)) == NULL && nNumberDely > 0) { Sleep(500); nNumberDely --; } } else { DWORD dwProcessId = 0; DWORD dwThreadId = 0; dwThreadId = GetWindowThreadProcessId(hWnd,&dwProcessId); hProcess = OpenProcess(PROCESS_ALL_ACCESS,NULL,dwProcessId); } return hWnd; <span style="font-size:14px;">}</span>通過以上函式,就能拿到控制代碼了。接下來就可以設定其父視窗了。
3.處理視窗WM_SIZE訊息
前面說過,將第三方視窗設定為子視窗,移動繪製這些我們不用管了,系統會幫忙完成,不過WM_SIZE是需要我們處理的,有了視窗控制代碼,這個也變得很簡單。只要在OnSize裡面呼叫MoveWindow即可
void CDlgMainIntelligent::OnSize(UINT nType, int cx, int cy) { CDetachDlg::OnSize(nType, cx, cy); if(GetSafeHwnd == NULL) { return ; } if(!IsWindow(GetSafeHwnd)) { return; } if(!m_bIsInit) { return ; } CRect rect; GetClientRect(&rect); if(m_hIVAWnd != NULL && !m_bIsAttached) { ::SetParent(m_hIVAWnd,GetSafeHwnd); m_bIsAttached = TRUE; } if(m_hIVAWnd) { ::PostMessage(m_hIVAWnd,WM_SYSCOMMAND,SC_MAXIMIZE,NULL); ::MoveWindow(m_hIVAWnd,rect.left,rect.top,rect.Width,rect.Height,TRUE); } }4.退出時,將第三方程式關掉
好的體驗就是,目標視窗完全做為宿主視窗的一部分,同生共死,因此當宿主視窗銷燬的時候,目標視窗也要一併銷燬。這個也很簡單,只要在宿主視窗的OnCLose中做處理即可。
void CDlgMainIntelligent::OnClose { // TODO: Add your message handler code here and/or call default if(m_hIVAWnd!= NULL) { ::PostMessage(m_hIVAWnd,WM_CLOSE,NULL,NULL); m_hIVAWnd = NULL; } if(m_hIVAProcess != NULL) { ::TerminateProcess(m_hIVAProcess,0); CloseHandle(m_hIVAProcess); m_hIVAProcess = NULL; } CDetachDlg::OnClose; }經過這樣處理,基本能完美嵌入第三方應用的主視窗了。另外還有一些細節上的體驗,一般第三方應用視窗,也是有自己的最大最小化按鈕的,需要把這個遮蔽掉,否則看起來不美觀。我使用的方法是,向第三方應用注入一個DLL,在該DLL中建立一個小視窗,把應用視窗的右上角遮擋住。這樣介面看起來就非常統一和協調了。
注入DLL的程式碼就是windows核心程式設計上的,沒什麼好說的,如下:BOOL CDlgMainIntelligent::InjectHookDLL(HANDLE hProcess) { if(hProcess == NULL) { return FALSE; } PCWSTR pszLibFile = L"MsgHookDLL.Dll"; if(hProcess != NULL) { int cch = 1 + lstrlenW(pszLibFile) ; int cb = cch * sizeof(wchar_t); PWSTR PszLibFileRemote = (PWSTR) VirtualAllocEx(hProcess,NULL,cb,MEM_COMMIT,PAGE_READWRITE); WriteProcessMemory(hProcess,PszLibFileRemote,(PVOID)pszLibFile,cb,NULL); PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE) GetProcAddress(GetModuleHandle(_T("Kernel32")),"LoadLibraryW"); HANDLE hThread = CreateRemoteThread(hProcess,NULL,0,pfnThreadRtn,PszLibFileRemote,0,NULL); WaitForSingleObject(hThread,INFINITE); return TRUE; } return FALSE; }