羽夏閒談——除錯控制檯

寂靜的羽夏發表於2022-02-08

前言

  在Windows上使用C++寫專案需要控制檯顯示一些資訊,尤其一些GUI程式。雖然自帶輸出和相關除錯函式,但總是不太方便,來回切挺麻煩的,這裡分享一下我封裝好的帶有格式化輸出的除錯控制檯供大家使用。如下是標頭檔案:

#pragma once

// 作者:WingSummer(寂靜的羽夏)
// 協議:MIT
// 作用:對除錯控制檯的封裝,僅限於 Windows 平臺

#include <Windows.h>
class CDebugConsole
{
public:
    /// <summary>
    /// 初始化控制檯,使用前必須執行
    /// </summary>
    /// <returns>成功返回 true,失敗返回 false</returns>
    bool static InitConsole();

    /// <summary>
    /// 關閉控制檯並釋放使用的資源
    /// </summary>
    /// <returns></returns>
    bool static CloseConsole();

    /// <summary>
    /// 向控制檯輸出字串資訊
    /// </summary>
    /// <param name="info">想要輸出的 ASCII 字串</param>
    /// <returns>成功返回 true,失敗返回 false</returns>
    bool static Write(char* info);

    /// <summary>
    /// 向控制檯輸出字串資訊
    /// </summary>
    /// <param name="info">想要輸出的寬字元字串</param>
    /// <returns>成功返回 true,失敗返回 false</returns>
    bool static Write(wchar_t* info);

    /// <summary>
    /// 向控制檯輸出格式化字串資訊
    /// </summary>
    /// <param name="format">待格式化的 ASCII 字串</param>
    /// <param name="">格式化引數</param>
    /// <returns>成功返回 true,失敗返回 false</returns>
    bool static WritePrintf(char* format, ...);

    /// <summary>
    /// 向控制檯輸出格式化字串資訊
    /// </summary>
    /// <param name="format">待格式化的寬字元字串</param>
    /// <param name="">格式化引數</param>
    /// <returns>成功返回 true,失敗返回 false</returns>
    bool static WritePrintf(wchar_t* format, ...);

    /// <summary>
    ///  向控制檯輸出一行字串資訊
    /// </summary>
    /// <param name="info">想要輸出的 ASCII 字串</param>
    /// <returns>成功返回 true,失敗返回 false</returns>
    bool static WriteLine(char* info);

    /// <summary>
    /// 向控制檯輸出一行字串資訊
    /// </summary>
    /// <param name="info">想要輸出的寬字元字串</param>
    /// <returns>成功返回 true,失敗返回 false</returns>
    bool static WriteLine(wchar_t* info = L"");

    /// <summary>
    /// 向控制檯輸出一行格式化字串資訊
    /// </summary>
    /// <param name="format">待格式化的 ASCII 字串</param>
    /// <param name="">成功返回 true,失敗返回 false</param>
    /// <returns></returns>
    bool static WritePrintfLine(char* format, ...);

    /// <summary>
    /// 向控制檯輸出一行格式化字串資訊
    /// </summary>
    /// <param name="format">待格式化的寬字元字串</param>
    /// <param name="">格式化引數</param>
    /// <returns>成功返回 true,失敗返回 false</returns>
    bool static WritePrintfLine(wchar_t* format, ...);

    /// <summary>
    /// 設定控制檯置頂,只在初始化成功控制檯有效
    /// </summary>
    /// <param name="topmost">true 則為設定置頂,反之取消</param>
    void static SetTopMost(bool topmost);
};

  如下是函式實現:


// 作者:WingSummer(寂靜的羽夏)
// 協議:MIT
// 作用:對除錯控制檯的封裝,僅限於 Windows 平臺

#include "pch.h" //這個是預編譯頭,如果程式碼專案沒有就刪掉
#include "CDebugConsole.h"
#include <stdarg.h>

#define CharBufferSize  4096
#define WCharBufferSize 2048
#pragma  warning(disable : 4267)

static HANDLE handle;
static void* buffer = NULL;

HWND console;

bool CDebugConsole::InitConsole()
{
    if (handle)
        return false;
    bool status = AllocConsole();
    handle = GetStdHandle(STD_OUTPUT_HANDLE);
    console = GetConsoleWindow();

    SetWindowTextW(console, L"除錯輸出控制檯");

    HMENU menu = GetSystemMenu(console, NULL);
    RemoveMenu(menu, SC_CLOSE, NULL);

    //申請分配一個物理頁,我不信你的字串會長於2047個
    buffer = VirtualAlloc(NULL, CharBufferSize, MEM_COMMIT, PAGE_READWRITE);
    return status;
}

