内嵌第三方EXE程序窗口

来源:互联网 发布:淘宝联盟推广位名称 编辑:程序博客网 时间:2024/04/30 23:00

近日,在做一个将一个第三方应用窗口嵌入自己的程序窗口内部的功能,经过几次摸索,终于能比较好的嵌入进去了,为方便叙述,以下称需要被嵌入应用窗口为目标窗口,承载嵌入应用窗口的为宿主窗口,目标窗口为任意第三方应用,宿主窗口为自己开发。

将目标窗口完美嵌入,关键在于将目标窗口做为宿主窗口的子窗口,这样操作系统就会帮我们完成移动,绘制等操作,为目标窗口设置父窗口,也很简单,调用一个API即可:

HWND SetParent(          HWND hWndChild,    HWND hWndNewParent);

第一个参数是子窗口的句柄,也即目标窗口的句柄,第二个窗口为我们想设置的目标窗口的父窗口,即宿主窗口,由于宿主窗口的程序为自己开发,要拿到这个句柄就很简单,关键在于如何找到目标窗口的句柄。

找到目标窗口的句柄,也有一个API:

HWND FindWindow(          LPCTSTR lpClassName,    LPCTSTR lpWindowName); 

第一个参数为窗口的类名,创建窗口之前,首先需要使用RegisterClass向系统注册一个窗口类WNDCLASS,这个结构体中有个lpszClassName字段,就代表这个窗口类的名称。第二个参数为目标窗口的标题。具体这两个值多少,可以使用vs自带的spy++工具,查看下目标窗口,就能获得。

下面分步骤,结合代码,详细展示下整体流程。

1.首先准备好宿主窗口的窗体,并且使其透明。

这个可以通过响应WM_CTLCOLOR消息,并返回一个透明画刷完成,代码很简单如下:

HBRUSH CDlgMainIntelligent::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor){CDetachDlg::OnCtlColor(pDC, pWnd, nCtlColor);pDC->SetBkMode(TRANSPARENT);return (HBRUSH)GetStockObject(NULL_BRUSH);}

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 lineNULL,           // Process handle not inheritableNULL,           // Thread handle not inheritableFALSE,          // Set handle inheritance to FALSE0,              // No creation flagsNULL,           // Use parent's environment blockNULL,           // 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 defaultif(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;}


 

 

0 0