Console學習

1.Console Handle(控制檯句柄)c++

1.1 進程的三種標準句柄windows

    每一個console進程都有standard input(STDIN), standard output(STDOUT), standard error(STDERR)三種句柄與之相關聯,當系統建立console進程時,系統默認地將該進程的STDIN與該進程的控制檯的輸入緩衝區(input buffer)相關聯,將該進程的STDOUT,STDERR與該進程的控制檯的活動屏幕緩衝區(active screen buffer)相關聯,也就是說standard input(STDIN), standard output(STDOUT), standard error(STDERR)三種句柄自己是與進程相關的,它們與進程的控制檯的輸入/輸出緩衝區沒有關聯!既然如此,咱們的程序可使用 SetStdHandle 來對進程的三種標準句柄進行重定向,好比能夠重定向到文件等。注:咱們可使用 GetStdHandle 來得到當前進程的三種標準句柄。cors

1.2 控制檯的輸入緩衝區(input buffer)和活動屏幕緩衝區(active screen buffer)函數

    進程能夠經過 CrateFile 函數來得到與該進程相關的控制檯的輸入緩衝區(input buffer)和活動屏幕緩衝區(active screen buffer),Use the CONIN$ (lpFileName of CreateFile) value to specify console input. Use the CONOUT$(lpFileName of CreateFile) value to specify console output. CONIN$ gets a handle to the console input buffer, even if the SetStdHandle function redirects the standard input handle. To get the standard input handle, use the GetStdHandle function. CONOUT$ gets a handle to the active screen buffer, even if SetStdHandle redirects the standard output handle. To get the standard output handle, use GetStdHandle.spa

1.3 建立新的屏幕緩衝區設計

    進程可使用 CreateConsoleScreenBuffer 來建立一個新的屏幕緩衝區,使用 SetConsoleActiveScreenBuffer 來從新設置console screen buffer.指針

    Note that changing the active screen buffer does not affect the handle returned by GetStdHandle. Similarly, using SetStdHandle to change the STDOUT handle does not affect the active screen buffer.code

2.得到 Console Handleblog

應用程序能夠經過下面了的代碼得到Console輸入,輸出緩衝區的句柄進程

 1 HANDLE hinput = INVALID_HANDLE_VALUE,houtput = INVALID_HANDLE_VALUE;
 2 if(INVALID_HANDLE_VALUE == (hinput = CreateFile(TEXT("CONIN$"), GENERIC_READ, 
 3     FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL)))
 4 {    
 5     return -1;
 6 }    
 7 if(INVALID_HANDLE_VALUE == (houtput = CreateFile(TEXT("CONOUT$"), GENERIC_WRITE,
 8     FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL)))
 9 {    
10     return -1;
11 }

CUI程序在啓動時其進程默認的標準輸入和標準輸出,標準錯誤句柄是關聯到,控制檯的輸入,輸出緩衝區的,咱們能夠經過 GetStdHandle 方法來得到。

HANDLE stdinput, stdoutput, stderr;

stdinput = GetStdHandle(STD_INPUT_HANDLE);

stdoutput = GetStdHandle(STD_INPUT_HANDLE);

stderr = GetStdHandle(STD_ERROR_HANDLE);

考慮此時,理論上應該有stdinput == hinput, stdoutput == houtput, stderr == houtput,然而,事實上,卻並不是如此。控制檯的輸入和輸出緩衝區至關於兩種資源,系統並無使用指針來讓咱們引用這些資源,目的是爲了保護這些資源。而句柄它是進程句柄表的ENTRY的index,因此儘管stdinput,stdoutput,stderr和houtput,hinput不相同,可是其內部的的指針卻必定是指向相同的對應的緩衝區地址的,證實以下:

 1 #include"../../Common/UnicodeSupport.h"
 2 #include<windows.h>
 3 #include<stdio.h>
 4 int _tmain()
 5 {
 6      HANDLE hinput = INVALID_HANDLE_VALUE,houtput = INVALID_HANDLE_VALUE;
 7     if(INVALID_HANDLE_VALUE == (hinput = CreateFile(TEXT("CONIN$"), GENERIC_READ, 
 8        FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL)))
 9     {    
10         return -1;
11     }
12     if(INVALID_HANDLE_VALUE == (houtput = CreateFile(TEXT("CONOUT$"),     GENERIC_WRITE,FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL)))
13     {    
14         return -1;
15     }
16     if(GetStdHandle(STD_INPUT_HANDLE) != hinput)
17     {
18         _tprintf_s(TEXT("input handle is not same\n"));
19     }
20     if(GetStdHandle(STD_OUTPUT_HANDLE) != houtput)
21     {
22         _tprintf_s(TEXT("output handle is not same\n"));
23     }
24     LPCTSTR lpBuffer = TEXT("hello, world");
25     DWORD nums;
26     WriteConsole(houtput, lpBuffer, 13, &nums, NULL);
27     WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), lpBuffer, 13, &nums, NULL);
28     _tprintf_s(TEXT("\nhoutput = %x stdoutput = %x\n"), houtput, GetStdHandle(STD_OUTPUT_HANDLE));
29     _tprintf_s(TEXT("hinput = %x stdinput = %x\n"), hinput, GetStdHandle(STD_INPUT_HANDLE));
30     return 0;
31 }    

