天天看點

控制台雙緩沖技術控制台雙緩沖技術

控制台雙緩沖技術

前言

在控制台重繪中,由于界面的不斷重新整理,顯示部分會有一定的閃爍,解決這個問題的辦法就是雙緩沖技術,由兩個緩沖區互相輸出,進而達到螢幕無閃爍的效果

相關函數

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統一輸出,使用者不會感覺到閃爍。

繼續閱讀