一阵日子没来了,因为去搞房子的事了。回来以后,发现大本营速度慢了好多,这是好事还是坏事?说明用户多了?说明性能不足?
2. 方法-2:外挂式
先接前一篇《在GUI程序中使用控制台的两种方法-方法.1》。方法1采用的是“内嵌式”,即由GUI程序自身申请一个控制台,然后在上面输入输出,用到一个API:“ AllocConsole ”。
2.1 API
这一次我们使用另一个函数:“AttachConsole”:
- BOOL WINAPI AttachConsole(
- __in DWORD dwProcessId
- );
那么怎么得到另一个进程的ID呢?有点难,但如果是要取“父进程”的ID,就方便多了——事实上是不用取,只要填-1就可以了。
2.2 “外挂式”思路
一个进程可以启动另一个进程,后者就称为前者的“子进程”,而前者则称为“父进程”。这个关系可以一直传递下去,即子进程也可以有自己的子进程。
基本上, 电脑用户通过操作系统运行一个程序,比如画笔,比如浏览器,比如Word,都是以一个叫“Explorer.exe”的子进程身份启动的。而我们在IDE里调试程序,则程序会以调试器的子进程运行……,可见子进程其实是相当常见的。现在,我们事先写一个控制台的程序,假设称为P程序,以后当S程序(通常是一个GUI程序)需要附加的控制台来输入输出时(效果像方法.1),我们就用P程序来启动S程序,S程序中则通过“AttachConsole(-1)”来“挂”到其父程序(也就是P)的控制台。完成调试之后,就把P程序丢一边,以普通方式运行S程序,则AttachConsole(-1)失败,那些用于调试的cout/cin自然失效。
2.3 GUI 测试例子
我们先来写GUI程序的一个例子,也就上面说的子进程S程序。代码和方法1中的几乎一样的,为了简单,我们干脆只写出WinMain函数。
- int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
- {
- hInst = hInstance;
- if (0 == AttachConsole((DWORD)-1))
- {
- ::MessageBox(0, "AttachConsole fail", "msg", 0);
- }
- else
- {
- freopen("conin$", "r+t", stdin);
- freopen("conout$", "w+t", stdout);
- freopen("conout$", "w+t", stderr);
- ::MessageBox(0, "AttachConsole OK", "msg", 0);
- }
- std::cout << std::hex << _WIN32_WINNT << std::endl;
- // The user interface is a modal dialog box
- return DialogBox(hInstance, MAKEINTRESOURCE(DLG_MAIN), NULL, (DLGPROC)DialogProc);
- }
当“AttachConsole”失败时,返回0,为了测试,我故意让它弹出一个消息框,实际程序当然不应这样扰民。当成功时,我们还是调用freopen来绑定cin,cout,cerr。
注意: 第18,我立即用cout 输出一个宏(整数)的值(采用16进制),这个宏 _WIN32_WINNT 在有些IDE里,默认是0x40的,而AttachConsole的文档里写到:
To compile an application that uses this function, define _WIN32_WINNT as 0x0501 or later. For more information, see Using the Windows Headers.
所以,我们必须通过IDE预定好_WIN32_WINNT,让它变成0x50好了。对于Code::Blocks和 VC,这都是很简单的事,你应该知道如何做。
上面的代码需要一些宏定义和头文件包含:
- #define WIN32_LEAN_AND_MEAN
- #include <iostream>
- #include <windows.h>
2.4 带控制台的父进程
GUI程序默认不带控制台,所以我们现在写一个控制台程序,用它来调用要调试的GUI程序。这本来是一件再简单不过的事了。只要用C语言的system程序就可以了,并且是跨平台的——问题是微软大叔总爱干些半拉子的事。Windows从DOS起家,都过去多少年了,它对控制台支持还是怪怪的,所以system函数在它身上,居然不支持带空格的路径,我们只好找CreateProcess来出气。
新建一个控制台程序:
- int main(int argc, char** argv)
- {
- if (argc < 2)
- {
- cout << "Usage:" << argv[0] << " <filename> <args ...>" << endl;
- return 1;
- }
- cout << argv[1] << endl;
- //return system(argv[1]); linux下就这样...
- STARTUPINFO si;
- ZeroMemory(&si, sizeof(si));
- si.cb = sizeof(si);
- PROCESS_INFORMATION pi;
- ZeroMemory(π, sizeof(pi));
- ::CreateProcess (argv[1]
- , NULL
- , NULL //LPSECURITY_ATTRIBUTES
- , NULL //LPSECURITY_ATTRIBUTES
- , FALSE //bInheritHandles
- , 0 //dwCreationFlags
- , NULL //lpEnvironment
- , NULL //LPCTSTR
- , &si //LPSTARTUPINFO
- , π); //LPPROCESS_INFORMATION
- ::WaitForSingleObject(pi.hProcess, INFINITE);
- DWORD exitCode = 0;
- int ret;
- if (!::GetExitCodeProcess(pi.hProcess, &exitCode))
- {
- ret = -1;
- }
- else
- {
- ret = exitCode;
- }
- ::CloseHandle(pi.hProcess);
- ::CloseHandle(pi.hThread);
- return ret;
- }
我们必须用WaitForSingleObject来调待子进程退出后,父进程才能退出。我们还用GetExitCodeProcess来得到子程序的退出代码。
说到CreateProcess ,我们还可做更多的事,比如我们可以倒过来,用它来创建一个带有控制台(也就是说,天生有cout,cin,cerr)的程序,然后抓取它的输出(cout, cerr),甚至也可以连通它的输入(cin),这样就可以倒过来,为一个控制台程序,挂一个GUI界面了,典型的的案例,比如WinRAR最初的版本。当然,今天我们谈的是 如何为一个GUI程序,挂一个控制台,那么,这就完事了。
------------欢迎关注《白话 C++》的出版-------------------------
如果您想与我交流,请点击如下链接成为我的好友:
http://student.youkuaiyun.com/invite.php?u=112600&c=f635b3cf130f350c