📄 013.txt
字号:
13.1 环境变量和命令行参数
13.1.1 环境变量
1. 什么是环境变量
环境变量就是在命令提示符下键入“Set”命令后列出来的内容,它的定义格式以XXX=YYY的形式来表示,其中的XXX是环境变量的名称,YYY是环境变量的值,下面的例子是笔者使用的计算机中列出的部分环境变量:
ALLUSERSPROFILE=C:\Documents and Settings\All Users
APPDATA=C:\Documents and Settings\Administrator\Application Data
BLASTER=A220 I7 D1 H7 P330 T6
SBPCI=C:\SBPCI
COMPUTERNAME=WORKGROU-86NSVP
ComSpec=C:\WINDOWS\system32\cmd.exe
SystemDrive=C:
SystemRoot=C:\WINDOWS
HOMEDRIVE=C:
ProgramFiles=C:\Program Files
HOMEPATH=\Documents and Settings\Administrator
LOGONSERVER=\\WORKGROU-86NSVP
OS=Windows_NT
Path=C:\WINDOWS\system32;C:\WINDOWS;c\tools;C:\WIN98\Twain_32\Nuscan
NUMBER_OF_PROCESSORS=1
PROCESSOR_ARCHITECTURE=x86
PROCESSOR_IDENTIFIER=x86 Family 6 Model 6 Stepping 0, GenuineIntel
PROCESSOR_LEVEL=6
PROCESSOR_REVISION=0600
PROMPT=$P$G
TEMP=C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp
TMP=C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp
USERDOMAIN=WORKGROU-86NSVP
USERNAME=Administrator
windir=C:\WINDOWS
环境变量按照用途可以分为3大类:
● 与系统运行相关的环境变量——这些变量的值和系统的正常运行息息相关。如PATH变量定义的是可执行文件的搜索路径,它直接影响系统搜寻可执行文件的位置和先后顺序;而TEMP和TMP变量将影响系统创建临时文件的位置;ComSpec变量定义命令行管理器的文件名,在DOS操作系统下,如果这个变量的定义错误,就会导致系统无法装入Command.com而挂起。
● 反映系统状态的环境变量——如NUMBER_OF_PROCESSORS,PROCESSOR_LEVEL和PROCESSOR_IDENTIFIER等是操作系统根据当前的硬件定义的;COMPUTERNAME,USERNAME和USERDOMAIN等变量是操作系统根据当前的机器名、登录用户等定义的;OS,HOMEDRIVE和ProgramFiles等则定义了操作系统的名称和安装位置。通过检测这些变量,应用程序可以了解系统的配置情况和其他一些重要信息。
● 应用程序自定义的环境变量——如上面列出的BLASTER和SBPCI是创新声卡自定义的变量。在本书例子程序的编译环境中,也定义了一些编译器和链接器使用的变量,如Include与Lib等,这些变量是应用程序根据自己的需要自定义的。
2. 对环境变量进行操作
在命令提示符窗口中,可以通过“Set 变量=内容”格式的命令来设置环境变量的值,也可以通过“Set 变量=”命令来删除某个环境变量。如果需要在程序中对环境变量进行操作,可以使用Win32中的几个API。
如果想获取一个环境变量的值,可以使用GetEnvironmentVariable函数:
invoke GetEnvironmentVariable,lpVarName,lpBuffer,dwSize
lpVarName指向一个以0结尾的字符串,用来指定需要获取的环境变量名,lpBuffer指向用来接收变量值的缓冲区,dwSize参数指定缓冲区的大小。如果函数执行成功,返回值是返回到缓冲区中的字符数量(不包括结束的0字符);如果环境变量不存在,返回值是0;如果缓冲区太小以至于放不下环境变量内容,那么缓存区中不会返回任何内容,这时函数的返回值是需要的缓存区的大小,这就意味着,如果返回值比缓冲区的大小要大,那么必须扩大缓冲区后再次调用。
下面的代码演示了如何将PATH变量的值获取到szBuffer缓冲区中:
.data
szBuffer db 200 dup (?)
szVarName db ~PATH~,0
.code
invoke GetEnvironmentVariable,addr szVarName,\
addr szBuffer,sizeof szBuffer
GetEnvironmentVariable函数只能用来获取已知名称的环境变量,如果需要枚举所有的环境变量,可以使用GetEnvironmentStrings函数。这个函数返回一个内存块指针,内存块中包含了所有的环境变量定义,通过扫描整个内存块就可以获得所有的环境变量定义。该函数没有输入参数:
invoke GetEnvironmentStrings
mov lpVar,eax
内存块中环境变量存放格式为“变量1=内容1”,0,“变量2=内容2”,0,……“变量N=内容N”,0,0,即每个定义字符串以0结束,然后开始下一个变量定义字符串,全部定义字符串的最后再以一个附加的0结束。
GetEnvironmentStrings函数返回的内存块是系统申请的,当不需要再使用的时候,需要将它释放,释放这个内存块并不等于删除全部环境变量,而仅是释放这份拷贝而已。释放使用的函数是FreeEnvironmentStrings:
invoke FreeEnvironmentStrings,lpVar
函数的输入参数lpVar就是GetEnvironmentStrings函数返回的内存块指针。
如果需要改变现存环境变量的值,设置新的环境变量或者删除某个环境变量,可以使用SetEnvironmentVariable函数:
invoke SetEnvironmentVariable,lpVarName,lpValue
lpVarName指向环境变量的名称,lpValue指向一个以0结尾的字符串,用来指定环境变量的新值。
当lpVarName指定的环境变量已经存在且lpValue指向一个空串时,这个变量将被删除;如果lpValue指向的不是一个空串,那么环境变量的值将被改为这个新的字符串;如果lpVarName指定的环境变量不存在且lpValue指向的不是一个空串,那么系统将建立新的环境变量。
SetEnvironmentVariable函数的运行结果仅改变本进程的环境变量,并不会影响其他进程。比如打开一个命令提示符窗口,在其中改变某些环境变量的设置,然后再打开另一个命令提示符窗口查看,就会发现这个窗口的环境变量并没有改变。
但是环境变量的值可以被子进程继承,如果在一个程序中创建了另一个进程,那么可以让这个子进程“看到”改动以后的环境变量,这就是在一个命令提示符窗口中改变了环境变量的设置,然后用命令行方式运行一些程序,改变的环境变量对这些程序都有效的原因。
13.1.2 命令行参数
1. 什么是命令行参数
在命令行中通过输入文件名来执行文件,在文件名后面跟的参数就是命令行参数,比如通过Telnet连接到某个远程计算机的1234号端口,可以输入:
Telnet 192.168.0.1 1234
“Telnet”后面跟的“192.168.0.1 1234”就是命令行参数,它可以被程序获取,命令行参数是当做一个以0结尾的字符串被程序获取的。
对于窗口程序来说,命令行参数并不是必需的,因为大部分的窗口程序都在菜单中提供了“打开文件”、“选项”等功能,并不需要用户在命令行参数中指定,如果窗口程序必须依靠命令行参数,那么用户就不能通过在任务管理器中双击图标来执行程序了,因为这样无法输入命令行参数。
但是某些情况下,命令行参数又是窗口程序的必然补充,比如Windows中的文本文件往往被关联到记事本程序Notepad.exe上,直接双击文本文件,Windows就会自动执行Notepad.exe,并把文本文件名通过命令行参数传递给它,所以对于Notepad.exe来说,虽然已经在菜单中提供了“打开文件”功能,但也必须处理命令行参数,否则对关联文件就无法处理。
而对于控制台程序(如系统中的Ping.exe,Format.exe和Xcopy.exe等在命令行下运行的程序)来说,它们没有自己的窗口,也就无法通过菜单来选择某些功能,这时通过命令行传递各种参数就是必然的选择。
2. 使用命令行参数
要获取命令行参数,可以使用GetCommandLine函数,这个函数没有输入参数,返回值是一个指向命令行参数字符串的指针:
invoke GetCommandLine
mov lpCmdline,eax
获取命令行参数字符串以后,首先必须对它进行处理,比如对于上面的Telnet程序来说,第一个参数“192.168.0.1”指定主机名,第二个参数“1234”指定端口号,但是我们得到的却是一个连在一起的字符串,所以必须扫描字符串将两个参数分开后才能使用,而且,必须通过检查参数字符串防止用户输入了错误的参数。
Win32中并没有通用的用来扫描参数字符串的函数,有一个函数CommandLineToArgvW虽然可以用来对字符串进行扫描,但这个函数仅适用于Unicode字符串,而且仅在NT系列中得到支持,在Windows 9x系列中无法使用,为了使用这个函数而将程序限制在Windows NT下运行显然是得不偿失的。
C语言为用户考虑到了这一点,在C的初始化程序将控制权交到WinMain函数之前就已经对命令行参数进行了处理,并将参数按照空格划分成不同的部分放到argv数组中,参数数量则存放在argc变量中,在程序的任何地方都可以存取这些变量;但在Win32汇编中,这些工作就需要自己来做了,笔者在本节的例子程序中提供了两个通用函数,读者可以把它们用在其他程序中。在分析这个例子之前,先来看看在命令行参数字符串中究竟可以收到哪些内容,这些内容将决定分析字符串的算法。
读者可以编写一个很简单的测试程序Test.exe,在程序中调用GetCommandLine函数并将得到的命令行参数显示出来,假如把这个Test.exe放在“C:\Program files”目录下并使用不同的方法去执行它,执行的时候带3个参数“aaa bbb ccc”,就可以发现得到的命令行参数可能是下面的样子:
(1)test aaa bbb ccc
(2)test.exe aaa bbb ccc
(3)te″st″.exe aaa bbb ccc
(4)c:\program″″files\test aaa bbb ccc
(5)″C:\Program files\Test.exe″
(6)″C:\Program files\Test.exe″″C:\aaa bbb ccc″
结果(1)到(4)显示的命令行参数字符串是在命令提示符下输入同样的执行命令得到的。需要注意的有两点:
首先是可执行文件名被当做命令行参数的第一个组成部分被传递过来了,所以要使用真正由用户输入的命令行参数,必须首先将这部分过滤掉。
其次是对可执行文件名的处理,输入test或test.exe可以正常执行文件并不奇怪,奇怪的是输入te"st".exe或者c:\program""files\test这样的文件名也能执行,难道这也是合法的文件名吗?
答案是肯定的,这是因为Windows将空格当做参数的分隔符,但是长文件名中同样可以使用空格,这就产生了冲突,为了解决这个问题,Windows使用双引号来定界,假如文件名中存在空格的话,必须将空格包含在两个双引号中,但双引号并不是只能用一对,也并不一定要用在文件名的头尾,实际上它们可以在文件名的任何地方出现,只要是成对的并且所有空格都被包含在某一对双引号中就可以了,双引号本身不是文件名的组成部分,Windows在最后会自动将它们剔除,但是传递到命令行参数中的时候还是保留了输入时的样子。
结果(5)是在文件管理器中通过双击Test.exe执行时得到的命令行字符串,结果(6)是在C盘的根目录下建立了一个名为“aaa bbb ccc”的文件,并在“打开方式”中选择Test.exe后得到的命令行字符串。从这些结果中可以发现:只要由Windows来打开文件,那么Windows就会用很规范的方式传递文件名,具体就表现在文件名肯定是带全路径的,并且不管文件名中是否包含空格,头尾都会被加上一个双引号。
根据这些特征,就不难写出一个通用的分析命令行参数的子程序来:
CHAR_BLANK equ 20h ;定义空格分隔符
CHAR_DELI equ ~"~ ;定义双引号分隔符
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 取命令行参数个数 (arg count)
; 参数个数必定大于等于 1, 第1个参数为当前执行文件名
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_argc proc
local @dwArgc ;需要返回的结果
pushad
mov @dwArgc,0
invoke GetCommandLine
mov esi,eax
cld
_argc_loop:
lodsb
or al,al
jz _argc_end ;如果到字符串尾则结束
cmp al,CHAR_BLANK
jz _argc_loop ;如果是空格则忽略
;********************************************************************
; 遇到一个非空格字符表示一个参数开始
;********************************************************************
dec esi
inc @dwArgc ;增加参数个数的计数
_argc_loop1:
lodsb
or al,al
jz _argc_end ;如果到字符串尾则结束
cmp al,CHAR_BLANK
jz _argc_loop ;再遇到一个空格表示参数结束
cmp al,CHAR_DELI
jnz _argc_loop1 ;如果遇到双引号则忽略空格
@@:
lodsb
or al,al
jz _argc_end
cmp al,CHAR_DELI ;直到遇到另一个双引号为止
jnz @B
jmp _argc_loop1
_argc_end:
popad
mov eax,@dwArgc
ret
_argc endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 取指定位置的命令行参数
; argv 0 = 执行文件名
; argv 1 = 参数1 ...
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_argv proc _dwArgv,_lpReturn,_dwSize
local @dwArgv,@dwFlag
pushad
inc _dwArgv
mov @dwArgv,0
mov edi,_lpReturn
invoke GetCommandLine
mov esi,eax
cld
_argv_loop:
lodsb
or al,al
jz _argv_end
cmp al,CHAR_BLANK
jz _argv_loop
;********************************************************************
; 一个参数开始
; 如果和要求的参数符合,则开始复制到返回缓冲区
;********************************************************************
dec esi
inc @dwArgv
mov @dwFlag,FALSE
mov eax,_dwArgv
cmp eax,@dwArgv
jnz @F
mov @dwFlag,TRUE ;表示需要将字符返回
@@:
_argv_loop1:
lodsb
or al,al
jz _argv_end
cmp al,CHAR_BLANK
jz _argv_loop ;参数结束
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -