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

📄 附录a-c.txt

📁 会变语言实现的一些程序
💻 TXT
📖 第 1 页 / 共 4 页
字号:
时候,系统将设置这个子程序为Ctrl+C 处理程序,如果Add 参数指定为FALSE ,系统将取
消这个设置。

HandlerRoutine 参数也可以为NULL,这时当Add 参数设置为TRUE 时,系统将忽略对
Ctrl+C 的处理,设置为FALSE 的话系统将恢复原来的处理方式。

例子程序中使用SetConsoleCtrlHandler 函数将处理子程序设置到_CtrlHandler 中。

Ctrl+C 的处理子程序必须按照规定的格式定义:

HandlerRoutine proc dwCtrlType 


该子程序有一个输入参数dwCtrlType ,系统调用该子程序的时候将使用这个参数指明发
生事件的类型,事件类型可能是以下几种。

● CTRL_C_EVENT——收到Ctrl+C 字符。
● CTRL_BREAK_EVENT——收到Ctrl+Break 字符。
● CTRL_CLOSE_EVENT——用户关闭了控制台窗口(比如按下了控制台窗口上面的关
闭按钮或在控制台窗口的菜单上选择了“关闭”等)。
● CTRL_LOGOFF_EVENT——当前用户注销。
● CTRL_SHUTDOWN_EVENT——系统准备关闭。
在例子代码中,_CtrlHandler 子程序仅处理CTRL_BREAK_EVENT 事件以及
CTRL_C_EVENT 事件,并在检测到这两个事件的时候用CloseHandle 函数将输入句柄关闭,
这样在后面的循环中ReadConsole 函数就会出错返回,程序即可退出循环。

3. 控制台窗口的输入和输出
从控制台窗口接收键盘输入可以用ReadConsole 或ReadFile 两种函数来完成(在第10 



章中有对ReadFile 函数的详细介绍),ReadConsole 函数的使用方法如下:

invoke ReadConsole,hConsoleInput,lpBuffer,\
nNumberOfCharsToRead,lpNumberOfCharsRead,lpReserved


hConsoleInput 参数为控制台的标准输入句柄,lpBuffer 指向用来接收输入数据的缓冲区,
nNumberOfCharsToRead 参数指定要读取的数据长度,lpNumberOfCharsRead 指向一个双字,
函数在这里返回实际读取的字节数。如果函数读取输入成功,则返回非零值,如果读取失败,
则返回零。

ReadConsole 和ReadFile 函数的区别在于能否支持重定向,ReadFile 函数允许使用“<” 
符号将一个文本文件的内容作为输入重定向到输入句柄中,而ReadConsole 函数不支持重定
向。

两个函数读取输入字符的方式取决于SetConsoleMode 函数对输入句柄工作模式的设置,
当工作模式指定为行读取模式的时候,只有用户输入的字符数等于nNumberOfCharsToRead 
参数指定的数量时,或者用户输入回车后函数才返回;当工作模式设置为非行读取模式时,
即使nNumberOfCharsToRead 参数指定的值很大,只要有任何字符输入,函数即返回,函数
实际读取的字符数量放在lpNumberOfCharsRead 参数指向的双字中。

如果需要向控制台窗口输出文本,同样可以使用WriteConsole 函数或者WriteFile 函数(在
第10 章中有WriteFile 函数的详细介绍),WriteConsole 函数的用法如下:

invoke WriteConsole,hConsoleOutput,lpBuffer,\
nNumberOfCharsToWrite,lpNumberOfCharsWritten,lpReserved 


参数hConsoleOutput 指定为前面获取的标准输出句柄,lpBuffer 参数指向输出的内容,
nNumberOfCharsToWrite 参数为要输出的数据长度,lpNumberOfCharsWritten 指向一个双字变
量,用来返回实际输出的数据长度。

同样,两种方法的区别在于WriteConsole 函数不支持输出内容的重定向,而WriteFile 函
数支持重定向,这样使用命令行方式的管道操作符可以将内容重定向到一个文件中,如下面
的命令将Ping 的结果存放到result.txt 文件中:

ping www.yahoo.com > result.txt


如果程序希望某些内容可以重定向,而某些内容不允许重定向,那么可以混合使用这两
个函数来输出文本。

例子程序中循环使用ReadConsole 函数来读入用户的输入,并用WriteConsole 函数来将
输入的字符串回显在控制台窗口中。当用户输入Ctrl+C 组合键的时候,Ctrl+C 的处理子程
序中将输入句柄关闭,这样ReadConsole 会返回失败,循环退出。

4. 设置控制台窗口文本的颜色
例子程序在用户输入的时候显示的字符颜色是白色的,但是在将同样的内容输出时,字
符颜色却是加亮的蓝色,这是因为在调用ReadConsole 和WriteConsole 函数前分别用
SetConsoleTextAttribute 函数设置了不同的文本颜色。

SetConsoleTextAttribute 函数用于设置控制台窗口中将要显示的文本颜色,一旦设置完毕
后,以后显示的字符将全部使用新的颜色,但是最初显示的字符颜色不受影响,函数的用法



如下:

invoke SetConsoleTextAttribute,hConsoleHandle,dwColor


hConsoleHandle 参数是需要设置的输出句柄,dwColor 参数指定颜色值,颜色值可以是
下列取值的组合。

● FOREGROUND_BLUE,FOREGROUND_GREEN,FOREGROUND_RED ——分别表
示字符颜色为蓝色、绿色和红色。
● FOREGROUND_INTENSITY ——字符颜色加亮。
● BACKGROUND_BLUE,BACKGROUND_GREEN,BACKGROUND_RED ——分别
表示字符的背景色为蓝色、绿色和红色。
● BACKGROUND_INTENSITY ——字符背景颜色加亮。
如果需要其他颜色,可以将这些颜色值按照三原色相加的方式组合起来,比如设置字符
颜色为加亮的白色时,因为白色是由红绿蓝三色组合而成的,所以dwColor 参数可以指定为
FOREGROUND_INTENSITY or FOREGROUND_RED or FOREGROUND_GREEN or 
FOREGROUND_BLUE ;需要红色加亮的字符,但是背景是黄色的时候,可以指定为
FOREGROUND_RED or FOREGROUND_INTENSITY or BACKGROUND_GREEN or 
BACKGROUND_RED ,这是因为背景的黄色是由红色和绿色组合而成的。

将控制台程序和普通的窗口程序对比就可以发现,由于不必处理复杂的消息机制,控制
台程序的流程可以使用和DOS 程序类似的架构,也就是说程序可以按顺序化的方式来写,
而不是以消息驱动的方式来写。这种架构可以让程序的编写更加简单。


附录B


窗口消息实验


在本节中,将通过不同的实验来了解常见的窗口消息,并进一步理解窗口的工作机制。
我们将构造一个程序,在程序中将收到的窗口消息查表翻译成文本以“WM_XXX”格式显
示出来,同时将和窗口相关的API 函数的调用也显示出来,这样可以分析窗口的各种行为和
消息之间的关系。

实验用到的源代码请参考附书光盘的Appendix B 目录。

B.1 MsgWindow 程序
为了把窗口消息翻译成文本信息显示出来,我们可以选择在窗口的客户区中显示文本,
但这样会引入新的消息,干扰实验,所以,这里选择了一种新的方法,就是先打开Windows 
附件中自带的Notepad 记事本程序,然后在程序中将要显示的内容通过SendMessage 发送到
记事本中,这样可以通过查看记事本中的内容来了解MsgWindow 的运行情况。

程序以Chapter04\FirstWindow 例子为模板,在此基础上增加了一些功能。增加的内容共
有3 个部分。
第一部分是将消息查表转换为字符串,首先在 .const 段中增加两个表:十六进制的消息
编号列表dwMsgTable 和字符串列表szStringTable ,两表中的项目一一对应,代码如下:

 .const


dwMsgTable dd WM_NULL 
dd WM_CREATE 
dd WM_DESTROY 
dd WM_MOVE


.

 dd WM_EXITSIZEMOVE
MSG_TABLE_LEN equ ($ - dwMsgTable)/sizeof dword


MSG_STRING_LEN equ sizeof szStringTable 
szStringTable db 'WM_NULL ',0 
db'WM_CREATE ',0 
db 'WM_DESTROY ',0 
db 'WM_MOVE ',0 
. 
db 'WM_EXITSIZEMOVE ',0 
szFormat db 'WndProc: [%04x]%s %08x %08x',0dh,0 



MSG_TABLE_LEN 定义了表的项数,MSG_STRING_LEN 定义了字符串表中每一项的
长度。sizeof 操作符取的是szStringTable 这一行中的数据长度,而非包括下面全部的字符串
行。为了简化处理,全部字符串的长度保持相等。由于篇幅所限,这里没有列出全部的消息
列表,完整的源代码可以在本书附带光盘的Appendix B\MsgWindow01 目录中找到。

程序在窗口过程的入口处调用_ShowMessage 子程序来翻译消息并传给记事本:

_ProcWinMain proc uses ebx edi esi,hWnd,uMsg,wParam,lParam


 invoke _ShowMessage,uMsg,wParam,lParam 
mov eax,uMsg 
.if eax == WM_XXX


. 

_ShowMessage 子程序用来将消息查表翻译成字符串,源程序如下:

