You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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;
}

问题根源与修复方案

核心问题

  1. 指针传递错误:修改后的sResponseReset函数中,CHAR_INFO* buffer是传值参数,函数内对buffer的赋值仅修改局部指针,不会同步到main函数中的sBuffer。原缓冲区被delete后,main仍使用野指针访问内存,导致程序崩溃/冻结。
  2. 窗口尺寸检测逻辑错误:使用dwSize(控制台缓冲区大小)而非srWindow(控制台窗口可见区域大小)来判断窗口是否调整,两者并非同步变化,导致无法正确触发重置逻辑。
  3. 辅助数组未同步更新: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);

代码架构优化建议

  1. 封装状态到类:将屏幕尺寸、缓冲区、辅助数组、控制台句柄等状态封装为MatrixConsole类,避免全局变量,提高代码可维护性。
  2. 使用智能指针/容器替代裸指针:用std::unique_ptr<CHAR_INFO>管理缓冲区,std::vector<int>替代动态数组,无需手动delete,避免内存泄漏。
  3. 多线程输入处理:启动独立线程监听键盘输入(如_getch()),设置退出标志位,避免主线程被输入阻塞。
  4. 优化随机数生成:替换rand()为C++11的<random>库(如std::mt19937),生成更均匀的随机字符和参数。
  5. 帧率精确控制:使用std::chrono::high_resolution_clock计算帧间隔,替代固定sleep,保证帧率稳定。
  6. 模块化拆分:将初始化、渲染、窗口重置、输入处理等逻辑拆分为独立成员函数,提高代码可读性。
  7. 错误处理:为Win32 API调用添加错误检查(如GetLastError()),便于调试问题。

内容的提问来源于stack exchange,提问作者B1smuth

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 07:37:02