控制台雙緩沖技術
前言
在控制台重繪中,由于界面的不斷重新整理,顯示部分會有一定的閃爍,解決這個問題的辦法就是雙緩沖技術,由兩個緩沖區互相輸出,進而達到螢幕無閃爍的效果
相關函數
CreateConsoleScreenBuffer
建立一個螢幕緩沖區
聲明
HANDLE WINAPI CreateConsoleScreenBuffer(
_In_ DWORD dwDesiredAccess, //權限
_In_ DWORD dwShareMode, //共享模式
_In_opt_ const SECURITY_ATTRIBUTES *lpSecurityAttributes, //安全級别
_In_ DWORD dwFlags, //辨別符
_Reserved_ LPVOID lpScreenBufferData //保留,為NULL
);//建立螢幕緩沖區
參數
dwDesiredAccess
緩沖區權限,有以下可選值
名稱 | 值 | 含義 |
---|---|---|
GENERIC_READ | 0x80000000 | 訓示緩沖區可讀 |
GENERIC_WRITE | 0x40000000 | 訓示緩沖區可寫 |
dwShareMode
分享權限
名稱 | 值 | 含義 |
---|---|---|
FILE_SHARE_READ | 0x00000001 | 緩沖區可共享讀 |
FILE_SHARE_WRITE | 0x00000002 | 緩沖區可共享寫 |
lpSecurityAttributes
指向安全屬性的指針,如不是NULL,将訓示一個SECURITY_ATTRIBUTES結構體,若是NULL,則該句柄無法被子程序繼承
dwFlags
辨別符,指建立緩沖區的類型,目前隻支援一種
名稱 | 值 | 含義 |
---|---|---|
CONSOLE_TEXTMODE_BUFFER | 0x00000001 | 文本模式輸出 |
lpScreenBufferData
該值保留,應為NULL
傳回值
若函數成功,則傳回指向緩沖區的一個句柄
若函數失敗,則傳回INVALID_HANDLE_VALUE(-1),若擷取更多的資訊請使用GetLastError
成功傳回的緩沖區以下屬性與現有緩沖區相同
- 字型 - 從目前活躍的緩沖區複制
-
- 顯示的視窗大小 - 從目前活躍的緩沖區複制
-
- 緩沖區大小 - 與顯示的視窗比對 (不複制)
-
- 預設屬性 (顔色) - 從目前活躍的緩沖區複制
-
- 預設彈出屬性 (顔色) - 從目前活躍的緩沖區複制
-
要求
名稱 | 值 |
---|---|
最小支援用戶端版本 | Windows 2000專業版[僅桌面應用程式] |
最小支援伺服器版本 | Windows 2000伺服器[僅桌面應用程式] |
頭檔案 | ConsoleApi2.h(通過Wincon.h,包含Windows.h) |
庫檔案 | Kernel32.lib |
DLL | Kernel32.dll |
SetConsoleActiveScreenBuffer
設定控制台活動緩沖區
聲明
BOOL WINAPI SetConsoleActiveScreenBuffer(
_In_ HANDLE hConsoleOutput //緩沖區輸出句柄
);//設定控制台活動緩沖區
參數
hConsoleOutput
要設定的緩沖區輸出句柄
傳回值若函數成功,則傳回非零(TRUE)
若函數不成功,則傳回FALSE,若擷取更多的資訊請使用GetLastError
要求
名稱 | 值 |
---|---|
最小支援用戶端版本 | Windows 2000專業版[僅桌面應用程式] |
最小支援伺服器版本 | Windows 2000伺服器[僅桌面應用程式] |
頭檔案 | ConsoleApi2.h(通過Wincon.h,包含Windows.h) |
庫檔案 | Kernel32.lib |
DLL | Kernel32.dll |
CloseHandle
關閉指定打開的句柄
聲明
BOOL CloseHandle(
HANDLE hObject //指定的句柄
);//關閉打開的句柄
參數
hObject
指向打開對象的句柄
傳回值
若函數成功,則傳回非零
若函數失敗,則傳回0,若擷取更多的資訊請使用GetLastError
若程式在調試模式下進行,則失敗時抛出異常
失敗有以下幾種情況
- 句柄無效- 将同一句柄使用CloseHandle兩次
- 将應使用FindClose關閉的句柄使用CloseHandle關閉
标記
使用CloseHandle可關閉以下類型的句柄
- 權限令牌
- 互動裝置
- 控制台輸入
- 控制台螢幕緩沖區
- 事件 (Event)
- 檔案
- 檔案映射
- 輸入與輸出端口
- 任務(Job)
- 郵槽(Mailslot)
- 記憶體資源辨別
- 互斥量(Mutex)
- 被命名的管道
- 管道
- 程序
- 信号(Semaphore)
- 線程- Transaction
- 可等待的計時器
要求
名稱 | 值 |
---|---|
最小支援用戶端版本 | Windows 2000專業版[桌面應用程式 或 UWP程式] |
最小支援伺服器版本 | Windows 2000伺服器[桌面應用程式 或 UWP程式] |
頭檔案 | handleapi.h(包括Windows.h) |
庫檔案 | Kernel32.lib |
DLL | Kernel32.dll |
應用
以下代碼會有閃爍出現
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <Windows.h>
int main(int argc,char** argv){
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE),
hIn = GetStdHandle(STD_INPUT_HANDLE);
DWORD recnum,i = 30;
CONSOLE_SCREEN_BUFFER_INFO cinfo;
GetConsoleScreenBufferInfo(hOut,&cinfo);
srand((unsigned)time(NULL));
//隐藏光标
SetConsoleCursorInfo(hOut,(CONSOLE_CURSOR_INFO){1,0});
while (1){ //清屏
FillConsoleOutputAttributes(hOut,0,cinfo.dwSize.X*cinfo.dwSize.Y,(COORD){0,0},&recnum) //繪制一個邊框
SetConsoleTextAttribute(hOut,FOREGROUND_RED);
FillConsoleOutputCharacter(hOut,'-',(COORD){0,0},20,&recnum);
while (i--){
SetConsoleCursorPosition(hOut,(COORD){0,i+1}); WriteConsole(hOut,L"|",1,&recnum,NULL);
SetConsoleCursorPosition(hOut,(COORD){19,i+1});
WriteConsole(hOut,L"|",1,&recnum,NULL);
}
FillConsoleOutputCharacter(hOut,'-',(COORD){0,31},20); //在方框内随機出現一個方塊
SetConsoleCursorPosition(hOut,(COORD){rand() % 18 + 1,rand() % 30 + 1});
SetConsoleTextAttribute(hOut,BACKCOLOR_GREEN);
WriteConsole(hOut,L" ",1,&recnum,NULL);
Sleep(1000);
}
return 0;
}
以上代碼在每次清屏與繪制方塊時就會有閃爍
解決方法如下:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <Windows.h>
#define exchange(a,b,type) {type tmpvalue = a;a = b; b= tmpvalue}
int main(int argc,char** argv){
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE),
hIn = GetStdHandle(STD_INPUT_HANDLE),
sOut = CreateConsoleScreenBuffer(GENERIC_WRITE,0,NULL,CONSOLE_TEXTMODE_BUFFER,NULL);
//在這裡隻需寫,若需讀請加上GENERIC_READ
if (sOut==INVALID_HANDLE_VALUE){//建立失敗
printf("緩沖區建立失敗,錯誤号:%d",GetLastError());
return -1;
}
DWORD recnum,i = 30;
CONSOLE_SCREEN_BUFFER_INFO cinfo;
GetConsoleScreenBufferInfo(hOut,&cinfo); //在這裡不設定sOut緩沖區
srand((unsigned)time(NULL));
//隐藏光标
SetConsoleCursorInfo(hOut,(CONSOLE_CURSOR_INFO){1,0});
SetConsoleCursorInfo(sOut,(CONSOLE_CURSOR_INFO){1,0});//由于建立了緩沖區,該緩沖區也需隐藏光标
while (1){
//清屏,這回是sOut
FillConsoleOutputAttributes(sOut,0,cinfo.dwSize.X*cinfo.dwSize.Y,(COORD){0,0},&recnum);
//繪制一個邊框
SetConsoleTextAttribute(sOut,FOREGROUND_RED);
FillConsoleOutputCharacter(sOut,'-',(COORD){0,0},20,&recnum);
while (i--){
SetConsoleCursorPosition(sOut,(COORD){0,i+1});
WriteConsole(sOut,L"|",1,&recnum,NULL);
SetConsoleCursorPosition(sOut,(COORD){19,i+1});
WriteConsole(sOut,L"|",1,&recnum,NULL);
}
FillConsoleOutputCharacter(sOut,'-',(COORD){0,31},20);
//在方框内随機出現一個方塊
SetConsoleCursorPosition(sOut,(COORD){rand() % 18 + 1,rand() % 30 + 1});
SetConsoleTextAttribute(sOut,BACKCOLOR_GREEN);
WriteConsole(sOut,L" ",1,&recnum,NULL);
SetConsoleActiveScreenBuffer(sOut);//顯示緩沖區
exchange(hOut,sOut,HANDLE);//交換hOut與sOut
Sleep(1000);
}
//如果不是無限循環,在退出時需寫入以下代碼
FillConsoleOutputAttributes(sOut,0,cinfo.dwSize.X*cinfo.dwSize.Y,(COORD){0,0},&recnum);//清屏sOut
SetStdHandle(hOut,STD_OUTPUT_HANDLE);//可能這時的hOut不是标準輸出,需改為标準輸出
CloseHandle(sOut);//關閉sOut
return 0;
}
在上述代碼中,程式寫入sOut緩沖區,使用者不會看到立即的輸出,到SetConsoleActiveScreenBuffer時,sOut統一輸出,使用者不會感覺到閃爍。