首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> VC++

讓控制台程序擁有 cmd.exe 級自動補全功能

     編輯:VC++

痛點:標準 ReadConsole 不支持自動補全

Windows 控制台 cmd.exe 擁有三大神技:命令歷史 (上下鍵)、快捷鍵 (F1-F9)、路徑/命令自動補全 (Tab)。調用標準 ReadConsoleA/W 僅能獲得前兩項,**自動補全功能隱藏在 ReadConsoleW 的保留參數 `lpReserved` 中**,且僅 Unicode 版本支持,ANSI 版 ReadConsoleA 完全無效。

核心秘密:READ_CONSOLE_CONTROL 結構

當 `lpReserved` 非 NULL 時,指向此結構 (未公開於早期 MSDN,實際定義如下):

struct READ_CONSOLE_CONTROL {
    DWORD nLength;      // 結構大小,必須為 sizeof(READ_CONSOLE_CONTROL) = 16
    DWORD dwInitLen;    // 初始緩衝區已有字符數 (光標起始位置)
    DWORD dwWakeMask;   // 喚醒控制掩碼:Bit n 對應 Ctrl+(n+'@'),如 Bit 5 (Ctrl+E) 觸發返回
    DWORD dwUnknown;    // 保留,置 0
};

關鍵機制解析

  • dwInitLen:告知控制台緩衝區已有內容長度,使補全後的文本可接在原有輸入之後,而非覆蓋。
  • dwWakeMask:設置特定控制鍵 (如 Ctrl+F, Ctrl+D) 觸發 ReadConsoleW 返回,而非等待回車。這是實現「補全後繼續輸入」的關鍵。
  • 流程:調用 ReadConsoleW → 用戶按下 WakeMask 指定鍵 (如 Ctrl+F) → 函數返回,緩衝區含觸發鍵 → 應用層識別鍵 → 執行補全邏輯 (寫入緩衝區、調用 WriteConsoleW 回顯) → 更新 dwInitLen → **循環再次調用 ReadConsoleW** 繼續等待輸入。

完整實戰代碼 (最小化演示)

#include 
#include 
#include 

#define CTRL_KEY(c) ((c) & 0x1F) // Ctrl+A=1, Ctrl+B=2 ... Ctrl+F=6, Ctrl+D=4

struct READ_CONSOLE_CONTROL {
    DWORD nLength;
    DWORD dwInitLen;
    DWORD dwWakeMask;
    DWORD dwUnknown;
};

int wmain() {
    _wsetlocale(LC_ALL, L".ACP"); // 支持中文顯示

    HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);

    READ_CONSOLE_CONTROL ctrl = {0};
    ctrl.nLength = sizeof(ctrl);
    ctrl.dwWakeMask = (1 << CTRL_KEY('F')) | (1 << CTRL_KEY('D')); // Ctrl+F, Ctrl+D 觸發補全

    WCHAR buf[256] = {0};
    DWORD read, written;

    while (true) {
        wprintf(L"\n$ ");
        ctrl.dwInitLen = 0;
        ctrl.dwUnknown = 0;

    READ_AGAIN:
        if (ReadConsoleW(hIn, buf, 255, &read, &ctrl)) {
            // 檢查最後一個字符是否為觸發鍵
            if (read > 0 && buf[read-1] == CTRL_KEY('F')) {
                // 用戶按下 Ctrl+F -> 補全 "C:\\Windows\\"
                wcscpy(buf + read - 1, L"C:\\Windows\\");
                WriteConsoleW(hOut, L"C:\\Windows\\", 11, &written, NULL);
                ctrl.dwInitLen = read - 1 + 11; // 更新已有長度
                goto READ_AGAIN; // 關鍵:繼續讀取剩餘輸入
            }
            else if (read > 0 && buf[read-1] == CTRL_KEY('D')) {
                // 用戶按下 Ctrl+D -> 補全 "D:\\Data\\"
                wcscpy(buf + read - 1, L"D:\\Data\\");
                WriteConsoleW(hOut, L"D:\\Data\\", 9, &written, NULL);
                ctrl.dwInitLen = read - 1 + 9;
                goto READ_AGAIN;
            }

            // 正常輸入結束 (回車)
            // 移除尾部 \r\n
            while (read > 0 && (buf[read-1] == L'\n' || buf[read-1] == L'\r')) read--;
            buf[read] = L'\0';

            if (wcscmp(buf, L"quit") == 0) break;
            wprintf(L"Input: [%s]\n", buf);
        } else {
            wprintf(L"ReadConsoleW failed: %lu\n", GetLastError());
            break;
        }
    }
    return 0;
}

從 Demo 到生產級補全引擎的差距

功能Demo 現狀生產級需求
補全觸發硬編碼 Ctrl+F/DTab 鍵觸發 (需處理 Tab 非控制字符特性,通常用鉤子或模擬)
補全來源硬編碼字符串文件系統枚舉、命令歷史、別名、環境變量
多候選無列表顯示、循環選擇、通配符支持
行編輯無 (只能尾部追加)光標移動、插入/刪除、Home/End、Ctrl+左右詞跳轉
Unicode/ANSI純 Unicode內部 UTF-16,輸出按代碼頁轉換

進階提示:如何捕獲 Tab 鍵?

ReadConsoleW 默認不將 Tab (0x09) 視為喚醒鍵。兩種方案:

  1. 控制台模式修改: SetConsoleMode(hIn, ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_WINDOW_INPUT) 移除 ENABLE_PROCESSED_INPUT 可讓 Tab 進緩衝區,但會失去系統行編輯功能,需完全自行實現行編輯器。
  2. 鉤子方案 (推薦): 使用 SetWindowsHookEx(WH_KEYBOARD, ...) 或 SetConsoleCtrlHandler 攔截 Tab,配合 WriteConsoleInput 注入自定義控制字符 (如 Ctrl+T) 觸發 ReadConsoleW 返回。

總結

Windows 控制台隱藏的 READ_CONSOLE_CONTROL 結構為自定義輸入行編輯器提供了強大擴展點。掌握 dwWakeMask + dwInitLen + 循環調用模式,即可構建擁有 cmd.exe 級甚至更強大補全、歷史、語法高亮功能的專業控制台應用。這是眾多高級 CLI 工具 (如 PowerShell, Clink, Cmder) 的核心技術基石之一。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved