05-18修订
- 修订Windows环境空间的分类以及优先级
- 增加无需注销更新系统环境变量的方案
05-06修订
- 增加Windows环境变量的注册表存储位置
- 修改explorer.exe下修改环境变量的说明
4-30修订
- 增加Windows的环境变量排序、动态变量、和setlocal/endlocal
- 增加msdos的set命令、autoexec.bat、setx.exe工具的说明
- 修改windows下环境空间和变量读写的说明
因为看了置顶主题“批处理变量表机制的猜测及测试”
http://www.bathome.net/thread-12030-1-1.html
有感于中间讨论的东西太多太杂
很多常识性的概念和论点被反复讨论
大段的测试代码看的让人头晕
又充斥了一堆似是而非甚至是谬误的言论
所以将变量存储的一些细节另发主题
这里是纯理论探讨
代码测试请另开主题
一、MS-DOS下的环境变量
MS-DOS系统太久远了
关于环境变量的处理这里不做过多的讨论
只大概明确两点
1.环境变量空间分为两类:全局和局部
全局环境由系统核心COMMAND.COM独立创建和维护
局部环境由COMMAND载入exe等应用程序时创建
局部环境创建时会复制全局环境的值
环境空间地址写入程序的PSP
但应用程序结束后不会写会全局环境
大多数操作环境的函数都是针对局部环境的
要写入全局环境需要通过特殊手段得到全局环境的起始地址
但是set命令是针对全局环境进行操作的
因为它是command.com的内部命令
它必然有command.com解释执行
所以自然会索引到command.com的环境空间
也就是全局环境空间
通过C盘根目录的config.sys和autoexec.bat文件可以配置MS-DOS的环境变量
2.MSDOS环境变量是按字节存储的
是按ASCII表顺序存储
变量名会统一转换为大写处理
所以不存在大小写的比较问题
COMSPEC例外,因为它在系统启动时指引IO.SYS寻找COMMAND.COM
其形式是“ 【变量名】=【变量值】【字节0】”
读取变量的方式是顺序遍历
之所以没有用二分搜索遍历
是因为那是的变量空间相对较小
变量的使用频率也相对较低
相对于与变量的索引性能而言
顺序遍历简单而够用
写入变量的方式是顺序遍历后插入
也就是说新变量后的变量会顺序后移
二、Windows下的环境变量
到了Windows系统下发生了很多变化
首先,环境空间变成了三级:
1)系统环境空间,存储在注册表 HKLM\System\CurrentControlSet\Control\Session Manager\Environment\
2)用户环境空间,存储在注册表 HKCU\Environment\ 和 HKCU\Volatile Environment
3)启动环境空间,存储在文件 系统盘:\AUTOEXEC.BAT
为了兼容性考虑
位于系统盘根目录的autoexec.bat
仍可以预定义Windows的环境变量
为了安全性考虑
系统只解释autoexec.bat中的环境变量设置语句
如set、path、append等
其它语句不予理会
系统启动时应用程序根据自己的需要读取环境变量
然后写入应用程序自己的环境空间
如果某个环境变量在多个环境空间被定义
将按照“系统、启动、用户”的顺序依次读取并设置
最后被设置的值为环境变量的当前值
path变量是一个特例
它是由"系统、用户、启动"三级空间的对应值组合而来的
也就是说当前path=系统path+用户path+启动path
同样的例子还有LibPath 和 Os2LibPath
应用程序的环境空间有继承特性
如果A进程启动了B进程
那么B进程作为A进程的子进程
将缺省集成A进程的环境空间
而MS-DOS的全局环境空间
类似于现在Windows下explorer.exe的环境空间
因为explorer.exe是Windows默认的外壳
很多情况下用户所启动的程序
包括从开始运行输入cmd、点击“命令提示符”或者批处理文件所启动的cmd.exe
都是作为explorer.exe的子进程运行
所以会继承它的环境空间
两类环境变量通常在“控制面板”的“系统属性”中更改
这不仅了修改了注册表中的相对应的键值
也同时修改了explorer.exe自己的环境空间
所以不用重启电脑马上可以在cmd.exe看到这个变化
也可以直接修改注册表中对应子键的键值
但是需要注销当前用户或重启计算机
由userinit.exe进程重新读取到新的变量状态
从而继承到explorer.exe的环境空间中
在新启动的类似cmd.exe的子进程中才能观察到这种变化
若要无需注销而更新环境空间
可以向所有窗口发送一个 WM_SETTINGCHANGE 广播消息
相关的应用程序(资源管理器、任务管理器、控制面板等)会执行更新
代码示例- SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0,
- (LPARAM) "Environment", SMTO_ABORTIFHUNG,
- 5000, &dwReturnValue);
复制代码 另外,在微软发行的Windows XP Service Pack 2 Support Tools(并非SP2)中
附带了一个命令行工具setx.exe
它可以在命令行中直接修改用户变量或者系统变量
其次,因为Windows系统自2000版本开始内部已全面采用Unicode编码
所以Windows的环境变量也是以Unicode形式存储
也就是说无论英文、汉字还是日文都会占用两个字节的空间
这也就解释了用set获取变量的长度或者子串时都是以字符计数
而非以ANSI存储(对于ASCII是单字节,对于GBK是双字节)的字节计数
环境变量的排序规则也从ASCII更新到了Unicode编码排序
只不过因为Windows支持了小写的变量名形式
所以它的排序比较是忽略大小写的
包括全局英文的大小写也被忽略
再次,对于Windows下的COMMAND.COM曾经有过讨论
它仅仅是虚拟机(ntvdm)封装下的cmd.exe的入口程序
当我们启动command.com时
实际上是启动了ntvdm.exe进程
这个ntvdm不仅仅提供了command命令到cmd.exe命令的虚拟通道
也对16位程序所需的内存环境做了虚拟
包括内存中的环境变量
可以通过创建PIF文件(16位DOS程序的快捷方式)定制内存环境(包括变量环境)
也可以修改%winir%\system32下的CONFIG.NT和AUTOEXEC.NT配置默认的虚拟内存环境
NTVDM虚拟环境最主要的变化就是
把双字节的Unicode映射为了单字节的ASCII编码和双字节的本地化编码(GB)
而debug.exe因为程序本身是为16位环境设计
必须运行在NT虚拟机环境下
启动debug.exe则同时会启动ntvdm.exe
所以debug操作的是ntvdm虚拟出的内存环境
所以它所看到的是单字节的环境变量存储
另外,cmd下多了很多特殊的环境变量
如果cmd的命令扩展被启用(默认是启用状态)
有几个动态环境变量可以被引用
每次变量数值被扩展时
这些变量数值都会被动态计算
它们不会出现在 SET 显示的变量列表中
也不存在于cmd的环境空间中
%CD% - 扩展到当前目录字符串。
%__CD__% - 扩展到当前目录字符串。末尾总会附带分隔路径的斜线。
%DATE% - 用跟 DATE 命令同样的格式扩展到当前日期。
%TIME% - 用跟 TIME 命令同样的格式扩展到当前时间。
%RANDOM% - 扩展到 0 和 32767 之间的任意十进制数字。
%ERRORLEVEL% - 扩展到当前 ERRORLEVEL 数值。
%CMDEXTVERSION% - 扩展到当前命令处理器扩展名版本号。
(NT4下是1;2000,XP下是2;Vista,Win7未确认)
%CMDCMDLINE% - 扩展到调用命令处理器的原始命令行。
还有一些动态变量更为特殊
它们占用了环境空间但是却不会出现在set命令的列表中
不过可以用一些不常见的用法看到它的存在 set,
即set后面直接跟一个半角逗号(也可以是分号或者 set "")
在命令输出的最上方会多出类似下面的变量
=::=::\ 标识未访问盘符的当前路径
=C:=C:\Documents and Settings\Administrator 标识已访问盘符的当前路径
=ExitCode=00000001 以32位的16进制数标识Exit /b所指定的错误返回码
它使十进制到十六进制数据转换更加快捷
=ExitCodeASCII=00000001 以ASCII表中对应的字符标识Exit /b所指定的错误返回码
指定的返回码必须大于等于32(也就是空格的ASCII码值)它才会被设置
它使ASCII码值转字符更加方便
最后说说setlocal/endlocal
它会将cmd.exe的当前环境空间另选新的内存空间备份一份
直到endlocal把这个备份空间恢复到当前空间并回收内存
嵌套使用setlocal时会将环境空间多次备份
而后endlocal时会从新到旧恢复空间并收回内存 |