⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 附录a-c.txt

📁 会变语言实现的一些程序
💻 TXT
📖 第 1 页 / 共 4 页
字号:
附录A


控制台程序

在Windows 操作系统中运行一个应用程序后,我们经常会看到两种界面,一种是标准的
窗口界面,窗口界面的程序架构在第4 章中已经有了详细的介绍;另一种是类似于MS-DOS 
程序的文本界面,如常用的Ping、Xcopy 等命令使用的都是这种界面,这种界面就叫做控制
台(Console),由于控制台在Windows 系统中还是以一个文本窗口的方式出现的,所以一般
将这个窗口称为控制台窗口。

从表面看,32 位的控制台程序和16 位的MS-DOS 应用程序在外观和表现上都是很相似
的,比如它们都是在一个黑洞洞的文本窗口中显示文本,都支持命令行下的重定向操作,读
取键盘的方式也是一样的。但是,在这个表象下面,两者却是完全不同的,DOS 应用程序是
16 位的实模式程序,而Windows 下的控制台程序却是不折不扣的32 位保护模式程序,它可
以使用Win32 API 函数,文件头中同样有导入表和导出表,可以在程序中建立多个线程执行。
总之,控制台程序是长着“DOS 程序面孔”的Win32 程序,可以使用Win32 编程中的所有特
征。

进一步来说,如果一定要让控制台程序有一个窗口的话,也可以在其中使用CreateWindow 
函数来创建一个窗口,这样控制台程序可以在使用终端界面输入输出的同时使用窗口上的菜单
来操作(但估计没有人会做这样的事情)。

控制台程序最主要的用途是用于网络的远程维护。进行远程维护时一般使用Telnet 等工
具登录到远程主机并在上面执行命令,如果执行的是图形界面的程序,这个界面是无法远程
操作的,所以我们可以发现Windows 中用于网络的命令大多数是控制台界面的,如Ping, 
Netstat,Tracert,Arp,Route,Ipconfig 和Finger 等,与此相比,很难想像类似于Office 这样
的软件会用在远程操作中。

作为对第4 章中窗口模式的补充,本节中将简单介绍控制台程序和窗口程序的区别,以
及控制台程序的写法。

A.1 控制台程序和窗口程序的区别
除了和界面相关的代码有所不同外,控制台程序和窗口程序的区别还在于链接的时候指
定参数的不同,读者一定还记得LINK 程序有个subsystem 参数,当这个参数指定为Windows 
的时候,链接器生成的是窗口程序,本书中绝大部分以窗口为界面的例子程序中,LINK 语
句是这样写的:



Link /subsystem:windows Test.obj Test.res


将subsystem 参数改为console 的时候,LINK 程序产生的就是控制台文件:

Link /subsystem:console Test.obj Test.res


两种参数生成的可执行文件的不同表现在文件头中,可执行文件(PE 文件)的文件头中
有一个IMAGE_OPTIONAL_HEADER32 结构,结构中的Subsystem 字段就记录了文件类型
的不同,读者可以在第17 章的17.1.3 节中看到对文件头的详细分析。

运行文件时,操作系统会检查文件头中的Subsystem 参数,如果发现参数的类型是窗口
文件,那么将文件以正常的方式运行;如果发现参数的类型是控制台文件,那么操作系统将
为程序创建一个控制台窗口(即类似于DOS 窗口的这个文本窗口),然后运行文件。

另外,当一个控制台程序是被另一个控制台程序作为子进程运行的时候,系统不会为它
创建新的控制台窗口,而是将父进程的窗口指定给它,所以在“我的电脑”中双击运行一个
控制台程序的时候,会出现一个新的控制台窗口,而在“命令提示符”窗口中用命令行参数
运行一个控制台程序的时候,程序会直接使用“命令提示符”的窗口。

我们可以用几个简单的实验来验证这一点。首先打开“命令提示符”,进入第4 章例子程
序的目录Chapter04\FirstWindow (这是一个普通的窗口程序而不是控制台程序),在命令行下
输入FirstWindow 来运行程序,程序运行后窗口出现了,但是不必等到窗口关闭,“命令提示
符”就会直接回到等待输入命令的状态,也就是说,普通的窗口程序并不会占用父进程的控
制台窗口。

