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
};
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 現狀 | 生產級需求 |
|---|---|---|
| 補全觸發 | 硬編碼 Ctrl+F/D | Tab 鍵觸發 (需處理 Tab 非控制字符特性,通常用鉤子或模擬) |
| 補全來源 | 硬編碼字符串 | 文件系統枚舉、命令歷史、別名、環境變量 |
| 多候選 | 無 | 列表顯示、循環選擇、通配符支持 |
| 行編輯 | 無 (只能尾部追加) | 光標移動、插入/刪除、Home/End、Ctrl+左右詞跳轉 |
| Unicode/ANSI | 純 Unicode | 內部 UTF-16,輸出按代碼頁轉換 |
ReadConsoleW 默認不將 Tab (0x09) 視為喚醒鍵。兩種方案:
SetConsoleMode(hIn, ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_WINDOW_INPUT) 移除 ENABLE_PROCESSED_INPUT 可讓 Tab 進緩衝區,但會失去系統行編輯功能,需完全自行實現行編輯器。SetWindowsHookEx(WH_KEYBOARD, ...) 或 SetConsoleCtrlHandler 攔截 Tab,配合 WriteConsoleInput 注入自定義控制字符 (如 Ctrl+T) 觸發 ReadConsoleW 返回。Windows 控制台隱藏的 READ_CONSOLE_CONTROL 結構為自定義輸入行編輯器提供了強大擴展點。掌握 dwWakeMask + dwInitLen + 循環調用模式,即可構建擁有 cmd.exe 級甚至更強大補全、歷史、語法高亮功能的專業控制台應用。這是眾多高級 CLI 工具 (如 PowerShell, Clink, Cmder) 的核心技術基石之一。