64位WINDOWS系统环境下应用软件开发的兼容性问题.docx
- 文档编号:9149210
- 上传时间:2023-05-17
- 格式:DOCX
- 页数:15
- 大小:453.38KB
64位WINDOWS系统环境下应用软件开发的兼容性问题.docx
《64位WINDOWS系统环境下应用软件开发的兼容性问题.docx》由会员分享,可在线阅读,更多相关《64位WINDOWS系统环境下应用软件开发的兼容性问题.docx(15页珍藏版)》请在冰点文库上搜索。
64位WINDOWS系统环境下应用软件开发的兼容性问题
应用软件开发的64位WINDOWS系统环境兼容性
1.64位CPU硬件
目前的64位CPU分为两类:
x64和IA64。
x64的全称是x86-64,从名字上也可以看出来它和x86是兼容的,原先的32位程序可以在x64上运行得很好,这也就是为什么在x64的CPU上能够成功安装32位的Windows操作系统。
现在市场上卖的家用电脑的CPU通常都是x64。
两家顶级CPU公司Intel和AMD分别推出了EM64T(ExtendedMemory64Technology)和AMD64(AdvancedMicroDevices64)。
这两款x64CPU的指令集有很多相似之处,Intel追加的大多数64位指令也与AMD64指令集相兼容,因此Microsoft就不用为两家公司的64位处理器开发各自的64位操作系统。
比如Vistax64和SQL2005x64即可以在EM64T上运行,也可以在AMD64上运行。
IA64是真正的64位CPU,指令集较x86有很大变化,它不兼容32位的程序。
微软的很多产品都不支持IA64,特别是面向个人用户的一系列产品。
但是面向服务器的一些产品还是有IA64版本的,比如Windows2003,SQLserver2005等等。
由于IA64没有x64这么普及,所以本文中讨论的64位CPU如果不加特别说明,指的是x64。
CPU从32位发展到64位的最大的驱动力就是为了让内存突破4GB的限制。
目前的操作系统和应用软件都越做越大,4GB的限制成了软件发展的瓶颈。
然而64位的到来一下子解决了这个问题,让软件发展的前途一片光明。
64位CPU使用64位来表示地址空间,也就是2的64次方,是一个天文数字:
16EB!
这个范围就目前来看太大了,当前任何软件都用不了这么多空间,所以AMD64CPU目前的实现只用了64位中的48位来表示地址空间。
它们分为两块,一块是从0000000000000000到00007FFFFFFFFFFF,还有一块是从FFFF800000000000到FFFFFFFFFFFFFFFF,一共是256TB(如下图的左边),在Windows下,低位的128T是给应用程序使用(UserMode),高位128TB是给系统使用(KernelMode)。
如果将来有需要的话,可以扩展成56位(如上图的中间),最终还可以使用全部的64位(Full64bitimplementation),这样的话,虚拟内存空间将是2的64次方,共16EB。
将来可以把大文件整个的映射到内存里,通常来说,这样会使文件的读写速度会比现在快很多。
上面所说的是虚拟内存,在物理内存方面,AMD64目前的实现支持40位的物理内存,共1TB,这个系统架构以后可以扩展成52位,共4PB。
内存地址空间变成了64位,那也就是意味着寄存器也是64位。
先来看看最具有代表性的RAX寄存器。
从16位时代的AX,到32位时代被扩展成EAX,现在扩展成64位的RAX,它们之间的关系如图
RAX,RBX,RCX,RDX,RBP,RSI,RDI,RSP和RIP都从原来的32位(EAX,EBX,ECX等等)变成了64位,并且还新增加了许多新的64位寄存器和128位寄存器。
2.64位Windows系统的32位代码兼容性设计
WOW系统
能安装64位操作系统的机器,其CPU必然是64位的。
根据IA64和X64架构的区别,MS分别推出了支持IA64以及X64的WINDOWS操作系统,包括XP,VISTA,2003SERVER,2008SERVER等。
之前的32位程序要在64位系统上运行,除了CPU要兼容32位代码(IA64采用软件模拟方式兼容IA32和X86指令,效率不高),在操作系统层面必然也要有一定的措施进行兼容。
由于IA64的系统并不常见,因此我们的讨论针对X64架构的硬件和软件系统。
64位Windows为了兼容32位应用程序,微软采用了WOW64技术。
WOW的全称是WindowsOnWindows,前一个Windows指32位,后一个是64位,意思就是说在64位Windows上的32位Windows。
WOW技术的目的是兼容以前的32位应用程序,使得它们在64位Windows上运行结果和在32位Windows上运行结果一样。
在64位环境下,System32目录下存放的是64位DLL。
为了能兼容32位应用程序,64位Windows也提供了一整套32位的系统DLL。
他们存放在Syswow64目录下。
从名字上看有点混淆:
System32是放64位DLL的,Syswow64是放32位DLL的。
在Windowsx64环境中,用32位的TaskExplorer查看32位应用程序的进程,可以发现,相关联接的系统DLL基本上都是在Syswow64目录下的。
如果使用64位的TaskExplorer查看32位的程序,会发现多出了3个特殊的DLL:
wow64,wow64win和wow64cpu和一个64位的ntdll.dll。
其中64位的ntdll.dll是64位系统下WINDOWS进程都加载的系统DLL,主要提供相关的系统调用的接口。
另外三个DLL就是用来创建64位系统下的模拟32位系统环境。
Wow64.dll管理进程和线程的创建,截获异常分发处理(ExceptionDispatching)和系统调用(ntolkrnl.exe所暴露出来的系统调用),这个DLL还实现了文件系统重定向(FileSystemRedirection),注册表重定向(RegistryRedirection),和注册表反射(RegistryReflection)。
这三种技术会在后面详细介绍。
Wow64cpu.dll管理wow里每个正在运行的线程的32位CPU上下文,并且控制CPU在32位模式和64位模式之间的切换。
Wow64win.dll截获GUI系统调用,这些调用是Win32k.sys暴露出来的。
由此可以看出,在64位系统下得32位程序,用到的系统内核是64位而不是32位的。
上面所说的三个DLL共同实现了在64位Windows上WOW技术。
如下图。
在32位系统中,默认情况下,应用程序可以使用2G内存,另外2G是系统使用的;通过修改配置,应用程序能使用3G内存,这时系统使用1G内存。
在WOW64环境下,一个32位应用程序能使用最多全部的32位地址空间:
4G!
只需要在编译程序的时候,设置IMAGE_FILE_LARGE_ADDRESS_AWARE标志(/LARGEADDRESSAWARE开关),如果不打开这个开关,就和原来一样,只能使用2G内存。
文件系统重定向器(FileSystemRedirector)
在32位Windows上的程序访问system32目录里的DLL,这些DLL是32位的。
在64位windows中System32目录里是64位的DLL,所以32位程序在64位环境下需要另外一个目录作为它system32目录,这个目录就是syswow64,WOW64提供了文件系统重定向器(FileSystemRedirector)。
在WOW64环境下,32位程序访问system32目录里的文件,都会被重定向到syswow64目录。
比如在32位程序中想访问%windir%\system32\user32.dll,那它实际上访问的是%windir%\syswow64\user32.dll。
重定向也有一些例外,下面这些目录是不需要被重定向的:
%windir%\system32\catroot
%windir%\system32\catroot2
%windir%\system32\drivers\etc
%windir%\system32\logfiles
%windir%\system32\spool
使用如下的一个小程序。
先输出system32下的notepad.exe文件大小,再输出system32\drivers\etc\hosts的文件大小。
然后调用WOWAPI:
Wow64DisableWow64FsRedirection把文件系统重定向器禁止掉,然后再次输出两个文件的大小。
voidPrintFileSize(PCWSTRwszFileName)
{
wprintf(L"%ls",wszFileName);
HANDLEhFile=CreateFile(wszFileName,GENERIC_READ,FILE_SHARE_READ,
NULL,OPEN_EXISTING,0,NULL);
//CheckhFilevalue.Ifopeningfilesucceeded,printfilesize
wprintf(L"FileSize:
%dBytes\r\n",GetFileSize(hFile,NULL));
CloseHandle(hFile);
}
voidwmain(int,WCHAR*)
{
PrintFileSize(L"c:
\\windows\\system32\\notepad.exe");
PrintFileSize(L"c:
\\windows\\system32\\drivers\\etc\\hosts");
PVOIDpOldValue;
Wow64DisableWow64FsRedirection(&pOldValue);//disableredirection
PrintFileSize(L"c:
\\windows\\system32\\notepad.exe");
PrintFileSize(L"c:
\\windows\\system32\\drivers\\etc\\hosts");
Wow64RevertWow64FsRedirection(pOldValue);//restoreredirection
}
程序编译成32位后,在64位Windows上运行的结果是:
可以看到在没有禁用文件系统重定向器之前,notepad.exe的大小是151040字节,禁用后是169472字节,打开“我的电脑”到Syswow64目录下找到notepad.exe,它的大小是151040,而system32下则是169472,虽然在程序中写的都是system32下的notepad.exe,实际上访问的却是两个不同的文件。
再看一下hosts文件,它在禁用前后的大小是一模一样的761字节,说明它没有被重定向过,可以打开%windir%\system32\drivers\etc目录看一下,hosts文件就是761字节。
注册表重定向器(RegistryRedirector)
和目录重定向类似,注册表的一些键也需要重定向,它们是:
HKEY_LOCAL_MACHINE\Software
HKEY_USERS\*\Software\Classes
HKEY_USERS\*_Classes
上面的星号(*)表示用户的SID(SecurityID)。
也就是说32位程序访问HKEY_LOCAL_MACHINE\Software\XXX和64位程序访问HKEY_LOCAL_MACHINE\Software\XXX是完全独立,不相关的。
比如在32位程序在HKEY_LOCAL_MACHINE\Software\下新建了一个值ABC,64位程序在访问HKEY_LOCAL_MACHINE\Software\的时候会看不到这个ABC值。
再来做一个试验:
64位Windows提供了两个版本的注册表编辑器(regedit.exe),一个版本是32位的:
%windir%\SysWOW64\regedit.exe,另一个是64位的:
%windir%\regedit.exe。
现在,先打开64位的注册表编辑器,在HKEY_LOCAL_MACHINE\Software\下面新建一个键aaa。
然后关掉64位的注册表编辑器(注意:
必须关掉,不然32位的注册表编辑器无法打开),打开32位的版本。
发现HKEY_LOCAL_MACHINE\Software\和64位的时候很不一样,而且看不到aaa键。
现在,再新建一个aaa2。
关掉32位版本,再打开原先的64位版本,可以发现HKEY_LOCAL_MACHINE\Software\只有aaa键,没有aaa2键。
打开wow6432Node,会惊奇的发现原来aaa2在这里。
实际上当32位程序访问或修改注册表的键时,注册表重定向器把它们重定向到了wow6432Node键。
如果一个32位程序就想访问64位的注册表,或者64位程序想访问32位的注册表,在调用注册表API:
RegCreateKeyEx,RegDeleteKeyEx和RegOpenKeyEx的时候可以加上标志KEY_WOW64_64KEY或者KEY_WOW64_32KEY。
前者表明不管程序是32位或者64位,一律访问64位的注册表,后者表明一律访问32位注册表。
和文件系统重定向相似,注册表重定向也有一些例外:
HKEY_LOCAL_MACHINE\Software\Classes
HKEY_LOCAL_MACHINE\Software\Microsoft\COM3
HKEY_LOCAL_MACHINE\Software\Microsoft\EventSystem
HKEY_LOCAL_MACHINE\Software\Microsoft\Ole
HKEY_LOCAL_MACHINE\Software\Microsoft\Rpc
HKEY_USERS\*\Software\Classes
HKEY_USERS\*_Classes
WOW64使用反射技术(Reflection)来处理这些例外情况。
比如在32位程序在HKEY_LOCAL_MACHINE\Software\Classes下新建了一个值XYZ,这个值会立刻复制到64位的HKEY_LOCAL_MACHINE\Software\Classes下面,所以当64位程序在访问HKEY_LOCAL_MACHINE\Software\Classes的时候就能访问到一样的XYZ值。
Windows还提供了2个API来打开和禁用反射功能:
RegEnableReflectionKey和RegDisableReflectionKey。
对于COM使用的注册表HKEY_LOCAL_MACHINE\Software\Classes\CLSID,它十分特别,是特例中的特例。
在处理这个键时,注册表重定向器会做一个相对复杂点的判断:
如果注册的32位COM是localserver(客户端和COM是两个独立的进程[5]),那注册的信息会被反射到64位,如果是in-proc(COM是一个DLL,客户端在使用这个COM的时候会把DLL加载到客户端进程[5]),那不会被反射。
为什么需要这种判断呢?
因为一个32位的localserverCOM是运行在一个独立的进程中,它即能够被32位程序调用,也能被64位程序调用。
但是32位in-procCOM的话是需要把这个32位DLL载入到64位进程中去,这是做不到的,也就是说32位in-procCOM无法为64位程序提供服务,那也就没有必要把它的注册表信息反射到64位了。
驱动程序和系统服务
对于驱动程序,64位系统只支持64位的驱动程序,因此对于代码中有驱动或者系统服务的情况,必须重新编译成64位的可执行代码,并且由于64位系统有强制的签名验证,未签名的驱动只能在开启调试状态的模式下加载。
3.64位编程代码注意事项(C++)
与JAVA等依靠虚拟机解释运行的程序不同,C/C++代码通常被编译成机器码执行,虽然提供了更快地速度和更强的功能,但也带来很多代码兼容性的问题。
数据类型
C++数据类型的变化,从下面的表格中可以看到:
char,short,int,long,float,double在64位Windows环境下的大小没有变化,但是size_t,time_t和指针的大小都从32变成了64位,8个字节。
TypeName
x86
x64windows
char
8
8
short
16
16
int
32
32
long
32
32
float
32
32
double
64
64
size_t
32
64
在Windows下,一些数据类型也发生了变化,这里列举了一些最常用的类型。
WPARAM,LPARAM,LRESULT和各种HANDLE(比如HWND,HKEY等等)本质上都是指针,所以他们都变成了64位。
typedefUINT_PTRWPARAM;
typedefLONG_PTRLPARAM;
typedefLONG_PTRLRESULT;
typedefvoid*HANDLE;#defineDECLARE_HANDLE(name)structname##__{intunused;};typedefstructname##__*name
DECLARE_HANDLE(HWND);DECLARE_HANDLE(HKEY);
我们在32位CPU下编写的正确代码,到了64位下可能会出问题,这些问题通常只能通过人工逻辑来逐个判断修改。
特殊数字兼容性
如果程序里出现了下列这些数字,有可能需要再检查一遍代码,看看有没有问题。
4:
32位时代一个指针的字节数
32:
32位时代一个指针的位数
0x7fffffff:
32位有符号整数的最大值,有时用来进行位操作(与、或、异或)
0x80000000:
32位有符号整数的最小值
0xffffffff:
32位无符号整数的最大值。
同时也是有符号整数-1,通常会被用来作为出错值,或者无效值。
还有其他一些情况无法一一列举,但是在编成的时候利用SDK中定义的一些宏往往能避免这些问题。
整数保存在双精度浮点数中
下面的程序,32位中a等于c,但在64位中a就不等于c了
size_ta=-1;
doubleb=a;
a--;
b--;
size_tc=b;//x86:
a==c
//x64:
a!
=c
原因就是双精度类型double一共有64位(不管是32位环境还是64位环境),根据IEEE-754,double用1位表示数字的符号,用11位表示指数,52位表示尾数。
尾数也就是所能表示的数字的精度。
尾数的位数越多,所能表示的数字的精度越高。
32位(x86)时代的size_t是32位,有52位尾数的double类型能绰绰有余的精确表示size_t。
但是到了64位时代,size_t是64位,这时double的52位尾数没有办法精确表示size_t,所以导致了a不等于c。
所以在64位环境中,尽量不要将整数保存在双精度浮点数中。
特别小心位移操作
看下面这段代码,想想i分别会是什么值。
(__int64是64位有符号整数)
__int64i;
i=1<<31;//i=?
i=1<<32;//i=?
您可能会认为一个是0x0000000080000000,另一个是0x0000000100000000。
实际上在第二行运行后i是0xffffffff80000000;第三行运行后i是0x0000000000000000,为什么呢?
编译器在处理1<<31的时候是按照32位整型来处理,得到的结果是0x80000000,由于i是64位有符号整数,在赋值操作的时候,把0x80000000,就转成了0xffffffff80000000。
同样,第三行的1<<32也是按照32位整型来处理的时候,得到的是0,所以把32位整数0转成64位整数后还是0。
正确的写法应该如下,先把1转成64位,这样在位移操作的时候已经是64位了。
__int64i;
i=__int64
(1)<<31;
i=__int64
(1)<<32;
5.不要混合使用32位类型和64位类型
对于此要点,大家需要特别注意,一般的测试比较难发现这个问题,因为它在数字较小的情况下,一切正常,一旦数字变大,则会出现严重的错误。
先来看下面一段代码,想想有什么不正确的地方。
size_tCount=SomeValue;
for(unsignedIndex=0;Index {...} 前面说过size_t在64位环境下是64位,unsigned在64位环境下还是32位,如果Count是一个很大的数字,超过了32位无符号整数的最大值,那条件Index 正确的写法应该是把Index也申明成size_t类型,和Count一致。 再看下面这个例子。 intx=100000; inty=100000; intz=100000; __int64r1=x*y*z;//-1530494976 __int64r2=__int64(x)*y*z;//1000000000000000 __int64r3=x*y*__int64(z);//141006540800000 如果.NET程序使用了32位的in-procCOM,在编译的时候需要指定它的目标平台为x86 .NET1.0和.NET1.1没有64位版本,但是从.NET2.0开始提供了x64版本。 C#,VB.NET等.NET语言的程序在编译的时候默认的目标平台(Platformtarget)是AnyCPU,意思是如果运行环境是32位的,那么按照32位应用程序运行,如果在64位Windows上运行,则按照64位应用程序运行。 回想一下“注册表重定向器”里提到的: 64位应用程序没有办法得到32位in-procCOM的注册信息。 也就是没有办法使用这个COM。 所以如果.NET程序使用到了32位in-procCOM,那就不要使用默认的“AnyCPU”选项,我们应该指定它为x86。 要不然您的程序在x64的Windows环境下,将无法调用这个COM。 下图是VisualStudioOrcas的C#工程属性。 "Detect64-bitPortabilityIssues"编译开关 VisualStudio里面的“Detect64-bitPortabilityIssues”编译开关。 这样,编译器可以帮您发现大多数的64位问题。 虽然打开这个开关会稍微增加一些编译时间,但是绝对值得这么去做。 代码检查(CodeReview) 这是最重要的一点,前面所说的这些要点能覆盖大多数的问题,但不是全部。 所以在写完代码后,一定要做检查,最好找别人帮您检查。 像微软和其他知名的软件公司都十分重视代码检查,不经别人检查的代码是不允许提交的。 让我们用一个例子来结束这篇文章,请看下面这段32位时代的代码 PVOIDp; .......... intn=int(p); .......... p=PVOID(n); 如果把这段代码移植到64位,很多人会写成这样: PVO
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 64 WINDOWS 系统 环境 应用软件 开发 兼容性问题