现在修改Chapter04\FirstWindow 目录中的Makefile 文件,将LINK 命令的参数改成
/subsystem:console ,然后用nmake /a 重新编译,这样程序的代码没有任何变化,仅仅是它的
文件类型变成了控制台程序而已。

重复上面的步骤,在命令行下运行FirstWindow 程序,可以看到,程序运行后窗口出现
了,但是“命令提示符”处于等待状态,只有关闭窗口FirstWindow 程序,“命令提示符”中
才会回到等待输入的状态,这说明控制台程序的父进程如果也是控制台程序的时候,程序将
继承父进程的控制台窗口。

现在在“程序管理器”中通过双击FirstWindow.exe 文件来运行,一个正常窗口出现的同
时也出现了一个新的文本窗口,但是源代码中并没有创建过这个窗口呀?原来这个窗口就是
操作系统自动“搭配”给程序的控制台窗口,关闭窗口退出程序后,控制台窗口也同时消失。
这说明了当父进程不是控制台程序的时候,操作系统会自动为控制台程序创建一个控制台窗
口。

所以,除了操作系统会在上述方面对控制台窗口的创建或继承进行一些准备工作外,控
制台程序和窗口程序在其他方面并没有什么不同,控制台程序中仍然可以有消息循环,可以
创建窗口,也可以做窗口程序能做的任何事情。

A.2 书写控制台程序
现在用一个例子来说明如何在控制台程序中进行输入及输出,例子程序的源代码位于
Appendix A\EchoLine 目录中,程序运行后,会等待用户输入一些字符,当用户按下回车键后,



程序读入输入的行并以蓝色将输入的内容重新显示出来,然后等待用户输入新的一行并循环
往复。如果用户按下Ctrl+C 组合键,程序将退出执行。

程序的界面如图A.1 所示,读者可以注意到,例子程序的控制台窗口的标题是“EchoLine 
例子”。如果在“命令行提示符”中运行这个程序,窗口的标题也会从“命令提示符”变成
“EchoLine 例子”,等程序退出以后,标题会恢复到“命令提示符”。


图A.1 EchoLine 例子程序的界面

例子程序的源代码如下:

.386 
.model flat, stdcall
option casemap :none


;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 
; Include 
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 
include windows.inc 
include kernel32.inc 
includelib kernel32.lib 
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 

.data? 
hStdIn dd ? ;控制台输入句柄
hStdOut dd ? ;控制台输出句柄
szBuffer db 1024 dup (?) 
dwBytesRead dd ? 
dwBytesWrite dd ? 
.const 
szTitle db 'EchoLine 例子',0 

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 
; 代码段



;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


.code 
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 
; 控制台 Ctrl+C 捕获例程
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 
_CtrlHandler proc _dwCtrlType 

pushad


mov eax,_dwCtrlType


.if eax == CTRL_C_EVENT || eax == CTRL_BREAK_EVENT


 invoke CloseHandle,hStdIn


.endif


popad


mov eax,TRUE


ret


_CtrlHandler endp 
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 
start: 
;******************************************************************** 
; 获取控制台句柄、设置句柄属性
;******************************************************************** 

invoke GetStdHandle,STD_INPUT_HANDLE


mov hStdIn,eax 


invoke GetStdHandle,STD_OUTPUT_HANDLE


mov hStdOut,eax 


invoke SetConsoleMode,hStdIn,ENABLE_LINE_INPUT or \ 


 ENABLE_ECHO_INPUT or ENABLE_PROCESSED_INPUT 


invoke SetConsoleCtrlHandler,addr _CtrlHandler,TRUE 


invoke SetConsoleTitle,addr szTitle 
;******************************************************************** 
; 循环读取控制台输入并显示
;******************************************************************** 

.while TRUE


 invoke SetConsoleTextAttribute,hStdOut,FOREGROUND_RED\ 


 or FOREGROUND_GREEN or FOREGROUND_BLUE


 invoke ReadConsole,hStdIn,addr szBuffer,\


 sizeof szBuffer,addr dwBytesRead,NULL


 .break .if ! eax


 invoke SetConsoleTextAttribute,hStdOut,\ 


 FOREGROUND_BLUE or FOREGROUND_INTENSITY


 invoke WriteConsole,hStdOut,addr szBuffer,\


 dwBytesRead,addr dwBytesWrite,NULL


.endw


invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


end start


