使用 C 语言编写游戏的小伙伴们想必起初都要遇到这样的问题,在不断清屏输出数据的过程中,控制台中的输出内容会不断地闪屏。出现这个问题的原因是程序对数据处理花掉的时间影响到了数据显示,或许你可以使用局部覆盖更新方法(减少更新数据量)来缓解闪屏,但是这种方法并不适用于所有场合,尤其是更新数据本身就非常大的场合。

 

  本文将讲述解决控制台应用程序输出闪屏的终级解决方法——双缓冲。

 
问题呈现

  下面的代码演示了在高速不断清屏输出数据的过程的闪屏问题,特邀您一试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
 
int main()
{
    while (1)
    {
        for (char c='a'; c<'z'; c++)
        {
            system("cls");
            for (int i=0; i<800; i++)
            {
                printf("%c",c);
            }
        }
    }
}
 
不完全解决方案:局部覆盖更新

  本例代码将使用两个 Win32 API 函数,GetStdHandle、SetConsoleCursorPosition,

图例 名称 说明
HANDLE GetStdHandle(
  _In_  DWORD nStdHandle
);
获取标准设备句柄

nStdHandle 标准设备,可取值:
STD_INPUT_HANDLE (DWORD)-10,输入设备
STD_OUTPUT_HANDLE (DWORD)-11,输出设备
STD_ERROR_HANDLE (DWORD)-12,错误设备

调用返回:
成功,返回设备句柄(HANDLE);
失败,返回 INVALID_HANDLE_VALUE;
如果没有标准设备,返回 NULL。
BOOL SetConsoleCursorPosition(
  _In_  HANDLE hConsoleOutput,
  _In_  COORD dwCursorPosition
);
设置控制台光标位置

hConsoleOutput 控制台输出设备句柄
dwCursorPosition 光标位置
 

函数参数中使用到 COORD 结构体:

图例 名称 说明
X
SHORT X;
水平坐标或列值,从 0 开始
Y
SHORT X;
垂直坐标或行值,从 0 开始
 

  示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <Windows.h>
 
int main()
{
    HANDLE hOutput;
    COORD coord={0,0};
    hOutput=GetStdHandle(STD_OUTPUT_HANDLE);
 
    while (1)
    {
        for (char c='a'; c<'z'; c++)
        {
            SetConsoleCursorPosition(hOutput, coord);
            for (int i=0; i<800; i++)
            {
                printf("%c",c);
            }
        }
    }
}
 
闪屏原因及解决方案

  首先,要说明的是,只有“显示缓存区”里面的数据才会被显示。默认的控制台应用程序的显示结构是这样的:

 

  在输出大量数据的时候,由于数据经过处理需要时间,导致数据到达显示缓存区时出现了先后顺序。即是说显示器在显示数据时,可能只有部分显示数据到达了显示缓存区,而其他数据还没有到达,从而使图像按部分呈现最终显示完整。这是更新大量显示数据出现闪屏的根本原因。

 
完全解决方案:使用双缓冲技术

  在图形处理编程过程中,双缓冲是基本技术之一,它是解决闪屏的有效解决方案。尤其在游戏编程领域,双缓冲技术得到了广泛地应用。

 

  如此看来,看似揪心的问题,其实我们只需要多一个缓冲区就可以完全解决这个问题。如果应用了双缓冲技术,那么这个控制台程序的结构将会有点变化:

 

  由于默认的缓冲区有标准输入输出流的支持,所以为了输入输出的方便,我们将默认的显示缓冲区作为后台缓冲区,而将新建的显示缓冲区作为活动的屏幕显示。基本过程是,先将要显示的数据传输到默认缓冲区,等到数据全部写入后,再一次性填充到新建的显示缓存区。

 

  为了实现这个过程,我们还需要调用几个 Win32 API(CreateConsoleScreenBuffer、SetConsoleActiveScreenBuffer、SetConsoleCursorInfo、ReadConsoleOutputCharacterA、WriteConsoleOutputCharacterA),

图例 名称 说明
HANDLE WINAPICreateConsoleScreenBuffer(
  _In_        DWORD dwDesiredAccess,
  _In_        DWORD dwShareMode,
  _In_opt_    const SECURITY_ATTRIBUTES *lpSecurityAttributes,
  _In_        DWORD dwFlags,
  _Reserved_  LPVOIDlpScreenBufferData
);
创建控制台显示缓冲

dwDesiredAccess,控制台缓冲安全与访问权限,可取值:
GENERIC_READ (0x80000000L),读权限
GENERIC_WRITE (0x40000000L),写权限

dwShareMode,共享模式,可取值:
FILE_SHARE_READ,读共享
FILE_SHARE_WRITE,写共享

lpSecurityAttributes,安全属性,NULL

dwFlags,缓冲区类型,仅可选:CONSOLE_TEXTMODE_BUFFER,控制台文本模式缓冲

lpScreenBufferData,保留,NULL
BOOL WINAPISetConsoleActiveScreenBuffer(
  _In_  HANDLE hConsoleOutput
);
设置控制台活动显示缓冲

hConsoleOutput,控制台输出设备句柄
BOOL WINAPISetConsoleCursorInfo(
  _In_  HANDLE hConsoleOutput,
  _In_  const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);
设置控制台光标信息

hConsoleOutput,控制台输出设备句柄
lpConsoleCursorInfo,光标信息(大小、可见性)
BOOL WINAPIReadConsoleOutputCharacterA(
  _In_  HANDLE hConsoleOutput,
  _Out_ LPTSTR lpCharacter,
  _In_  DWORD nLength,
  _In_  COORD dwReadCoord,
  _Out_ LPDWORDlpNumbersOfCharsRead
);
读取控制台输出到字符数组

hConsoleOutput,控制台输出设备句柄
lpCharacter,保存的字符数组指针
nLength,读取长度dwReadCoord,读取起始坐标lpNumbersOfCharsRead,实际读取长度
BOOL WINAPIWriteConsoleOutputCharacterA(
  _In_  HANDLE hConsoleOutput,
  _In_ LPTSTR lpCharacter,
  _In_  DWORD nLength,
  _In_  COORD dwWriteCoord,
  _Out_ LPDWORDlpNumberOfCharsWritten
);
写入字符数组到控制台输出

hConsoleOutput,控制台输出设备句柄
lpCharacter,写入的字符数组指针
nLength,写入长度dwWriteCoord,写入起始坐标lpNumberOfCharsWritten,实际写入长度
 

函数参数中使用到 CONSOLE_CURSOR_INFO 结构体:

图例 名称 说明
dwSize
DWORD dwSize;
光标大小,在范围 1 到 100 中取值。
bVisible
BOOL bVisible;
可见性,可取值:
FALSE,0,不可见;TRUE,1,可见。
 

  示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <stdio.h>
#include <Windows.h>
 
int main(){
    //获取默认标准显示缓冲区句柄
    HANDLE hOutput;
    COORD coord={0,0};
    hOutput=GetStdHandle(STD_OUTPUT_HANDLE);
 
    //创建新的缓冲区
    HANDLE hOutBuf = CreateConsoleScreenBuffer(
        GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        CONSOLE_TEXTMODE_BUFFER,
        NULL
    );
 
    //设置新的缓冲区为活动显示缓冲
    SetConsoleActiveScreenBuffer(hOutBuf);
 
    //隐藏两个缓冲区的光标
    CONSOLE_CURSOR_INFO cci;
    cci.bVisible=0;
    cci.dwSize=1;
    SetConsoleCursorInfo(hOutput, &cci);
    SetConsoleCursorInfo(hOutBuf, &cci);
 
    //双缓冲处理显示
    DWORD bytes=0;
    char data[800];
    while (1)
    {
        for (char c='a'; c<'z'; c++)
        {
            system("cls");
            for (int i=0; i<800; i++)
            {
                printf("%c",c);
            }
            ReadConsoleOutputCharacterA(hOutput, data, 800, coord, &bytes);
            WriteConsoleOutputCharacterA(hOutBuf, data, 800, coord, &bytes);
        }
    }
    return 0;
}

