Windows下开发的 GUI 程序,如果在程序中调用了console的一些调试输出信息,则 GUI 模式下就不能看到这些信息了,如果能在 GUI 外带一个console 窗口,把调试信息输出到这个窗口是个不错的选择。
实现这个功能可以有多种方法,其中可以使用 Windows 的API AllocConsole 或者 AttachConsole ,也可以使用开发工具 EDITBIN.exe
代码实现方法
AllocConsole
在Windows 的API,提供一大族Console的函数( 比如可以设置字符颜色等 ),AllocConsole 用来直接为一个进程创建一个控制台。注意,一个进程只能有一个控制台:
Remarks
A process can be associated with only one console, so the AllocConsole function fails if the calling process already has a console. A process can use the FreeConsole function to detach itself from its current console, then it can call AllocConsole to create a new console or AttachConsole to attach to another console.
既然“A process can be associated with only one console”,那就来一个简单的“单例模式”吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 struct EmbeddedConsole { public : static void Need () { if (!_instance) { _instance = new EmbeddedConsole; } } static void Unneed () { delete _instance; _instance = 0 ; } private : EmbeddedConsole () { AllocConsole (); SetConsoleTitle (“XXX程序内嵌测试控制台”); freopen (“conin$”, “r + t”, stdin); freopen (“conout$”, “w + t”, stdout); freopen (“conout$”, “w + t”, stderr); } ~EmbeddedConsole () { fclose (stderr); fclose (stdout); fclose (stdin); FreeConsole (); } static EmbeddedConsole* _instance; }; EmbeddedConsole* EmbeddedConsole::_instance;
测试例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 #define WIN32_LEAN_AND_MEAN #include #include #include #include "resource.h" HINSTANCE hInst; struct EmbeddedConsole { public : static void Need () { if (!_instance) { _instance = new EmbeddedConsole; } } static void Unneed () { delete _instance; _instance = 0 ; } private : EmbeddedConsole () { AllocConsole (); SetConsoleTitle (“XXX程序内嵌测试控制台”); freopen (“conin$”, “r + t”, stdin); freopen (“conout$”, “w + t”, stdout); freopen (“conout$”, “w + t”, stderr); } ~EmbeddedConsole () { fclose (stderr); fclose (stdout); fclose (stdin); FreeConsole (); } static EmbeddedConsole* _instance; }; EmbeddedConsole* EmbeddedConsole::_instance; BOOL CALLBACK DialogProc (HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { std::string str; switch (uMsg) { case WM_INITDIALOG: return TRUE; case WM_CLOSE: EndDialog (hwndDlg, 0 ); return TRUE; case WM_COMMAND: switch (LOWORD (wParam)) { case IDC_BTN_QUIT: EndDialog (hwndDlg, 0 ); EmbeddedConsole::Unneed (); return TRUE; case IDC_BTN_TEST: EmbeddedConsole::Need (); std::cout << “please input :”; std::cin >> str; std::cerr << str << std::endl; ::MessageBox (hwndDlg, str.c_str (), “Information” , MB_ICONINFORMATION); return TRUE; } } return FALSE; } int APIENTRY WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { hInst = hInstance; return DialogBox (hInstance, MAKEINTRESOURCE (DLG_MAIN), NULL , (DLGPROC)DialogProc); }
AttachConsole
函数原型:
1 2 3 BOOL WINAPI AttachConsole ( _In_ DWORD dwProcessId ) ;
与 AllocConsole 类似,也是一个进程只能有一个控制台;Attach的意思是:一个GUI程序,完全可以附加到另一个进程的控制台。
Remarks
A process can be attached to at most one console. If the calling process is already attached to a console, the error code returned is ERROR_ACCESS_DENIED. If the specified process does not have a console, the error code returned is ERROR_INVALID_HANDLE. If the specified process does not exist, the error code returned is ERROR_INVALID_PARAMETER.
那么怎么得到 dwProcessId 呢?有点难,但如果是要取“父进程”的ID,就方便多了——事实上是不用取,只要填-1就可以了。
一个进程可以启动另一个进程,后者就称为前者的“子进程”,而前者则称为“父进程”。这个关系可以一直传递下去,即子进程也可以有自己的子进程。
基本上, 电脑用户通过操作系统运行一个程序,比如画笔,比如浏览器,比如Word,都是以一个叫“Explorer.exe”的子进程身份启动的。而我们在IDE里调试程序,则程序会以调试器的子进程运行……,可见子进程其实是相当常见的。现在,我们事先写一个控制台的程序,假设称为P程序,以后当S程序(通常是一个GUI程序)需要附加的控制台来输入输出时(效果像方法.1),我们就用P程序来启动S程序,S程序中则通过“AttachConsole(-1)”来“挂”到其父程序(也就是P)的控制台。完成调试之后,就把P程序丢一边,以普通方式运行S程序,则AttachConsole(-1)失败,那些用于调试的cout/cin自然失效。
测试:
我们先来写GUI程序的一个例子,也就上面说的子进程S程序。代码和方法1中的几乎一样的,为了简单,我们干脆只写出WinMain函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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; return DialogBox (hInstance, MAKEINTRESOURCE (DLG_MAIN), NULL , (DLGPROC)DialogProc); }
直接使用命令行实现:
editbon /SUBSYSTEM:CONSOLE binpath\bin.exe
在 visual studio 中使用
为了方便调试,需要在VS中为GUI程序添加console窗口。
项目 -> 属性 -> 配置属性 -> 生成事件 -> 生成后事件 -> 命令行:
editbin /SUBSYSTEM:CONSOLE $(OUTDIR)\ipmsg.exe
其实这是一个自动化的命令调用
QTCreator
在.pro文件中加入一句: CONFIG+= console
在运行设置里勾选在终端运行的选项