基于Win32 API的矩阵效果程序窗口调整大小后崩溃求助
控制台矩阵效果程序窗口Resize冻结问题及优化方案
问题概述
基于Linux cmatrix 命令启发,使用Win32 API与C++基础库开发的控制台矩阵效果程序,在固定屏幕尺寸下运行正常,但调整控制台窗口大小后会冻结停止执行。尝试通过sResponseReset函数检查控制台缓冲区大小并重置缓冲区,但修改扩容逻辑后问题仍存在。同时正在研究多线程处理退出输入方案。
相关代码
初始代码
#include <iostream> #include <Windows.h> #include <cstdlib> #include <chrono> #include <thread> #include <future> #include <conio.h> int nScreenWidth = 80; int nScreenHeight = 80; int MAX_OFFSET = 30; int MAX_LENGTH = 10; using namespace std; //RETURNS A RANDOM UNICODE CHARACTER wchar_t randChar() { char fChar = rand() % 95 + 32; return (wchar_t)fChar; } //CREATES A BUFFER WITH FORECOLOR BLACK AND BACKGROUND BLACK void newBuffer(CHAR_INFO* buffer) { for (int i = 0; i < nScreenWidth; i++) { for (int j = 0; j < nScreenHeight; j++) { buffer[j * nScreenWidth + i].Char.UnicodeChar = randChar(); buffer[j * nScreenWidth + i].Attributes = 0; } } } //GENERATES A RANDOM OFFSET BASED ON THE MAXIMUM OFFSET int nOffset(){ return (-1) * (rand() % MAX_OFFSET); } //GENERATES A RANDOM LENGTH BASED ON THE MAXIMUM LENGTH int nLength() { return rand() % MAX_LENGTH; } void sResponseReset(HANDLE* handle, CONSOLE_SCREEN_BUFFER_INFO* pBufferInfo, CHAR_INFO* buffer) { GetConsoleScreenBufferInfo(*handle, pBufferInfo); if (nScreenWidth != pBufferInfo->dwSize.X || nScreenHeight != pBufferInfo->dwSize.Y) { nScreenWidth = pBufferInfo->dwSize.X; nScreenHeight = pBufferInfo->dwSize.Y; newBuffer(buffer); } } int main() { //CREATING BUFFER AND HANDLE TO MODIFY HANDLE hHandle = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CONSOLE_TEXTMODE_BUFFER, NULL); SetConsoleActiveScreenBuffer(hHandle); CONSOLE_SCREEN_BUFFER_INFO sBufferInfo; GetConsoleScreenBufferInfo(hHandle, &sBufferInfo); nScreenWidth = sBufferInfo.dwSize.X; nScreenHeight = sBufferInfo.dwSize.Y; CHAR_INFO* sBuffer = new CHAR_INFO[nScreenHeight * nScreenWidth]; newBuffer(sBuffer); //OFFSET AND LENGTH ARRAY AND INITIALIZING IT int* arrOffset = new int[nScreenWidth]; int* arrLength = new int[nScreenWidth]; for (int i = 0; i < nScreenWidth; i++) { arrOffset[i] = nOffset(); arrLength[i] = nLength(); } //CREATING RECT FOR CONSOLE OUTPUT SMALL_RECT sRect = { 0, 0, (SHORT)nScreenWidth, (SHORT)nScreenHeight }; srand((unsigned int)time(0)); //SETS SEED SO THAT WE DON'T GET SAME RANDOM VALUE bool qConsole = false; while (!qConsole) { sResponseReset(&hHandle, &sBufferInfo, sBuffer); //CHECKS IF THE UPPER AND LOWER CELL ARE SAME AND PROPOGATES COLOUR for (int i = 0; i < nScreenWidth; i++) { for (int j = nScreenHeight - 1; j > 0; j--) { if (sBuffer[(j-1) * nScreenWidth + i].Attributes != sBuffer[(j+1) * nScreenWidth + i].Attributes) { sBuffer[(j + 1) * nScreenWidth + i].Attributes = sBuffer[j * nScreenWidth + i].Attributes; sBuffer[j * nScreenWidth + i].Attributes = sBuffer[(j - 1) * nScreenWidth + i].Attributes; } } } for (int i = 0; i < nScreenWidth; i++) { if (arrOffset[i] > 0) { if (arrOffset[i] <= arrLength[i]) sBuffer[i].Attributes = FOREGROUND_GREEN; else { sBuffer[i].Attributes = 0; sBuffer[i].Char.UnicodeChar = randChar(); arrOffset[i] = nOffset(); arrLength[i] = nLength(); } } arrOffset[i]++; } //CHANGE OUTPUT BUFFER AND MAKING IT 1 FRAME PER SECOND WriteConsoleOutput(hHandle, sBuffer, { (SHORT)nScreenWidth, (SHORT)nScreenHeight }, { 0,0 }, &sRect); this_thread::sleep_for(chrono::milliseconds(500)); } (void)getchar(); //CLOSING AND DELETING HANDLE AND ALLOCATED MEMORY CloseHandle(hHandle); delete[] sBuffer; delete[] arrOffset; delete[] arrLength; return 0; }
修改后代码
#include <iostream> #include <Windows.h> #include <cstdlib> #include <chrono> #include <thread> #include <future> #include <conio.h> int nScreenWidth = 80; int nScreenHeight = 80; int MAX_OFFSET = 30; int MAX_LENGTH = 10; using namespace std; //RETURNS A RANDOM UNICODE CHARACTER wchar_t randChar() { char fChar = rand() % 95 + 32; return (wchar_t)fChar; } //CREATES A BUFFER WITH FORECOLOR BLACK AND BACKGROUND BLACK void newBuffer(CHAR_INFO* buffer) { for (int i = 0; i < nScreenWidth; i++) { for (int j = 0; j < nScreenHeight; j++) { buffer[j * nScreenWidth + i].Char.UnicodeChar = randChar(); buffer[j * nScreenWidth + i].Attributes = 0; } } } //GENERATES A RANDOM OFFSET BASED ON THE MAXIMUM OFFSET int nOffset(){ return (-1) * (rand() % MAX_OFFSET); } //GENERATES A RANDOM LENGTH BASED ON THE MAXIMUM LENGTH int nLength() { return rand() % MAX_LENGTH; } void sResponseReset(HANDLE handle, CONSOLE_SCREEN_BUFFER_INFO* pBufferInfo, CHAR_INFO* buffer, SMALL_RECT* pRect) { GetConsoleScreenBufferInfo(handle, pBufferInfo); if (nScreenWidth != pBufferInfo->dwSize.X || nScreenHeight != pBufferInfo->dwSize.Y) { nScreenWidth = pBufferInfo->dwSize.X; nScreenHeight = pBufferInfo->dwSize.Y; CHAR_INFO* nBuffer = new CHAR_INFO[nScreenWidth * nScreenHeight]; newBuffer(nBuffer); delete[] buffer; buffer = nBuffer; pRect->Right = (SHORT)nScreenWidth; pRect->Bottom = (SHORT)nScreenHeight; } } int main() { //CREATING BUFFER AND HANDLE TO MODIFY HANDLE hHandle = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CONSOLE_TEXTMODE_BUFFER, NULL); SetConsoleActiveScreenBuffer(hHandle); CONSOLE_SCREEN_BUFFER_INFO sBufferInfo; GetConsoleScreenBufferInfo(hHandle, &sBufferInfo); nScreenWidth = sBufferInfo.dwSize.X; nScreenHeight = sBufferInfo.dwSize.Y; CHAR_INFO* sBuffer = new CHAR_INFO[nScreenHeight * nScreenWidth]; newBuffer(sBuffer); //OFFSET AND LENGTH ARRAY AND INITIALIZING IT int* arrOffset = new int[nScreenWidth]; int* arrLength = new int[nScreenWidth]; for (int i = 0; i < nScreenWidth; i++) { arrOffset[i] = nOffset(); arrLength[i] = nLength(); } //CREATING RECT FOR CONSOLE OUTPUT SMALL_RECT sRect = { 0, 0, (SHORT)nScreenWidth, (SHORT)nScreenHeight }; srand((unsigned int)time(0)); //SETS SEED SO THAT WE DON'T GET SAME RANDOM VALUE bool qConsole = false; while (!qConsole) { sResponseReset(hHandle, &sBufferInfo, sBuffer, &sRect); //CHECKS IF THE UPPER AND LOWER CELL ARE SAME AND PROPOGATES COLOUR for (int i = 0; i < nScreenWidth; i++) { for (int j = nScreenHeight - 1; j > 0; j--) { if (sBuffer[(j-1) * nScreenWidth + i].Attributes != sBuffer[(j+1) * nScreenWidth + i].Attributes) { sBuffer[(j + 1) * nScreenWidth + i].Attributes = sBuffer[j * nScreenWidth + i].Attributes; sBuffer[j * nScreenWidth + i].Attributes = sBuffer[(j - 1) * nScreenWidth + i].Attributes; } } } for (int i = 0; i < nScreenWidth; i++) { if (arrOffset[i] > 0) { if (arrOffset[i] <= arrLength[i]) sBuffer[i].Attributes = FOREGROUND_GREEN; else { sBuffer[i].Attributes = 0; sBuffer[i].Char.UnicodeChar = randChar(); arrOffset[i] = nOffset(); arrLength[i] = nLength(); } } arrOffset[i]++; } //CHANGE OUTPUT BUFFER AND MAKING IT 0.5 FRAME PER SECOND WriteConsoleOutput(hHandle, sBuffer, { (SHORT)nScreenWidth, (SHORT)nScreenHeight }, { 0,0 }, &sRect); this_thread::sleep_for(chrono::milliseconds(500)); } (void)getchar(); //CLOSING AND DELETING HANDLE AND ALLOCATED MEMORY CloseHandle(hHandle); delete[] sBuffer; delete[] arrOffset; delete[] arrLength; return 0; }
问题根源与修复方案
核心问题
- 指针传递错误:修改后的
sResponseReset函数中,CHAR_INFO* buffer是传值参数,函数内对buffer的赋值仅修改局部指针,不会同步到main函数中的sBuffer。原缓冲区被delete后,main仍使用野指针访问内存,导致程序崩溃/冻结。 - 窗口尺寸检测逻辑错误:使用
dwSize(控制台缓冲区大小)而非srWindow(控制台窗口可见区域大小)来判断窗口是否调整,两者并非同步变化,导致无法正确触发重置逻辑。 - 辅助数组未同步更新:
arrOffset和arrLength数组的大小依赖于nScreenWidth,窗口宽度变化后数组长度不匹配,引发越界访问。
修复代码示例
// 修改sResponseReset的参数为引用,同步指针变化 void sResponseReset(HANDLE handle, CONSOLE_SCREEN_BUFFER_INFO* pBufferInfo, CHAR_INFO*& buffer, SMALL_RECT* pRect, int*& arrOffset, int*& arrLength) { GetConsoleScreenBufferInfo(handle, pBufferInfo); // 检测窗口可见区域变化,而非缓冲区大小 int newWidth = pBufferInfo->srWindow.Right - pBufferInfo->srWindow.Left + 1; int newHeight = pBufferInfo->srWindow.Bottom - pBufferInfo->srWindow.Top + 1; if (nScreenWidth != newWidth || nScreenHeight != newHeight) { nScreenWidth = newWidth; nScreenHeight = newHeight; // 重新分配缓冲区 CHAR_INFO* nBuffer = new CHAR_INFO[nScreenWidth * nScreenHeight]; newBuffer(nBuffer); delete[] buffer; buffer = nBuffer; // 重新分配辅助数组 int* newOffset = new int[nScreenWidth]; int* newLength = new int[nScreenWidth]; for (int i = 0; i < nScreenWidth; i++) { newOffset[i] = nOffset(); newLength[i] = nLength(); } delete[] arrOffset; delete[] arrLength; arrOffset = newOffset; arrLength = newLength; // 更新输出矩形 pRect->Right = (SHORT)nScreenWidth; pRect->Bottom = (SHORT)nScreenHeight; } } // main函数中调用方式修改为: sResponseReset(hHandle, &sBufferInfo, sBuffer, &sRect, arrOffset, arrLength);
代码架构优化建议
- 封装状态到类:将屏幕尺寸、缓冲区、辅助数组、控制台句柄等状态封装为
MatrixConsole类,避免全局变量,提高代码可维护性。 - 使用智能指针/容器替代裸指针:用
std::unique_ptr<CHAR_INFO>管理缓冲区,std::vector<int>替代动态数组,无需手动delete,避免内存泄漏。 - 多线程输入处理:启动独立线程监听键盘输入(如
_getch()),设置退出标志位,避免主线程被输入阻塞。 - 优化随机数生成:替换
rand()为C++11的<random>库(如std::mt19937),生成更均匀的随机字符和参数。 - 帧率精确控制:使用
std::chrono::high_resolution_clock计算帧间隔,替代固定sleep,保证帧率稳定。 - 模块化拆分:将初始化、渲染、窗口重置、输入处理等逻辑拆分为独立成员函数,提高代码可读性。
- 错误处理:为Win32 API调用添加错误检查(如
GetLastError()),便于调试问题。
内容的提问来源于stack exchange,提问作者B1smuth
相关产品推荐
相关产品推荐