//運行以上程序,輸出的結果以下:

input handle is not same

output handle is not same

hello, world hello, world

houtput = 13 stdoutput = b

binput = 7 stdinput = 3

------------------------------------------------------------------------

1.4 關於 CRT(c/c++ runtime library)中Stream I/O中的stdout,stdin,stderr和每一個console進程都有standard input(STDIN), standard output(STDOUT), standard error(STDERR)三種句柄之間的關係。

struct _iobuf {

        char *_ptr; 

        int   _cnt;

        char *_base; // 待輸出的字符串的地址

        int   _flag;

        int   _file;

        int   _charbuf;

        int   _bufsiz;

        char *_tmpfname;

        };

typedef struct _iobuf FILE;

在stdio.h文件中咱們找到了以下定義:

_CRTIMP extern FILE _iob[];

#define stdin  (&_iob[0])

#define stdout (&_iob[1])

#define stderr (&_iob[2])

能夠看出 stdin, stdout, stder 都是 FILE* 類型,也就是 struct _iobuf* 類型, 顧名思義,FILE 類型的變量中存放着 I/O Stream 的緩衝區信息(緩衝區基址,緩衝區的大小,待輸出字符串的大小等).

CRT(c/c++ runtime library)中Stream I/O中的stdout,stdin,stderr和每一個console 進程都有的 standard input(STDIN), standard output(STDOUT), standard error(STDERR)三種句柄之間的關係是沒有任何關係,就是指它們之間並不存在這某種映射關係,使得stdout變化時STDOUT也發生變化。

可是,當咱們對CRT中的stdout, stdin, stderr進行重定向的時候,C-RunTime中Stream I/O將對進程的三種標準句柄(STDIN,STDOUT,STDERR)也進行重定向,使其正確地定向到CRT中定向的文件中去。而當咱們對進程的三種標準句柄(STDIN,STDOUT,STDERR)進行重定向時,CRT中的stdout,stdin,stderr不會進行重定向。

從邏輯角度上來理解這種重定向行爲,Mircorsoft的控制檯(Console)API先於其C/C++運行時庫中的流類(I/O Stream)API被設計和開發出來,而且C/C++運行時庫中的流類(I/O Stream)API其實是對控制檯(Console)API的二次封裝。控制檯(Console)API中的函數(例如:進行重定向的函數:SetStdHandle),天然不會知道C/C++運行時庫中的流類(I/O Stream)API中的任何細節,因此當咱們對進程的三種標準句柄(STDIN,STDOUT,STDERR)進行重定向時(使用SetStdHandle),CRT中的stdout,stdin,stderr不會進行重定向,這是天然的。而當設計C/C++運行時庫中的流類(I/O Stream)API時,其提供的函數freopen,天然能夠調用SetStdHandle對進程的標準句柄進行重定向。

C/C++運行時庫中的流類(I/O Stream)API設計思路推測:

printf等輸出函數,使用默認的 stdout(FILE*) 進行輸出時,其底層必然使用 CreateFile(TEXT("CONOUT$"),GENERIC_READ,FILE_SHARE_READ | FILE_SHARE_WRITE,NULL,OPEN_EXISTING, 0, NULL)得到屏幕緩衝區句柄進行輸出(使用WriteFile)。

