开始学习OpenGL由于有一段时间,但是glfw只有窗口区,虽然通过某种手段(移步这里)可以加入工具栏,但仍然无法作为一个标准的GUI,而直接在MFC或Qt里面使用OpenGL API感觉有诸多制肘,各有利弊,所以打算将其嵌入GUI框架,此处以MFC为例
参考博文:https://blog.csdn.net/sunbibei/article/details/51783783
由于要通过CreateProcess创建子进程的方式调用第三方exe程序,所以有必要知道创建的子进程信息,此处exe来自GLFW示例程序
windows任务管理器 -> 进程 -> 查看 -> 选择列 -> 进程勾选PID选项
如图,同一个exe窗口程序多次重复打开之后其PID是唯一的,其他信息(名称)相同,所以首先拿到以CreateProcess方法创建子进程时的进程PID
/* * 创建子进程 * @program 被调用进程的路径 * @args 需传入的参数列表 */ HANDLE StartNewProcess(LPCTSTR program, LPCTSTR args) { HANDLE hProcess = NULL; PROCESS_INFORMATION pi; STARTUPINFO si; ::ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE; // 创建子进程 if (::CreateProcess( program, // 参数1.应用程序的名称,绝对路径,也可以是相对路径,可为NULL,若为NULL,则执行lpCommandLine (LPTSTR)args, // 参数2.命令行参数,可为NULL,一般为应用程序传参,若为NULL,函数则使用 lpApplicationName字符串为运行命令行 NULL, // 参数3.进程的属性,指向一个SECURITY_ATTRIBUTES结构,结构体决定返回的句柄是否被子进程继承,一般为NULL NULL, // 参数4.线程的属性,同参数3.但是这个参数决定的是 线程 是否被继承,一般为NULL FALSE, // 参数5.是否继承父进程的属性,TRUE\FALSE ,一般为FALSE ,若为TRUE 进程中每个可被继承的打开句柄都被继承,被继承者有相同的值和访问权限 0, // 参数6.标志位信息,参数太多,具体见MSDN,或者百度百科,一般默认为 0 NULL, // 参数7.环境变量,指向新进程的环境块,一般为NULL,为NULL则新进程使用调用进程的环境 NULL, // 参数8.程序当前目录,为指定子进程的工作路径,如果是启动Exe程序,则为应用程序所在的目录 &si, // 参数9.传给新进程的信息,指向新进程主窗口如何显示的STARUPINFO 结构体 &pi) // 参数10.进程返回的信息,用来接收新进程识别信息的PROCESS_INFORMATION结构体 ) { Sleep(100); // 此处要是不等待或等待时间过短,子进程窗口未创建完成,则无法在回调中枚举到当前创建的窗口句柄 TRACE("PID = >>>>> = %d, TID = >>>>> = %d, hProcess = >>> %d\n", pi.dwProcessId, pi.dwThreadId, pi.hProcess); // 枚举所有屏幕上的顶层窗口,并将窗口句柄传送给应用程序定义的回调函数 ::EnumWindows(&EnumWindowsProc, pi.dwThreadId); hProcess = pi.hProcess; } else { TRACE("CreateProcess failed (%d).\n", GetLastError()); return 0; } return hProcess; // 返回进程句柄 }
任务管理器显示如下,进程PID为10172
VS打印输出
d:\vsworkspace\mfcglfwtest\mfcglfwtest\mfcglfwtestview.cpp(80) : atlTraceGeneral - PID = >>>>> = 1072, TID = >>>>> = 6288, hProcess = >>> 300
过程无误,由PROCESS_INFORMATION 结构体对象pi拿到进程pid和句柄,两个结构体详情查看这里。
/* * 回调函数, 枚举获取窗口句柄 * @hwnd 枚举获取的顶层窗口句柄 * @param 进程名称 */ BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM param) { DWORD pID; // 进程ID DWORD TpID = GetWindowThreadProcessId(hwnd, &pID); // 返回值为创建窗口的线程ID if (TpID == (DWORD)param) { TRACE("TpID = %d, > ===== > param = %d, > ===== > hwnd = %d\n", TpID, param, hwnd); apphwnd = hwnd; return FALSE; // 停止枚举,返回FALSE } return TRUE; // 继续枚举,返回TRUE }
/* * 关闭进程 */ BOOL CloseProcess() { return TerminateProcess(handle, 0); }
此处加入了一个工具栏按钮事件,点击触发此函数,客户区位置rect和上下文m_pDC定义为CXXXView类的成员
public: CClientDC *m_pDC = NULL; // 客户区上下文 CRect rect;
事件处理函数
void CMFCGlfwTestView::OnGlfwsimple() { // TODO: 在此添加命令处理程序代码 handle = StartNewProcess(_T("..\\Debug\\simple.exe"), NULL); // 获取客户区位置 GetClientRect(&rect); // 更改窗口的位置和尺寸,此处填满父窗口 ::MoveWindow(apphwnd, rect.left, rect.top, rect.Width(), rect.Height(), false); // ::SetWindowLong(apphwnd, GWL_STYLE, WS_VISIBLE); // 更改窗口属性 // 获取客户区上下文 m_pDC = new CClientDC(this); HDC c_DC = m_pDC->GetSafeHdc(); HWND viewWnd = WindowFromDC(c_DC); TRACE("child_hwnd >> %d, parent_hwnd >> %d\n", apphwnd, viewWnd); ::SetWindowLong(apphwnd, GWL_STYLE, WS_VISIBLE); // 获取客户区所在窗口句柄 ::SetParent(apphwnd, viewWnd); }
关键在于通过::SetParent(apphwnd, viewWnd)函数设置子窗口的父窗口为单文档客户区,所以要用WindowFromDC(c_DC)函数经客户区上下文拿到客户区句柄。
上面代码涉及到两个全局变量子窗口句柄和子进程句柄,这两个变量被定义在CXXXView.cpp中而不是头文件中,还有2、3、4三个函数也是直接在CXXXView.cpp中定义而没有进行头文件声明,因为将其作为CXXXView类的成员或对象成员都会使得::EnumWindows(&EnumWindowsProc, pi.dwThreadId)枚举函数参数错误,暂未找到解决之法。
HWND apphwnd; // 子窗口句柄 HANDLE handle; // 进程句柄
子进程窗口应该随着父窗口的销毁而关闭,所以需要为当前类添加一个OnDestroy消息处理函数,在其中调用上面定义的进程关闭函数
/* * 窗口销毁 */ void CMFCGlfwTestView::OnDestroy() { // TODO: 在此处添加消息处理程序代码 if (CloseProcess() != 0) { TRACE("child process has been specified ... \n"); } // 释放客户区上下文对象 if (m_pDC) { delete m_pDC; } TRACE("m_pDC has been free ... \n"); CView::OnDestroy(); }
注意:由于是通过该函数关闭子进程,所以正常点击主窗口上的×按钮是没问题的,但是通过VS的停止调试按钮关闭程序不会触发该函数,子进程仍会在后台运行,需要手动关闭
要使得父窗口大小变化时子窗口随之改变,需要再给当前类添加一个OnSize消息处理函数
/* * 窗口大小改变监听 */ void CMFCGlfwTestView::OnSize(UINT nType, int cx, int cy) { CView::OnSize(nType, cx, cy); // TODO: 在此处添加消息处理程序代码 GetClientRect(&rect); ::MoveWindow(apphwnd, rect.left, rect.top, rect.Width(), rect.Height(), true); }
每次父窗口改变后获取窗体大小,重新设置子窗口大小。
注意:子窗口本身具有按键监听函数,按ESC可关闭子窗口,该功能嵌入MFC后依然有效,由于没有设置点击打开子窗口的次数,所以多次打开会重叠,无法关闭前面进程,需要手动关闭。
10、进程间通信
既然是通过子进程方式调用OpenGL程序,那么某些复杂交互应该需要进程间通信,待补充。
原文:https://www.cnblogs.com/jixiaohua/p/11353229.html