双缓冲解决控制台应用程序输出“闪屏”(C/C++,Windows)的更多相关文章

  1. MFC双缓冲解决图象闪烁[转]

    转载网上找到的一篇双缓冲的文章,很好用.http://www.cnblogs.com/piggger/archive/2009/05/02/1447917.html__________________ ...

  2. VC++绘图时,利用双缓冲解决屏幕闪烁 转载

    最近做中国象棋,绘制界面时遇到些问题,绘图过程中屏幕闪烁,估计都会想到利用双缓冲来解决问题,但查了下网上双缓冲的资料,发现基本是MFC的,转化为VC++后,大概代码如下: void DrawBmp(H ...

  3. C# 控制台应用程序输出颜色字体[更正版]

    首先感谢院子里的“yanxinchen”,之前的方法是通过c#调用系统api实现的,相比之下我的有点画蛇添足了,哈哈. 最佳解决方案的代码: static void Main(string[] arg ...

  4. vue cavnas绘制矩形,并解决由clearRec带来的闪屏问题

    起因:在cavnas绘制矩形时 鼠标移动一直在监测中,所以鼠标移动的轨迹会留下一个个的矩形框, 要想清除矩形框官方给出了ctx.clearRect() 但是这样是把整个画布给清空了,因此需要不断 向画 ...

  5. JQuery Mobile - 解决切换页面时,闪屏,白屏等问题

    在点击链接,切换页面时候,总是闪屏,感觉很别扭,看起来不舒服,怎么解决这个问题?方法很简单,就是在每个页面的meta标签内定义user-scalable的属性为 no! <meta name=& ...

  6. C# 控制台应用程序输出颜色字体

    最佳解决方案的代码: static void Main(string[] args) { Console.ForegroundColor = ConsoleColor.Green; Console.W ...

  7. Delphi 使用双缓冲解决图片切换时的闪烁问题 good

    var TempCanvas: TCanvas; BufDC: HDC; BufBitmap: HBITMAP; begin // 创建一个与显示设备兼容的内存设备 BufDC := CreateCo ...

  8. vscode环境配置(三)——解决控制台终端中文输出乱码

    由于系统终端默认编码为GBK,所以需要修改为UTF-8 方法一 打开cmd输入chcp查看编码格式,查看以及修改如下图所示: 方法二

  9. MFC框架下Opengl窗口闪屏问题解决方案

    转自https://blog.csdn.net/niusiqiang/article/details/43116153 虽然启用了双缓冲,但是仍然会出闪屏的情况,这是由于OpenGL自己有刷新背景的函 ...

随机推荐

  1. duilib 给List表头增加百分比控制宽度的功能

    转载请说明原出处,谢谢~~:http://blog.csdn.net/zhuhongshu/article/details/42503147 最近项目里需要用到包含表头列表,而窗体大小改变后,每个列表 ...

  2. 题解 P2762 【太空飞行计划问题】

    P2762 太空飞行计划问题 题目描述 W 教授正在为国家航天中心计划一系列的太空飞行.每次太空飞行可进行一系列商业性实验而获取利润.现已确定了一个可供选择的实验集合E={E1,E2,-,Em},和进 ...

  3. 题解 P3153 【[CQOI2009]跳舞】

    P3153 [CQOI2009]跳舞 题目描述 一次舞会有n个男孩和n个女孩.每首曲子开始时,所有男孩和女孩恰好配成n对跳交谊舞.每个男孩都不会和同一个女孩跳两首(或更多)舞曲.有一些男孩女孩相互喜欢 ...

  4. Python学习笔记(四十九)爬虫的自我修养(一)

    论一只爬虫的自我修养 URL的一般格式(带括号[]的为可选项): protocol://hostname[:port]/path/[;parameters][?query]#fragment URL由 ...

  5. linux的MySQL设为开机启动

    linux开启启动的程序一般放在/etc/rc.d/init.d/里面,/etc/init.d/是其软连接 mysql设为linux服务cp /usr/local/mysql5/share/mysql ...

  6. 说说asp.net中的异常处理和日志追踪

    关于异常的处理想必大家都了解try{}catch(){}finally{},这里就不再讲了.通过在VS里的"调试"-"异常",在弹出的异常对话框里的Common ...

  7. 【BZOJ】4756: [Usaco2017 Jan]Promotion Counting

    [题意]带点权树,统计每个结点子树内点权比它大的结点数. [算法]线段树合并 [题解]对每个点建权值线段树(动态开点),DFS中将自身和儿子线段树合并后统计. 注意三个量tot,cnt,tots,细心 ...

  8. 下载Google My Tracks

    http://code.google.com/p/mytracks/source/browse/?name=2.0.2#hg%2FMyTracks%253Fstate%253Dclosed 需要类似的 ...

  9. LintCode之二叉树的最大节点

    分治问题,可以把整棵树看做是由一颗颗只有三个节点组成的小树,一颗树的构成是根节点.左子树.右子树,这样只需要从左子树找出一个最大的节点,从右子树找出一个最大的节点,然后与根节点三个取个最大的,就是最终 ...

  10. jQuery.pin.js笔记

    jQuery.pin.js是一个把元素钉在页面上某个位置的插件,它能够将某个元素一直挂在一个固定的位置而不论滚动条是否滚动. 特点: 1. 可以钉住一个元素,主要作用就是滚动超出的时候不会隐藏而是一直 ...