雙緩沖解決控制台應用程序輸出“閃屏”(C/C++,Windows)


使用 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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM