CE6的一个设计目标是,向后兼容第三方的二进制应用程序。二进制形式的兼容包括:
1、 兼容core库中的一些导出内容(如coredll.dll)
2、 兼容所有导出函数的接口
3、 兼容函数的功能,除非是函数因为新的内存布局和安全性而做的改变
4、 即使函数不再支持了,但保持在coredll中的导出。这能让应用成功加载,但是运行时候可能会失败。不过即使使用不再支持的函数,失败的比例也不大,后面会继续讨论这个问题。
我们希望移植应用到CE6,能做最少的改动。大部分应用不需要改动,就能在CE6上正常运行。对应用程序的影响,取决于应用程序是否按规则编写。一个良好按规则编写的应用,是只使用SDK功能、不使用其他文档没有提及的功能、不使用OAL函数、不使用本文提到的其他方面(如传递句柄的值、传递内存指针、假定组件的运行是和内存映射文件那样,等等)。所谓的SDK功能,是指SDK安装后有效的功能,如mobile的PPC SDK或者PB生成的CE SDK。应用只使用文档中列出的SDK函数,那么移植到CE6上,改动就会很少。
下面来看看CE6的变动,CE6提供新的内存管理,会影响到第三方的应用程序。
函数有三个主要的变动:
1、 函数的改变:
下面列出CE6完全改变了的函数,这些函数依然由coredll导出,但它们的调用会返回错误或者什么都不干。如下:
内存指针函数,如MapCallerPtr。每个进程现在有2GB的空间(不包括内核那的2GB),从一个进程传递内存指针到另一个进程,包括读其他进程的内存,或者建立进程内存的虚拟复制。这些操作都由标准的SDK函数提供,而不再是基于slot机制的函数。
进程索引函数,如GetProcessIndexFromId。进程数目已经增长到32768个,因此CE6中不再提供这些函数(CE只能有32个进程,提供这些函数还比较方便)。
权限函数,如SetProcPermissions。由于现在没有进程slot的机制了,所以也不提供这些功能。现在需要通过OpenProcess来得到一个有效进程句柄,然后通过标准SDK函数,去访问进程的内存。
所有这些无用的函数,现在通过debug check来检查。放到一个调试区域中了,在coredll中打开(DBGDEPRE == 0x40)
2、 只在内核中使用的函数。这类函数只能在0x80000000以上的地址内被调用(内核空间),这对应用程序影响不大,大部分都是硬件相关的函数,如中断、物理地址映射、进程内存分配等。这些函数会影响到驱动。
3、 不鼓励使用的函数。这类函数有了新的替换函数,或者工作的更好了。这里有些函数包括PSL服务的检查(WaitForAPIReady由于是不能主动激活,现由IsAPIReady代替,完全支持128个函数),文件映射等(CreateFile/CreateFileMapping替换为 CreateFileForMapping)。
PB发布了一个PC端工具,应用程序(exe和dll)可以来检查函数是否属于以上三种类型,工具会列出:
Name: ceappcompat.exe
Location: public/common/oak/bin/i386
Usage: 运行桌面工具,在一个目录下去扫描 dll/exe或者所有的文件
Results:最后,工具生产HTML文件,列出所有符合的函数,和这些函数的用法。
这个工具检查二进制文件中,函数调用列表中的模块。但这个工具不能检查出,通过指针方式调用的函数。
句柄:
CE6以前的版本中,句柄是全局的,所有应用都能访问到。
CE6中,句柄是每个进程特有的。一个应用程序中的句柄,对于其他进程是没有意义的。除非这些句柄使用DuplicateHandle函数,映射到其他进程中。
对应用程序的影响:如果应用程序从其他进程接收到句柄的值,需要调用DuplicateHandle创建一个句柄的备份,到当前的进程中,才能被正确的使用上。
CE6以前的版本也有DuplicateHandle函数,但只针对mutex、semaphore、event等对象。CE6中扩展为支持任何句柄,如函数句柄、消息队列句柄、进程线程句柄或者其他进程的句柄指针等。
内存指针:
CE6以前的版本中,虚拟内存(VM:virtual memory)是基于一个内存分布的设计下,所有应用的虚拟内存都处于同一个4GB的空间中。内核有自己的2GB空间,下端的2GB空间分了32个slot给应用使用。这种设计下,从一个进程得到另一个进程的内存指针就比较容易。
CE6中,虚拟内存是基于并列的多个内存分布设计的,内核依然有2GB空间。但每个应用邮自己独立的2GB空间。它们之间有一小块共享堆和dll的空间,这里不做讨论。其他的空间归属各个进程,因此通过简单的传递内存指针,来达到访问的目的是不可能的。
对应用的影响,如果应用以前通过MapCallerPtr来得到其他进程的内存指针,那CE6中就不能这样做了。把这些直接读写进程内存的代码,修改为SDK函数ReadProcessMemory 或WriteProcessMemory。这些函数能让你获得其他进程的权限(用SDK函数OpenProcess打开的句柄)。
这是CE6以前版本中,进程空间只有32M的限制导致的问题。
注意,不要使用SDK函数去读写内核进程的内存。应用程序是不能访问内核空间的。然而应用程序也不能写入共享堆0x70000000的地址,应用程序只能对共享堆只读,而内核可以读写共享堆的内容。
函数处理:
CE6以前版本中,如果应用传入一个无效方式到PSL服务,内核会简单的继续PSL服务。有一些情况可能会让PSL产生异常,或者返回错误。
CE6中,如果产生这样情况,在内核层就被拒绝了,由PSL服务决定是否标记为函数错误句柄。如果PSL不标记为函数错误句柄,一个异常会返回到调用者函数中。如果调用者函数没有异常处理的话,这个线程就会结束。如果PSL服务通过内核标记了函数错误句柄,内核会让错误句柄继续调用下去,这类似CE6以前的做法。
函数的信任检查:
这不属于向后兼容的内容,但注意的是CE6中所有信任检查函数,都会返回可信任。OEM可以通过SYSGEN_CERTMOD,来生产带有信任检查的镜像。
中断函数:
CE6以前版本中,应用可以使用中断函数。WM中只能在被信任应用中使用。
CE6中,中断函数只能在内核态中或者用户态驱动中使用。如在用户态其他组件中使用,会返回FALSE。
对应用的影响很小,大部分要使用这些函数是驱动,而不是第三方应用。如想在内核态外使用的话,那只能为应用编写驱动(用户态驱动较好)了。
物理内存映射:
CE6以前版本,所有应用都能用VirtualCopy或MmMapIoSpace来建立物理和虚拟内存的映射。WM中只能是可信任模块使用。
CE6中,这部分调用只能在内核态或者用户态驱动中。这可能限制了第三方软件的一些使用,但这样设计能加强稳定性和弹性。
可以通过内核态驱动或者公开的组件,来给应用使用这部分功能。下面会提到。
OAL的Ioctl代码:
CE6以前,用户态可以通过KernelIoControl或KernelLibIoControl,调用OAL的代码。
CE6中,为了提供更好的安全性,能给用户态调用的函数被预先定义好(即使应用是被信任的,CE6中没有应用信任的概念),限制在部分的功能内。OEM厂商必须明白和确认,那些是用户态应用程序可以使用的ioctl。例如,应用程序需要使用IOCTL_HAL_REBOOT,除非在应用的ioctl列表中添加此功能,否则还是不能调用。下面列出部分用户态缺省可以调用的ioctl。
IOCTL_HAL_GET_CACHE_INFO
IOCTL_HAL_GET_DEVICE_INFO
IOCTL_HAL_GET_DEVICEID
IOCTL_HAL_GET_UUID
IOCTL_PROCESSOR_INFORMATION
内存映射文件:
CE6以前版本,使用内存映射的文件的虚拟内存,是在应用程序slot外面空间中申请的。那么个文件对应地址是唯一的,而且所有应用都可以访问到。另外,获取访问此映射文件的返回值,和使用相同名字申请映射文件的返回,是不一样的。
CE6中,申请内存映射文件的虚拟地址,是在当前应用空间内部了。那么一个进程创建了的映射文件,不能被其他进程访问,除非是由SDK函数打开以后。同样,对此映射文件的权限完全由创建者控制,而不会关心之前这个对象有没有存在过。
如果应用有传递内存映射文件的句柄,给其他进程时候的话。那现在就不能这样去访问了。需要把此文件的名字传递给其他进程,使用这个打开后,才能进行读写。假如应用用只读权限打开一个映射文件,而系统已经存在一个相同名字、可读写的映射文件。CE6以前的版本中,打开者也拥有了读写权限。而CE6中,打开者只能有只读的权限。因此,检查应用有使用到CreateFileMapping 和 MapViewOfFile的地方。
http://blogs.msdn.com/ce_base/archive/2006/11/14/application-compatibility-in-windows-ce-6-0.aspx