WOW64简介
WOW64(Windows 32-bit On Windows 64-bit)是x64平台上运行win32应用程序的模拟器,它在系统层提供了中间层,将win32的系统调用转换成x64进行调用,并且将x64返回的结果转换成win32形式返回给win32程序。下图描述了win32程序如何在x64系统上运行的。
WOW64局限性:
- 地址空间默认是2G,使用/LARGEADDRESSAWARE开关可以达到4G。
- 32位进程无法加载64位DLL(除了指定的系统DLL)。
- 不支持16位进程的运行。
- 无法使用DOS虚拟机(VDM, Vitrual DOS Machine)。
- Intel安腾系列处理器不支持 AWE, Vectored I/O, PAE 以及DirectX硬件加速API。
注册表机制
众所周知,注册表是windows系统的数据库,系统本身以及安装的程序都依赖注册表。当Windows进化到64位,还要兼容大量的32位应用程序,便碰到了注册表冲突的问题。
注册表树最大可以有512级深度,通过注册表API一次可以创建32级深的键值。
为了解决64位系统的兼容性问题,Windows使用了三套方案,共享(Shared)、注册表重定向(Registry Redirector)和注册表反射(Registry Reflection)。
1. 共享
保存可以被32位和64位共同使用的注册表。
2. 注册表反射
注册表反射是在64位注册表视图和32位注册表视图之间复制某些特定的注册表项和项值。简单的说就是备份和同步,把同一份注册表保存到两个物理位置,分别被32位或64位程序使用。保存发生在RegCloseKey调用结束。
使用RegDisableReflectionKey和RegEnableReflectionKey方法可以禁用/启用反射机制。
该方案只用于Windows Server 2008, Windows Vista, Windows Server 2003 和 Windows XP,从 Windows 7 和 Windows Server 2008 R2 开始被移除。
举例:在X64系统中安装64位Microsoft Office后,64位的winword.exe将注册.doc这个扩展名并把这个扩展名关联到winword.exe程序,根据X64的运行机制,64位程序修改的是64位的注册表键值,但是WOW64会自动的把这个修改会同步到32位的注册表键下面,这样32位和64位的应用程序都可以使用64位winword.exe打开.doc文件。
但是,并不是所有的键值都会受到注册表反射机制的影响。实验证明,如果我们使用32位的注册表编辑器在HKEY_LOCAL_MACHINE/Software下新建一个项,然后使用64位的注册表编辑器查看,会发现这个项只会出现在HKEY_LOCAL_MACHINE/Software/Wow6432Node键下而不会出现在HKEY_LOCAL_MACHINE/Software键下,因为HKEY_LOCAL_MACHINE/Software键是专门用于存放64位程序所使用的注册表数据的,而HKEY_LOCAL_MACHINE/Software/Wow6432Node键是专门用于存放32位程序所使用的注册表数据的。
注册表中受到反射机制影响的有:
HKEY_LOCAL_MACHINE/Software/Classes
HKEY_LOCAL_MACHINE/Software/COM3
HKEY_LOCAL_MACHINE/Software/EventSystem
HKEY_LOCAL_MACHINE/Software/Ole
HKEY_LOCAL_MACHINE/Software/Rpc
HKEY_USERS/*/Software/Classes
HKEY_USERS/*_Classes
3. 注册表重定向
注册表重定向为32位和64位程序分别提供不同的注册表物理存储位置,但会映射成同一个逻辑视图,这个过程对程序本身是透明的。也就是说,一个32位程序可以像在32位系统中一样来使用注册表,虽然它们在64位系统上被存储在不同的物理位置。
32位程序重定向的注册表存放在Wow6432Node下,例如, HKEY_LOCAL_MACHINE\Software 会被重定向到 HKEY_LOCAL_MACHINE\Software\Wow6432Node。
需要重定向的注册表项如下所示:
// 64位程序的注册信息存储键
HKLM/Software
HKEY_CLASSES_ROOT
HKEY_CURRENT_USER/Software/Classes
HKEY_LOCAL_MACHINE/Software
HKEY_USERS/*/Software/Classes
HKEY_USERS/*_Classes
//32位程序的注册信息重定向存储键
HKLM/Software/WOW6432node
HKEY_CLASSES_ROOT/WOW6432node
HKEY_CURRENT_USER/Software/Classes/WOW6432node
HKEY_LOCAL_MACHINE/Software/WOW6432node
HKEY_USERS/*/Software/Classes/WOW6432node
HKEY_USERS/*_Classes/WOW6432node
注册表重定向机制对系统的影响
- 下列程序调用没有问题:
32位应用程序A调用32位应用程序B并访问B的注册表信息。由于注册表重定向机制,32位应用程序B的注册信息在HKLM/Software/Wow6432Node中,而32位应用程序A访问注册表也会被重定向到HKLM/Software/Wow6432Node中,所以访问正常。
64位应用程序A调用64位应用程序B并访问B的注册表信息。64位应用程序B的注册信息在HKLM/Software,64位应用程序A访问注册表时直接访问HKLM/Software,所以访问正常。
- 在下列情况时会出现问题:
-
64位应用程序调用32位应用程序并访问其注册表信息。因为32位应用程序的注册信息在HKLM/Software/Wow6432Node中,而64位应用程序访问注册表时直接范围HKLM/Software,所以访问异常。
解决方案:在写注册表时,32位应用程序要将该注册信息写到64位程序的注册表项中,即HKLM/Software下。
32位应用程序调用64位应用程序并访问其注册表信息。同上。
应用程序如何访问注册表
下面对32位与64位应用程序分别访问注册表进行简单总结:
- 64位程序如何访问64位的注册表(HKLM/Software)
- 64位程序访问64位的注册表,直接到 HKLM/Software。
- 32位程序如何访问32位的注册表(HKLM/Software/Wow6432Node)
- 32位程序访问32位的注册表,WOW64将会截取对HKLM/Software访问,并重定向到HKLM/Software/Wow6432Node。
- 32位程序如何访问64位的注册表(HKLM/Software)
- 在调用函数RegCreateKeyEx创建注册表项时,对第六个参数REGSAM samDesired设置中添加参数KEY_WOW64_64KEY,这样可以实现对64位注册表的访问;
- 在调用函数RegOpenKeyEx打开注册表项时,对第四个参数REGSAM samDesired设置中添加参数KEY_WOW64_64KEY,这样可以实现对64位注册表的访问;
- 64位程序如何访问32位的注册表(HKLM/Software/Wow6432Node)
- 在调用函数RegCreateKeyEx创建注册表项时,对第六个参数REGSAM samDesired设置中添加参数KEY_WOW64_32KEY,这样可以实现对32位注册表的访问;
- 在调用函数RegOpenKeyEx打开注册表项时,对第四个参数REGSAM samDesired设置中添加参数KEY_WOW64_32KEY,这样可以实现对32位注册表的访问;
文件系统重定向
与注册表类似,系统为了解决文件冲突的问题,引入了文件系统重定向的机制,文件系统重定向使32位程序和64位程序的文件与数据分开存放,%systemroot%/system32 目录被保留给64位文件使用,而32位文件会被重定向到%systemroot%/SysWOW64目录。任何32位程序试图访问%systemroot%/system32 目录都会被重定向到%systemroot%/SysWOW64目录。这是系统默认行为,除非程序的线程明确的指明需要关闭文件系统重定向机制。
文件系统重定向开关
针对%windir%\system32,如果我们用32位程序去访问%windir%\system32,不管我们用硬编码还是其它的方式,系统都会自动地给我们转向到%windir%\syswow64目录。这种转向对于每个32位应用程序默认都是打开的,但是这种转向并不总是需要的。因此,系统提供了相关的API来控制文件重定向的打开与关闭。常用的函数有3个,如下所示
- 关闭系统重定向: Wow64DisableWow64FsRedirection
- 打开系统重定向: Wow64RevertWow64FsRedirection
- 打开系统重定向: Wow64EnableWow64FsRedirection
注意:Wow64EnableWow64FsRedirection在嵌套使用的时候不可靠,所以通常用上面的 Wow64RevertWow64FsRedirection来打开文件系统转向功能。
文件与变量的引用
应用程序必须确保对文件名与变量(包括系统变量,环境变量,系统特殊路径等)的引用适用于当前的操作系统,否则会引起如下问题:
- 有些变量只适用于64位操作系统,比如:%ProgramW6432%和FOLDERID_ProgramFilesX64。
- 有些变量只适用于32位操作系统,比如:%windir%\Sysnative。
- 有些变量在不同位数的操作系统下数值不同,比如:%ProgramFiles%和FOLDERID_ProgramFiles。
注意:我们在使用上述变量时必须先判定当前系统的位数信息以确保引用正确。
同理,注册表信息中包含环境变量时也会出现类似问题。比如REG_EXPAND_SZ指向包含环境变量的注册表信息,系统会自动解析键值中包含的%xxx%环境变量。而REG_SZ键值中的%xxx%不会被自动解析。但是可以通过ExpandEnvironmentStrings等系统API进行扩展。当程序调用RegGetValue获取注册表键值的时候,系统会根据当前操作系统的位数信息对环境变量进行扩展,但是应用程序也可以覆盖这种扩展方式。
因此,在64位操作系统中我们要避免使用32位和64位系统不通用的变量,比如:
- %ProgramFiles%, FOLDERID_ProgramFiles(CSIDL_PROGRAM_FILES), FOLDERID_ProgramFilesCommon(CSIDL_PROGRAM_FILES_COMMON)
- FOLDERID_ProgramFilesX64, FOLDERID_ProgramFilesCommonX64
下面对上述的文件与变量的引用以及文件目录重定向进行简单总结。
应用程序安装与启动
64位操作系统上的应用程序分为三种:
32位应用程序
安装目录:C:\Program Files (x86)64位应用程序
安装目录:C:\Program Files双重版本应用程序(Dual-bitness application)
安装目录:参考上述目录
这种方式必须保证32位版本与64位版本兼容。
补充说明:
- 针对文件重定向,只有32位程序访问64位目录时才会被重定向,例如32位程序访问64位目录C:\Windows\system32;而64位程序则可以直接访问32位目录,不存在重定向,例如64位程序可以直接访问C:\Windows\syswow64
- 文件系统重定向只是存在system32和systemWOW64的重定向,而不存在program files和ProgramFiles(x86)的重定向一说,直接硬编码绝对路径即可。
获取系统信息代码片段
// 判断应用程序是否运行在X64系统下
BOOL IsX64System()
{
BOOL bIsWow64 = FALSE;
typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS)(HANDLE, PBOOL);
LPFN_ISWOW64PROCESS pfnIsWow64 = NULL;
// 32位系统的kernel32没有IsWow64Process导出函数,直接使用会有问题
pfnIsWow64 = (LPFN_ISWOW64PROCESS)GetProcAddress(
GetModuleHandle(_T("kernel32.dll")), "IsWow64Process");
if (pfnIsWow64)
{
if (!pfnIsWow64(GetCurrentProcess(), &bIsWow64))
{
// handle error
}
}
return bIsWow64;
}
// 获取系统信息
BOOL GetSystemInfoEx(SYSTEM_INFO *pSystemInfo)
{
BOOL bRet = FALSE;
if (!pSystemInfo)
{
goto Exit0;
}
typedef void (WINAPI *PGNSI)(LPSYSTEM_INFO);
PGNSI pfnGNSI = NULL;
ZeroMemory(pSystemInfo, sizeof(SYSTEM_INFO));
if (IsX64System())
{
// x64下要调用这个API
pfnGNSI = (PGNSI)GetProcAddress(
GetModuleHandle(_T("kernel32.dll")), "GetNativeSystemInfo");
if (!pfnGNSI)
{
goto Exit0;
}
pfnGNSI(pSystemInfo);
}
else
{
// 32位系统调用下面的API
GetSystemInfo(pSystemInfo);
}
bRet = TRUE;
Exit0:
return bRet;
}