如下代碼,對上述推斷進行了證實:

 1 //使用Console API SetStdHandle 進行重定向
 2     if(!SetStdHandle(STD_OUTPUT_HANDLE, CreateFile(TEXT("filex.txt"), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)))
 3     {
 4         _tprintf_s(TEXT("set stdhandle fail.\n"));
 5     }
 6     LPCTSTR lpBuffer = TEXT("hello, world");
 7     DWORD nums;
 8     WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), lpBuffer, 13 * sizeof(TCHAR), &nums, NULL);
 9     _tprintf_s(TEXT("I'm in child process.\n"));
10     _ftprintf_s(stdout, TEXT("This is go to file."));
11 //注意:以上代碼使用SetStdHandle進行重定向後,_tprintf_s和_ftprintf_s仍在屏幕上輸出
 1 //使用CRT流類API _tfreopen_s 進行重定向
 2 LPCTSTR lpBuffer = TEXT("hello, world");
 3 DWORD nums;    
 4     FILE *fwrite;
 5     _tfreopen_s(&fwrite, TEXT("file.txt"), TEXT("w"), stdout);
 6     _tprintf_s(TEXT("I'm in child process.\n"));
 7     _ftprintf_s(fwrite, TEXT("This is go to file."));
 8     
 9     if(!WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), lpBuffer, 13, &nums, NULL))
10     {_tprintf_s(TEXT("stdoutput fail."));}
11     WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), lpBuffer, 13 * sizeof(TCHAR), &nums, NULL);
12 //注意: 紅色代碼執行後也將字符串輸出到文件file.txt中,可見_tfreopen_s的確對Console的STDOUT也進行了重定向

1.4

當使用如下代碼建立新的Console進程時

CreateProcess(TEXT("child.exe"), NULL, NULL, NULL, FALSE,

    DETACHED_PROCESS, NULL, NULL, &si, &pi);

注意標記DETACHED_PROCESS,它表示 for console processes, the new process does not inherit its parent's console (the default), and the new process is not attached to any console.So the new process can call the AllocConsole function at a later time to create a console.因爲在建立新的進程時使用了DETACHED_PROCESS,則新進程在進行C/C++運行時庫初始化時天然沒法將stdout,stdin,stderr與有效地控制檯(Console)輸入/輸出句柄進行關聯,從而使得當使用AllocConsole建立新的控制檯(Console)以後,仍然不能使用printf這樣的函數(高級流類API,由於printf類函數默認使用stdout進行輸出,然而stdout此時倒是無效的。),此時向控制檯輸出函數是能夠有兩種方法:

方法一:使用CreateFile and WriteConsole

 1     HANDLE houtput;
 2     if(INVALID_HANDLE_VALUE == (houtput = CreateFile(TEXT("CONOUT$"), GENERIC_WRITE,
 3         FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL)))
 4     {    
 5         _tprintf_s(TEXT("get console output handle fail."));
 6         return -1;
 7     }
 8     LPCTSTR lpBuffer = TEXT("write go to child.txt.\n");
 9     DWORD nums;
10     WriteConsole(houtput, lpBuffer, lstrlen(lpBuffer), &nums, NULL);

方法二:使用 Console and Port I/O

例如方法 _cprintf, _cscanf 等等,這類函數直接向當前Console寫入值。實際上這些方法內部會使用方法一來完成真正的功能!

方法三:對 stdin, stdout, stderr 進行重定向,從而使其有效,讓後就可以使用printf類API了。

1 FILE *fwrite;
2     _tfreopen_s(&fwrite, TEXT("child.txt"), TEXT("w"), stdout);
3     _tprintf_s(TEXT("child process.\n"));

文件句柄和FILE*之間的轉換

 1 FILE* HANDLE2FILE(HANDLE hFile)
 2 {
 3     FILE *pfile = reinterpret_cast<FILE*>(malloc(sizeof(FILE)));
 4     if(!pfile)
 5     {
 6         return NULL;
 7     }
 8     
 9     int descriptor = _open_osfhandle(reinterpret_cast<intptr_t>(hFile), _O_TEXT | _O_APPEND);
10     if(-1 == descriptor)
11     {
12         return NULL;
13     }
14     
15     pfile = _tfdopen(descriptor, TEXT("w+"));
16     if(!pfile)
17     {
18         return NULL;
19     }
20     
21     return pfile;
22 }
相關文章
相關標籤/搜索