2024年3月27日发(作者:)

XDE(Xanavi Development Enveriment)的在PC机上运行的时候,除了模拟导航仪屏

幕的对话框窗口外,还有一个用来协助调试的控制台(console)窗口。这个console窗口可

以用来输入调试命令,也可以进行打屏输出以观察程序的运行状态,非常方便。这样做有两

个好处:一、console窗口的输入输出速度比一般的windows窗口要快;二、console窗口的

输入输出不会对消息流产生影响(你知道,就像dos窗口不知道消息的存在一样,console

窗口也具有这样的特性),而在一般的windows窗口上进行输入输出时,会打乱原有的消息

流(因为会有一大堆的像WM_PAINT、WM_GETFOCUS这样与鼠标、焦点、键盘、刷新

相关的消息产生)。对于调试与消息相关的程序来说(比如像XDE这样拥有自己消息控制

的程序),console这样的特性是非常重要的。

在windows系统下使用C/C++编程的时候,我们一般会有两种选择,一个是做成类似

DOS字符界面样式的程序(这就是console程序),这种程序的入口函数是main(对于

UNICODE则是wmain),另一种是做成win32窗口程序,这样的程序会产生标准的windows

窗口,它的入口函数是WinMain 。一般来讲,一个win32的窗口程序在默认状态下是不会

产生console窗口的。

但是,对于一个win32窗口程序,我们可以在运行期间给这个程序添加一个控制台窗口。

有几个win32的API是用来实现这个功能的:AllocConsole,FreeConsole以及与Console相

关的Get和Set系列函数。AllocConsole用来产生一个窗口,FreeConsole用来销毁它。下面

我们来具体地看看每一个函数的作用。

与Console窗口相关的API函数介绍:

BOOL AllocConsole(void)

这个函数为调用它的进程产生一个console窗口,如果成功,就返回非0值;否则,返

回0。要注意的是,每个进程最多只有拥有一个console的窗口(但多个进程可能同时拥有

一个console窗口,比如子进程可以共享父进程的console窗口),如果这个进程已经有一个

console窗口了,那么再调用AllocConsole的时候它会返回0。

BOOL FreeConsole(void)

销毁进程所拥有的console窗口(如果这个console是被多个进程共同拥有,那么它不

会被Free掉,但是调用此函数的进程将无法再访问到这个console了)。成功返回非0值,

否则返回0 。

HANDLE GetStdHandle(

DWORD nStdHandle

);

nStdHandle:取 STD_INPUT_HANDLE、STD_OUTPUT_HANDLE、STD_ERROR_HANDLE

三个值中的一个。

STD_INPUT_HANDLE:标准输入设备句柄

STD_OUTPUT_HANDLE:标准输出设备句柄

STD_ERROR_HANDLE:标准错误输出设备句柄。

这个函数用来获取标准输入、输出、错误输出设备的句柄。这些句柄是某些console相关的

函数上会用到,比如下面几个函数。

BOOL WriteConsole(

HANDLE hConsoleOutput, // 控制台窗口输出句柄

const VOID* lpBuffer, // 需要输出的字符所在的buffer address

DWORD nNumberOfCharsToWrite, // 需要输出的字符个数

LPDWORD lpNumberOfCharsWritten, // 实际被输出的字符个数

LPVOID lpReserved // 保留用,必须为NULL

);

这个函数用来向console窗口的输入缓冲区中写入字符串。

BOOL WriteConsoleInput(

HANDLE hConsoleInput, // 控制台窗口输出句柄

const INPUT_RECORD* lpBuffer, // 需要输出的字符所在的buffer address

DWORD nLength, // 需要输出的字符个数

LPDWORD lpNumberOfEventsWritten // 实际被输出的字符个数

);

功能与上一个函数类似,只是它不写入缓冲区而是直接写到输出设备上。

BOOL ReadConsole(

HANDLE hConsoleInput, // 控制台窗口输入句柄

LPVOID lpBuffer, // 需要输入的字符存放的buffer address

DWORD nNumberOfCharsToRead, // 需要输入的字符个数

LPDWORD lpNumberOfCharsRead, // 实际被输入的字符个数

LPVOID lpReserved // 保留用,必须为NULL

);

这个函数用来从console窗口的输入字符。

BOOL ReadConsoleInput(

HANDLE hConsoleInput, // 控制台窗口输入句柄

PINPUT_RECORD lpBuffer, // 需要输入的字符存放的buffer address

DWORD nLength, // 需要输入的字符个数

LPDWORD lpNumberOfEventsRead // 实际被输入的字符个数

);

功能与上一个函数类似,如果输入函数中包含除键盘之外的输入事件(如窗口的大小变化、

鼠标事件)时,用ReadConsole 无法读入,只能使用ReadConsoleInput 读入。

虽然使用这几个API可以为win32产生一个控制台窗口,并在这个窗口中进行输入和

输出。但是仅仅这样做的话,便不能使用标准C函数库的输入输出系列的函数(如printf、

scanf、gets等),而只能使用WriteConsole这类win32函数向这个窗口进行输出,使用

ReadConsole这类win32函数来从控制台窗口得到输入。但是这样会有一些问题:最重要的

是WriteConsole、ReadConsole等几个函数没有printf系列的函数那样方便灵活,因为它们

不能像printf系列函数那样对字符串进行格式化,而且WriteConsole、ReadConsole等几个

函数的输入参数使用起来也不那么方便,比如要得到控制台窗口的句柄等等。另外,对于构

建在系统的上层的部分(如HMI)来说,使用标准C是一个很重要的选择――这样HMI层

(包括它的调试代码部分)才能与平台无关。所以我们需要将标准C的输入输出流与生成

的控制台窗口绑定到一起去,或者说,把标准C的输入输出流重定向到用AllocConsole生

成的控制台窗口中去。

标准C的输入输出在内部是使用stdin、stdout、stderr来进行的。这三者实际上是三个

FILE *类型的指针。这三个指针所指向的三个FILE类型的数据存储着标准输入输出流的对