_ShowMessage proc _uMsg,_wParam,_lParam 
local @szBuffer[128]:byte


 pushad 
;******************************************************************** 
; 查找消息的说明字符串
;********************************************************************

 mov eax,_uMsg 
mov edi,offset dwMsgTable 
mov ecx,MSG_TABLE_LEN 
cld 
repnz scasd 
.if ZERO?


sub edi,offset dwMsgTable + sizeof dword
shr edi,2
mov eax,edi
mov ecx,MSG_STRING_LEN
mul ecx
add eax,offset szStringTable


;******************************************************************** 
; 翻译格式并发送到 Notepad 窗口
;******************************************************************** 

invoke wsprintf,addr@szBuffer,addr szFormat,\ 
_uMsg,eax,_wParam,_lParam


invoke _SendtoNotepad,addr @szBuffer 
.endif 
popad 
ret


_ShowMessage endp


在这里要用到repnz scasd 指令,scasd 指令是把eax 中的值从[edi]开始的内存中按双字比
较,同时将edi 加4,如果相等,则ZR 标志置位,否则为NZ,repnz 表示如果标志为NZ, 
则以ecx 为重复次数重复搜索,直到相等或ecx 为零为止。

将ecx 赋值为消息表的项数MSG_TABLE_LEN ,将edi 赋值为消息表的开始地址offset 



dwMsgTable ,然后开始查找,停止后可以查看标志Zero 位,如果是非ZERO,表示查完全部
都没有找到,如果是ZERO ,则表示找到表项。

当标志为ZERO 时,edi 指向找到项目的后一项,将edi 减去一项的长度(sizeof dword) 
以及表的基址,再除以表项的长度(sizeof dword 等于4,除以4 等于右移两位,所以程序中
用shr edi,2),就是消息在表中的索引了,接下来算出消息字符串的位置,位置等于:索引×
字符串长+字符串表基址,代码如下:

 mov ecx,MSG_STRING_LEN 
mul ecx 
add eax,offset szStringTable


这样,eax 中就是字符串的地址了。最后将消息编号、名称和参数用wsprintf 函数格式化
成可以发送的字符串存放到@szBuffer 中,并用_SendtoNotepad 子程序将@szBuffer 中的内容
发送到记事本中。

程序增加的第二部分就是下面这个_SendtoNotepad 子程序:

szDestClass db 'Notepad',0 
_SendtoNotepad proc _lpsz 
local @hWinNotepad

 pushad 
invoke FindWindow,addr szDestClass,NULL 
.if eax


mov ecx,eax


invoke ChildWindowFromPoint,ecx,20,20 
.endif 
.if eax


mov @hWinNotepad,eax
mov esi,_lpsz
@@:
lodsb
or al,al
jz @F
movzx eax,al
invoke PostMessage,@hWinNotepad,WM_CHAR,eax,1
jmp @B
@@:


 .endif 
popad 
ret


_SendtoNotepad endp


该子程序中首先用FindWindow 函数来查找记事本程序是否已经运行,记事本程序的窗
口类名称为“Notepad”,FindWindow 可以用窗口类当做第一个参数来查找,如果找到,返回
的是记事本程序的主窗口句柄,否则返回0。

要发送的是模拟键盘按键的消息WM_CHAR,这样就好像在记事本中人工键入字符,但
直接向记事本主窗口发送WM_CHAR 消息是不行的,要向记事本窗口客户区中的编辑子窗



口发送消息才行,所以程序中又用从位置获取子窗口句柄的函数ChildWindowFromPoint 来获
得编辑子窗口的句柄。

锁定了最后的目标即记事本中的编辑子窗口后,程序用PostMessage 向它发送消息,根
据字符串的长度,用一个循环每次发送一个WM_CHAR 消息,WM_CHAR 消息的wParam 
和lParam 的含义如下:

wParam = chCharCode // wParam 是键值
lParam = lKeyData // lParam 是键数据(重复次数)


程序中用mov eax,al 将键值扩展到参数所需的32 位,当做wParam 参数发送,lParam 为
1,表示键的重复次数为1 次,这样一来,记事本中就源源不断地显示出MsgWindow 程序的
运行轨迹了。

MsgWindow 程序增加的第三部分是在每个函数的前后增加了显示状态的语句,它们只是
简单地把一个字符串发送到记事本中。

;定义一些字符串

szCreateWindow1 db 'Creating Window...',0dh,0 
szCreateWindow2 db 'CreateWindow end',0dh,0 
szShowWindow1 db 'Showing Window...',0dh,0 
szShowWindow2 db 'ShowWindow end',0dh,0 
szUpdateWindow1 db 'Updating Window...',0dh,0 

⌨️ 快捷键说明

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