bool CDebugConsole::CloseConsole()
{
    if (buffer) VirtualFree(buffer, NULL, MEM_FREE);
    bool status = FreeConsole();
    handle = NULL;
    console = NULL;
    return status;
}

bool CDebugConsole::Write(char* info)
{
    if (!handle)
        return false;
    return WriteConsoleA(handle, info, strlen(info), NULL, NULL);
}

bool CDebugConsole::Write(wchar_t* info)
{
    if (!handle)
        return false;
    return WriteConsoleW(handle, info, wcslen(info), NULL, NULL);
}


bool CDebugConsole::WritePrintf(char* format, ...)
{
    va_list ap;
    va_start(ap, format);
    int outcount = _vsnprintf_s((char*)buffer, CharBufferSize, CharBufferSize, format, ap);
    if (outcount < 0 || outcount > CharBufferSize)
        return false;
    bool status = WriteConsoleA(handle, buffer, outcount, NULL, NULL);
    va_end(ap);
    return status;
}

bool CDebugConsole::WritePrintf(wchar_t* format, ...)
{
    va_list ap;
    va_start(ap, format);
    int outcount = _vsnwprintf_s((wchar_t*)buffer, WCharBufferSize, WCharBufferSize, format, ap);
    if (outcount < 0 || outcount > WCharBufferSize)
        return false;
    bool status = WriteConsoleW(handle, buffer, outcount, NULL, NULL);
    va_end(ap);
    return status;
}

bool CDebugConsole::WriteLine(char* info)
{
    size_t len = strlen(info);

    if (len + 1 > CharBufferSize)
    {
        return  false;
    }

    memcpy_s(buffer, CharBufferSize, info, len);
    char* p = (char*)buffer;
    p[len] = '\n';

    return WriteConsoleA(handle, buffer, len + 1, NULL, NULL);
}

bool CDebugConsole::WriteLine(wchar_t* info)
{
    size_t len = wcslen(info);

    if (len + 1 > WCharBufferSize)
    {
        return  false;
    }

    memcpy_s(buffer, CharBufferSize, info, len * 2);
    wchar_t* p = (wchar_t*)buffer;
    p[len] = '\n';

    return WriteConsoleW(handle, buffer, len + 1, NULL, NULL);
}

bool CDebugConsole::WritePrintfLine(char* format, ...)
{
    va_list ap;
    va_start(ap, format);
    int outcount = _vsnprintf_s((char*)buffer, CharBufferSize, CharBufferSize, format, ap);
    if (outcount < 0 || outcount > CharBufferSize - 1)
        return false;

    char* p = (char*)buffer;
    p[outcount] = '\n';

    bool status = WriteConsoleA(handle, buffer, outcount + 1, NULL, NULL);
    va_end(ap);
    return status;
}

bool CDebugConsole::WritePrintfLine(wchar_t* format, ...)
{
    va_list ap;
    va_start(ap, format);
    int outcount = _vsnwprintf_s((wchar_t*)buffer, WCharBufferSize, WCharBufferSize, format, ap);
    if (outcount < 0 || outcount > WCharBufferSize - 1)
        return false;

    wchar_t* p = (wchar_t*)buffer;
    p[outcount] = '\n';

    bool status = WriteConsoleW(handle, buffer, outcount + 1, NULL, NULL);
    va_end(ap);
    return status;
}

void CDebugConsole::SetTopMost(bool topmost)
{
    if (!console)
        return;
    SetWindowPos(console, topmost ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
}

  注意,由於裡面使用了WinAPI進行封裝,故 只能在Windows 使用。使用上述程式碼時,請保留我的個人資訊

後記

  這個實現的原理並不難,程式碼也通俗易懂,使用函式介面註釋比較詳盡。對於C#版本本人暫時無法寫,由於PInvoke無法呼叫WriteConsole函式,故本人無法封裝。如果仍想使用,可以只封裝我程式碼中的初始化控制檯和關閉控制檯以及Write函式即可,生成Dll,再PInvoke,因為C#的字串處理和格式化十分的方便,沒必要寫。希望以上程式碼對你有幫助。如下是效果:

羽夏閒談——除錯控制檯

相關文章