Windows下开发的 GUI 程序,如果在程序中调用了console的一些调试输出信息,则 GUI 模式下就不能看到这些信息了,如果能在 GUI 外带一个console 窗口,把调试信息输出到这个窗口是个不错的选择。

实现这个功能可以有多种方法,其中可以使用 Windows 的API AllocConsole 或者 AttachConsole,也可以使用开发工具 EDITBIN.exe

代码实现方法

  1. 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:
/*
* TODO: Add code to initialize the dialog.
*/
return TRUE;
case WM_CLOSE:
EndDialog(hwndDlg, 0);
return TRUE;
case WM_COMMAND:
switch (LOWORD(wParam))
{
/** TODO: Add more control ID’s, when needed.
*/
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;
// The user interface is a modal dialog box
return DialogBox(hInstance, MAKEINTRESOURCE(DLG_MAIN), NULL, (DLGPROC)DialogProc);
}

  1. 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;
// The user interface is a modal dialog box
return DialogBox(hInstance, MAKEINTRESOURCE(DLG_MAIN), NULL, (DLGPROC)DialogProc);
}

开发工具 EDITBIN.exe

  1. 直接使用命令行实现:
    editbon /SUBSYSTEM:CONSOLE binpath\bin.exe

  2. 在 visual studio 中使用
    为了方便调试,需要在VS中为GUI程序添加console窗口。
    项目 -> 属性 -> 配置属性 -> 生成事件 -> 生成后事件 -> 命令行:
    editbin /SUBSYSTEM:CONSOLE $(OUTDIR)\ipmsg.exe

    其实这是一个自动化的命令调用

QTCreator

  1. 在.pro文件中加入一句: CONFIG+= console
  2. 在运行设置里勾选在终端运行的选项