创建基于控制台的调试窗口






4.70/5 (13投票s)
本文将展示如何有选择地为Win32甚至MFC程序添加控制台窗口,用于调试目的。
构建块
一个合适的日志记录函数应该具有灵活性,能够接受任意数量的各种类型的参数,例如日期、数字和字符串。首先,我将介绍可以在每个日志条目中包含的信息类型。
调用函数
可以始终知道调用每个日志条目的函数的名称。为此,我们在 WriteLogFile()
函数中添加一个参数,并通过一个 MACRO 调用它,该 MACRO 自动添加以下 预处理器指令 作为第一个参数:__FUNCTION__
(参见 这篇文章)。
一般信息
我们通常希望日志条目包含一般信息,例如
- 当前日期和时间
- 应用程序启动的日期和时间
- (可选)应用程序创建的日期和时间
计算我们的周期
这不是每个应用程序都需要的东西,但有些应用程序确实需要。如果您的应用程序有一个主事件循环,您通常会从一些初始化开始,然后启动此循环,直到应用程序终止。您可能想计算此循环发生了多少次。
设置文本和背景颜色
以下函数可用于设置文本的文本和背景颜色。该函数应由一个基于设置的条件包装器调用,以使其更易于在运行时查看日志。例如,区分信息日志条目和错误/警告,并为不同类型的紧急情况设置不同的颜色。
inline void setcolor(int textcol, int backcol)
{
if ((textcol % 16) == (backcol % 16))textcol++;
textcol %= 16; backcol %= 16;
unsigned short wAttributes = ((unsigned)backcol << 4) | (unsigned)textcol;
HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(hStdOut, wAttributes);
}
定义日志级别
使用以下代码,我们定义了不同的日志级别,允许稍后根据选定的“日志级别”显示日志条目。
// Logging Level
typedef enum
{
minimal = 1, // messages that must be displayed
normal = 2, // messages that would normally be displayed
verbose = 3 // messages that don't necessarily need to be displayed
} LoggingLevel;
然后,我们可以使用 MACRO 选择所需的日志级别作为 WriteLogFile()
的参数。
#define LOG_VERBOSE utils::LoggingLevel::verbose
#define LOG_NORMAL utils::LoggingLevel::normal
#define LOG_MINIMAL utils::LoggingLevel::minimal
但是,我们需要定义两组不同的日志级别
日志消息级别:假设一个函数有一个调用 WriteLogFile
,我们认为在大多数情况下不应该显示。然后,我们希望将此特定消息定义为“冗余”。
应用程序级别:现在,我们希望动态地定义我们现在处于“冗余”、“调试”还是“最小”模式。我们使用这些 MACRO 来实现。
#define LOGGINGTYPE_NONE 0
#define LOGGINGTYPE_SIMPLE 1
#define LOGGINGTYPE_DEBUG 2
如果我们处于“SIMPLE”模式,我们将仅显示被分类为“最小”的日志条目。如果我们处于“DEBUG”模式,我们还将包括被分类为“冗余”的条目,等等...
显示我们程序的创建日期
我们将获取我们程序的创建日期,但首先,我们定义一个“友好”的正式格式来显示它
#define FRIENDLY_DATEFORMAT L"%d-%m-%Y, %H:%M:%S"
这意味着:日-月-年 时:分:秒。例如
04-04-2019 10:00:00
我们使用以下代码获取我们程序的创建日期
CTime GetFileDateTime(LPCWSTR FileName)
{
FILETIME ftCreate, ftAccess, ftWrite;
HANDLE hFile;
CTime result = NULL;
CTime FileTime;
hFile = CreateFile(FileName, GENERIC_READ,
FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
return result;
}
// Retrieve the file times for the file.
if (!GetFileTime(hFile, &ftCreate, &ftAccess, &ftWrite))
return result;
FileTime = ftWrite;
CloseHandle(hFile);
result = (CTime)FileTime;
return result;
}
// GetSelfDateTime- returns the creation date and time of the running program
CTime GetSelfDateTime(void)
{
WCHAR szExeFileName[MAX_PATH];
GetModuleFileName(NULL, szExeFileName, MAX_PATH);
return GetFileDateTime(szExeFileName);
}
正如您所看到的,GetFileDateTime()
有 2 种变体。当它被调用时没有参数时,将找出当前运行程序的完整路径,然后使用它来调用另一个变体,并将其传递给函数。返回值是一个 CTime。
显示处理器类型
我们可能还想显示处理器类型,很可能是 32 位 (x86) 或 64 位 (x64)。我们使用以下函数来实现
bool Isx64OS()
{
SYSTEM_INFO systemInfo;
GetNativeSystemInfo(&systemInfo);
return (systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64);
}.
Using the Code
现在是时候看看整个函数以及我们如何调用它了。
我们使用 WriteLogFile()
,如下面的代码片段所示。
注意:当设置了 _LOG 预处理器指令时,我们使用日志记录,否则我们不使用,并且此代码的组织方式确保了未打开 _LOG 的版本不会在二进制可执行文件中包含所有日志消息,因为有时我们不希望发布这些消息,而只是在内部使用它们。
int myIntVar = 999;
wchar_t *myStringVar = L"Test";
#if _LOG
WriteLogFile(__FUNCTION__, LOG_COLOR_WHITE, LOG_VERBOSE,
L"myStringVar = %s myIntVar = %d", myStringVar, myIntVar);
#endif
结果
正如您所看到的,当运行代码时,您应该会看到控制台窗口,如以下屏幕截图所示
历史
- 2019年4月21日:初始版本