使用WriteConsoleOutputAttribute时控制台异常颜色的原因排查
控制台WriteConsoleOutputAttribute批量着色导致颜色混乱问题解决
我尝试给控制台窗口特定位置的字符添加颜色编码优化显示,此前操作都正常,但在对第4行文本执行简单着色时,出现大量前景色与背景色混乱的异常情况。
异常效果截图:
可复现问题的代码:
#define UNICODE #include <iostream> #include <Windows.h> #include <string> #pragma comment(lib, "User32.lib") using namespace std; int main() { HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE); HANDLE hStdErr = GetStdHandle(STD_ERROR_HANDLE); SMALL_RECT consoleCoords{0, 0, 79, 19}; COORD sbSize{80, 20}; if(!SetConsoleWindowInfo(hStdOut, TRUE, &consoleCoords)) /* HANDLE hConsoleOutput, BOOL bAbsolute, const SMALL_RECT* lpConsoleWindow */ { wcout << L"SetConsoleWindowInfo failed. Error Code: " << GetLastError() << L'\n'; system("pause"); return 0; } if(!SetConsoleScreenBufferSize(hStdOut, sbSize)) /* HANDLE hConsoleOutput, COORD dwSize */ { wcout << L"SetConsoleScreenBufferSize failed. Error Code: " << GetLastError() << L'\n'; system("pause"); return 0; } DWORD cCharsWritten{}; DWORD attrsWritten{}; CONSOLE_SCREEN_BUFFER_INFO bufferInfo{}; DWORD dwConsoleSz{}; system("pause"); if(!GetConsoleScreenBufferInfo(hStdOut, &bufferInfo)) { wcout << L"GetConsoleScreenBufferInfo failed. Error Code: " << GetLastError() << '\n'; system("pause"); return 0; } dwConsoleSz = bufferInfo.dwSize.X * bufferInfo.dwSize.Y; /* X (character cells) * Y (character cells) */ COORD cursorHome{0, 0}; /* Now we fill the entire screen with blanks. */ if(!FillConsoleOutputCharacter( hStdOut, L' ', dwConsoleSz, cursorHome, &cCharsWritten ) ) { wcout << L"FillConsoleOutputCharacter failed. Error Code: " << GetLastError() << '\n'; system("pause"); return 0; } if(!SetConsoleCursorPosition(hStdOut, cursorHome)) /*Research is needed regarding this function as it does not seem to move the cursor. */ { wcout << L"SetConsoleCursorPosition failed. Error Code: " << GetLastError() << L'\n'; system("pause"); return 0; } wchar_t topmiddlebottomRow[] = L"================"; wchar_t separatorString[] = L"================================================================================"; LPCWSTR writeRow = topmiddlebottomRow; LPCWSTR writeSeparator = separatorString; WORD S_attr = FOREGROUND_GREEN; if(!WriteConsoleOutputCharacterW(hStdOut, writeRow, 16, COORD{64, 0}, &cCharsWritten)) { wcout << L"WriteConsoleOutputCharacterW failed. Error Code: " << GetLastError() << L'\n'; system("pause"); return 0; } if(!WriteConsoleOutputCharacterW(hStdOut, writeRow, 16, COORD{64, 1}, &cCharsWritten)) { wcout << L"WriteConsoleOutputCharacterW failed. Error Code: " << GetLastError() << L'\n'; system("pause"); return 0; } if(!WriteConsoleOutputCharacterW(hStdOut, writeRow, 16, COORD{64, 2}, &cCharsWritten)) { wcout << L"WriteConsoleOutputCharacterW failed. Error Code: " << GetLastError() << L'\n'; system("pause"); return 0; } if(!WriteConsoleOutputCharacterW(hStdOut, writeSeparator, 80, COORD{0, 4}, &cCharsWritten)) { wcout << L"WriteConsoleOutputCharacterW failed. Error Code: " << GetLastError() << L'\n'; system("pause"); return 0; } if(!WriteConsoleOutputAttribute(hStdOut, &S_attr, 80, COORD{0, 4}, &attrsWritten)) { wcout << L"WriteConsoleOutputAttribute failed. Error Code: " << GetLastError() << L'\n'; system("pause"); return 0; } while(1) { } return 0; }
问题原因
调用WriteConsoleOutputAttribute时,第二个参数要求传入一个属性数组的起始地址,函数会从该地址开始连续读取nLength个(这里是80个)WORD类型的属性值。但原代码只定义了单个S_attr变量,函数读取时会越界访问后续未初始化的内存,这些垃圾数据被当作属性值应用到字符上,导致颜色混乱。
解决方法
方法1:创建对应长度的属性数组
提前初始化一个包含80个相同属性值的数组,确保函数读取的所有属性都是合法且正确的:
// 替换原单个S_attr定义 WORD S_attrs[80]; // 初始化所有元素为绿色前景色 for (int i = 0; i < 80; ++i) { S_attrs[i] = FOREGROUND_GREEN; } // 调用WriteConsoleOutputAttribute时传入数组地址 if(!WriteConsoleOutputAttribute(hStdOut, S_attrs, 80, COORD{0, 4}, &attrsWritten)) { wcout << L"WriteConsoleOutputAttribute failed. Error Code: " << GetLastError() << L'\n'; system("pause"); return 0; }
方法2:循环逐个设置属性
如你自己发现的,通过循环逐个给每个字符设置属性,避免内存越界问题:
COORD pos; pos.Y = 4; for (int i = 0; i < 80; ++i) { pos.X = i; if(!WriteConsoleOutputAttribute(hStdOut, &S_attr, 1, pos, &attrsWritten)) { wcout << L"WriteConsoleOutputAttribute failed. Error Code: " << GetLastError() << L'\n'; system("pause"); return 0; } }
额外说明
#pragma comment(lib, "User32.lib")确实多余,控制台相关API依赖的是Kernel32.lib,编译器通常会自动链接该库。
内容的提问来源于stack exchange,提问作者nore
相关产品推荐
相关产品推荐

