内嵌第三方EXE程序窗口

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

将目标窗口完美嵌入,关键在于将目标窗口做为宿主窗口的子窗口,这样操作系统就会帮我们完成移动,绘制等操作,为目标窗口设置父窗口,也很简单,调用一个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 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;
}

通过以上函数,就能拿到句柄了。接下来就可以设置其父窗口了。

 

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;
}


 

 

你可能感兴趣的:(软件开发)