
1. 写在前面的话
在调试程序的时候,最常用的办法就是打断点,看内存、堆栈等信息。但对一个GUI应用程序来讲,有时候我们不得不利用输出调试信息的方式来排查程序异常。比如调试鼠标hover事件、应用全屏等事件时,由于调试器本身也是GUI程序,当触发断点时,hover或全屏状态已经发生了改变,断点时的状态可能并不是我们期望的状态,这个时候就可以利用输出调试信息的方式来查看程序的状态。文本总结了在GUI应用程序中输出调试信息比较常用的三种方式。
2. 输出到调试器输出窗口中
将调试信息输出到调试器输出窗口(Output)是首选,也是用的最多的方法。对于一个GUI应用程序来讲,默认是没有控制台的,如果我们用标准输入输出函数,如printf, std::cout等,这些函数是不起作用的。但如果GUI应用程序在被调试状态下,调试器会将标准输出重定向到输出窗口,以下是在Visual Studio 2015中的输出:

除了直接用标准输出函数输出调试信息外,VS调试器还支持条件式断点,即在触发某个条件的时候触发断点或输出信息到控制台,如:

当触发条件hWndButton为0时,会在输出窗口输出调试信息$FUNCTION: value x={x}, y={y},其中$FUNCTION是所在函数名,{x}和{y}是栈上x和y的变量值。
3. 输出到DebugView中
有时候我们的程序的运行环境中可能并没有可用的调试器,这个时候我们可以利用免费的sysinternals工具集中的DebugView来查看调试信息。使用DebugView的前提是,你需要使用OutputDebugString API输出调试信息。该工具不仅可以捕获用户态Win32应用程序的调试信息,还支持捕获内核态驱动程序的调试信息。我觉得该工具更好用的地方在于,它支持过滤规则的设置,并且支持一些通配符,可以很方便地过滤出我们想要查看的信息,如:


4. 输出到标准控制台中
最后一种稍微麻烦点,需要写几行代码,先说一下其工作原理。我们在写Win32控制台程序的时候,有一个标准输入输出的命令行窗口供用户跟程序交互,虽然控制台应用程序在交互上不够友好,但标准输出的信息却可以在窗口中直观查看。而对于Win32 GUI应用,默认是没有那个控制台的。其实Win32控制台程序和Win32 GUI程序并没有严格的界线,我们可以在Win32控制台程序中展示窗口,也可以在Win32 GUI程序中展示控制台。
Windows提供了一个API叫 AllocConsole可以创建一个控制台,但即使控制台创建成功后,当你调用函数printf或std::cout时,发现仍然不能输出到控制台中,因为Win32 GUI程序默认将标准输入(stdin)、标准输出(stdout),标准错误输出(stderr)都设置为了0, 我们需要重新将其关联到创建的控制台上,其实现如下:
#include <windows.h>
#include <stdio.h>
#include <iostream>
int WINAPI wWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPWSTR lpCmdLine,
int nCmdShow)
{
::AllocConsole();
FILE* pfStdOut = nullptr;
FILE* pfStdIn = nullptr;
freopen_s(pfStdOut, "CONOUT$", "w", stdout);
freopen_s(pfStdIn, "CONIN$", "r", stdin);
fprintf(stdout, "fprintf::test\n");
printf("printf::test\n");
std::cout << "cout::test" << std::endl;
::FreeConsole();
return 0;
}
运行结果如下:

- 更多参考
[1]. http://dslweb.nwnexus.com/~ast/dload/guicon.htm
[2]. https://stackoverflow.com/questions/32185512/output-to-console-from-a-win32-gui-application-on-windows-10#
[3]. https://www.codeproject.com/Articles/15836/Writing-to-and-read-from-the-console-From-a-GUI-ap
[4]. https://docs.microsoft.com/zh-cn/sysinternals/
网友评论