控制台程序输入输出的表现和DOS 程序类似,它可以从标准输入设备进行输入,也可



以输出到标准输出设备或者标准错误输出设备,所以,控制台程序中首先要做的事情是获取
这些设备的句柄。

1. 控制台句柄的获取和设置
控制台的各种句柄可以用GetStdHandle 函数来获取,函数的用法如下:

invoke GetStdHandle,nStdHandle
.if eax != INVALID_HANDLE_VALUE
mov hStd,eax
.endif


将nStdHandle 参数指定为以下不同的取值即可获取不同类型的句柄。

● STD_INPUT_HANDLE ——标准输入句柄。
● STD_OUTPUT_HANDLE ——标准输出句柄。
● STD_ERROR_HANDLE ——标准出错信息句柄。
函数执行成功将返回对应的句柄,否则将返回INVALID_HANDLE_VALUE 值。在例子
程序中,程序两次调用GetStdHandle 函数来分别获取输入和输出句柄,并保存到hStdIn 和
hStdOut 变量中以便在以后用来读取键盘输入,以及进行屏幕输出,对应的代码如下:

 invoke GetStdHandle,STD_INPUT_HANDLE 
mov hStdIn,eax 
invoke GetStdHandle,STD_OUTPUT_HANDLE 
mov hStdOut,eax 
...


获取句柄后就可以用ReadConsole 函数来读取键盘输入了,但在此之前,可以用
SetConsoleMode 函数对输入句柄的工作模式进行设置,这样能让ReadConsole 函数以我们所
期望的方式工作,如一次读取一行还是读取一个字符,是否对Ctrl+C 组合键进行拦截等。

SetConsoleMode 函数的用法如下:

invoke SetConsoleMode,hConsoleHandle,dwMode


hConsoleHandle 参数是要设置的控制台句柄,dwMode 是工作模式,当控制台句柄是输
入句柄时,dwMode 可以指定为以下标志位的组合。

● ENABLE_LINE_INPUT ——行模式标志,指定该标志位后ReadConsole 函数将在用户
输入回车后才返回,否则用户输入任何字符后即返回。
● ENABLE_ECHO_INPUT ——指定该标志后,用户输入的时候字符将在屏幕上回显。
● ENABLE_PROCESSED_INPUT ——指定该标志后,系统将拦截Ctrl+C 组合键,如果
不指定该标志的话,系统将Ctrl+C 组合键的键值(03h)作为字符返回给ReadConsole 
函数。
● ENABLE_WINDOW_INPUT 和ENABLE_MOUSE_INPUT——当用户对控制台窗口
的大小进行改变,或者按动鼠标时,系统将记录这些消息并允许程序用
ReadConsoleInput 函数读取(ReadConsole 函数会忽略这种类型的消息)。
例子程序中对输入句柄的模式进行设置时指定了三个标志位:ENABLE_LINE_INPUT, 
ENABLE_ECHO_INPUT 和ENABLE_PROCESSED_INPUT ,表示程序在后面将以行模式读



取键盘输入,用户输入的同时需要将字符回显并且程序将过滤Ctrl+C 组合键,对应的代码
如下:

 invoke SetConsoleMode,hStdIn,ENABLE_LINE_INPUT or \ 
ENABLE_ECHO_INPUT or ENABLE_PROCESSED_INPUT 


例子程序中还用到SetConsoleTitle 函数将控制台窗口的标题设置为“EchoLine 例子”, 
SetConsoleTitle 函数只有一个参数——指向标题字符串的指针:

 invoke SetConsoleTitle,addr szTitle


2. 截获Ctrl+Break 
在控制台程序中往往需要截获Ctrl+C(或Ctrl+Break)的组合键来判断是否要中途退
出,这在DOS 时代的程序中靠截获Int 23h 中断来实现,但在Win32 中不能再使用这种方法。

Win32 控制台程序使用SetConsoleCtrlHandler 函数来将Ctrl+C 的处理代码重新定义到自
己指定的子程序中,这样当输入句柄具有ENABLE_PROCESSED_INPUT 属性时,用户按下
Ctrl+C 组合键后系统即调用指定的子程序:

invoke SetConsoleCtrlHandler,HandlerRoutine,Add


当HandlerRoutine 参数指定为处理Ctrl+C 按键的子程序地址,Add 参数指定为TRUE 的

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -