7.1.1 内存映射文件
内存映射文件允许将文件映射到一段内存区域,然后直接访问这段内存区域就能实现对文件的访问,而不需要通过调用文件读写API函数来实现,读写文件就跟读写一段内存区域一样高效简单,而且对内存的更新操作将写入到文件中。内存映射文件还可以用于实现进程间通信,此时需要使用命名内存映射文件对象。使用内存映射文件首先需要创建一个内存映射文件对象,创建好之后需要将内存映射文件对象映射到进程的视图,读写操作完成后需要释放视图。
1. 创建内存映射文件
创建内存映射文件首先需要打开文件句柄,然后调用CreateFileMapping()函数实现:
HANDLE CreateFileMapping(
HANDLE hFile,
LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
DWORD flProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
LPCTSTR lpName
);
l 参数hFile指定将被映射到内存的文件句柄。如果这个参数被设置为(HANDLE)INVALID_HANDLE_VALUE,系统将从物理内存中分配空间来创建内存映射文件对象。
l 参数lpFileMappingAttributes指定安全属性,在Win CE7中不支持,设置为NULL。
l 参数flProtect指定对内存映射文件视图的保护访问权限,表7-8列出了可以取的值。
可取值 |
说明 |
PAGE_READONLY |
对映射区域页面拥有只读访问权限,对页面的写操作或执行都将导致访问错误。文件句柄打开时必须指定GENERIC_READ访问权限。 |
PAGE_READWRITE |
对页面具有读写访问权限,文件句柄打开时必须同时指定GENERIC_READ和GENERIC_WRITE访问权限。 |
PAGE_WRITECOPY |
在Win CE中不支持 |
l 参数dwMaximumSizeHigh和参数dwMaximumSizeLow都用于指定内存映射对象的大小,分别代表高32位值和低32位值。如果将这两个参数都设置为0,那么内存映射对象的大小将被设置为对应文件的大小。
l 参数lpName指定内存映射文件对象的名字,用于创建命名对象。命名对象可以在不同的进程之间共享数据,如果指定的名字在系统中已经存在,那么将不会新建内存映射文件对象,而是打开已经存在的对象,并返回句柄,这时多个进程实际上操作的是同一个内存映射文件对象。如果希望创建匿名对象,那么将这个参数设置为NULL。
如果函数调用成功,将返回内存映射文件对象的句柄;如果失败返回NULL。
1. 获取内存映射文件视图
创建好内存映射文件对象之后,还不能像文件那样直接对句柄进行读写操作,还需要首先将内存映射文件对象映射到进程地址空间内的视图,然后就可以像操作内存中的数据一样,通过指针直接对内存映射文件进行读写。函数MapViewOfFile()实现这个功能:
LPVOID MapViewOfFile(
HANDLE hFileMappingObject,
DWORD dwDesiredAccess,
DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow,
DWORD dwNumberOfBytesToMap
);
l 参数hFileMappingObject为内存映射文件对象的句柄。
l 参数dwDesiredAccess指定对文件视图的访问权限,表7-9列出了可取值。
表7-9 文件视图的访问权限
值 |
说明 |
FILE_MAP_ALL_ACCESS |
指定读写访问权限,将文件映射为读写视图 |
FILE_MAP_READ |
指定只读访问权限 |
FILE_MAP_WRITE |
指定读写访问权限 |
l 参数dwFileOffsetHigh和dwFileOffsetLow指定从文件开始位置的偏移量,从这个位置开始的文件内容将被映射到文件视图中。
l 参数dwNumberOfBytesToMap指定映射的字节数,如果这个参数被设置为0,将映射到文件结束。
如果函数调用成功,将返回映射视图的起始地址,使用这个返回值就可以读写文件数据;调用失败将返回NULL。
2. 将映射视图写回磁盘
如果对文件映射视图进行写入操作,此时修改的内容还只是放在内存中,需要进一步调用FlushViewOfFile()函数将视图中的数据写回到磁盘:
BOOL FlushViewOfFile(
LPCVOID lpBaseAddress,
DWORD dwNumberOfBytesToFlush
);
l 参数lpBaseAddress指定映射视图内写回数据的起始地址。可以指定为映射视图内部的任意位置,我们只需要对那些真正被写过的区域写回磁盘即可。
l 参数dwNumberOfBytesToFlush指定写回的字节数。
写回磁盘成功,返回TRUE;失败则返回FALSE。
3. 释放内存映射文件视图
使用完映射视图后需要将其从进程的地址空间中释放,Win CE提供UnmapViewOfFile()实现:
BOOL UnmapViewOfFile(
LPCVOID lpBaseAddress
);
l 参数lpBaseAddress必须指定为映射视图的基地址,也就是之前调用MapViewOfFile()的返回值。
不需要指定映射视图的大小,因为系统负责维护每段映射视图的大小信息。如果释放文件文件视图成功,返回TRUE;失败则返回FALSE。一旦将映射文件视图释放,指针lpBaseAddress就变为无效,不能再对其进行读写操作。一个好的风格是在调用UnmapViewOfFile(lpBaseAddress)函数之后,直接将指针lpBaseAddress设置为NULL。
对内存映射文件使用完之后,需要将其关闭,实际上就是调用CloseHandle()关闭之前CreateFileMapping()返回的句柄。
下面的代码演示如何在进程内创建内存映射文件视图,并将其释放:
HANDLE hMapFile; // 内存映射文件对象句柄
HANDLE hFile; // 文件句柄
LPVOID lpMapAddress; // 指向内存文件映射区域的指针,通过这个指针来进行读写
hFile = CreateFile(lpcTheFile,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile != INVALID_HANDLE_VALUE)
{
hMapFile = CreateFileMapping(
hFile, // 文件句柄
NULL, // 不支持安全属性
PAGE_READWRITE, // 读写访问权限
0, // 内存映射文件对象大小的高位部分
dwFileMapSize, //内存映射文件对象大小的低位部分
NULL); // 非命名内存映射文件
If(hMapFile)
{
lpMapAddress = MapViewOfFile(
hMapFile, // 内存映射文件对象的句柄
FILE_MAP_ALL_ACCESS, // 读写
0, // 文件偏移的高32位部分
dwFileMapStart, //文件偏移的低32位
dwMapViewSize); // 映射字节数
if(lpMapAddress)
{
//映射成功,读写访问
…
//使用完毕,需要释放内存映射文件视图
UnmapViewOfFile(lpMapAddress);
}
CloseHandle(hMapFile); //关闭内存映射文件对象
}
CloseHandle(hFile); //关闭文件句柄
7.1.2 存储管理器
存储管理器负责管理系统中的所有外部存储设备,包括所有的文件系统和块设备驱动,所有的文件,数据甚至音量控制操作都需要通过存储管理器来完成。存储管理器主要包括3大部分:块设备驱动管理器,分区管理器和文件系统驱动 (FSD) 管理器。其中块设备驱动为物理存储器的驱动程序;分区驱动允许在物理存储设备上划分多个存储分区,每个分区可以使用不同的文件格式进行格式化,分区驱动的主要任务是将接收到分区地址转换成设备的底层物理地址,并将请求传给下面的块设备驱动;文件系统驱动将数据以文件和文件夹的结构组织,并负责将数据传给下面的分区驱动。
1. 打开,关闭及枚举存储设备
函数OpenStore()用于打开指定名字的存储设备:
HANDLE OpenStore(
LPCSTR szDeviceName
);
l 参数szDeviceName指定存储设备的名称,如DSK1。
如果打开设备成功,函数返回设备的句柄;失败则返回INVALID_HANDLE_VALUE。比如当系统中不存在指定名称的设备时,函数调用就会失败。
使用完设备句柄之后,需要将其关闭,调用函数CloseHandle()实现。
很多时候,我们并不知道系统中存在哪些设备,这时需要便利系统中的设备,获取每个设备的信息。Windows CE中提供了FindFirstStore()和FindNextStore()函数来实现。
FindFirstStore()函数获取系统中注册的第一个存储设备信息,并返回搜索句柄,这个返回的搜索句柄将被用于后续的搜索:
HANDLE WINAPI FindFirstStore(
PSTOREINFO pStoreInfo
);
l 参数pStoreInfo为输出参数,是一个指向结构体STOREINFO的指针,返回该设备的详细信息。
结构体STOREINFO的定义如下:
typedef struct {
DWORD cbSize;
TCHAR szDeviceName[DEVICENAMESIZE];
TCHAR szStoreName[STORENAMESIZE];
DWORD dwDeviceClass;
DWORD dwDeviceType;
STORAGEDEVICEINFO sdi;
DWORD dwDeviceFlags;
SECTORNUM snNumSectors;
DWORD dwBytesPerSector;
SECTORNUM snFreeSectors;
SECTORNUM snBiggestPartCreatable;
FILETIME ftCreated;
FILETIME ftLastModified;
DWORD dwAttributes;
DWORD dwPartitionCount;
DWORD dwMountCount;
} STOREINFO, *PSTOREINFO;
l 成员cbSize为结构体STOREINFO的大小,总是为sizeof(STOREINFO)。
l 成员szDeviceName为设备的名称,为一个字符数组,最大长度只能为8。
l 成员szStoreName为对用户更具可读性的存储名称,最大长度为32。
l 成员dwDeviceClass为设备类别,可取值为STORAGE_DEVICE_CLASS_BLOCK或 STORAGE_DEVICE_CLASS_MULTIMEDIA。
l 成员dwDeviceType为设备底层存储介质类型,可取值有:
n STORAGE_DEVICE_TYPE_ATA
n ・ STORAGE_DEVICE_TYPE_ATAPI
n ・ STORAGE_DEVICE_TYPE_CDROM
n ・ STORAGE_DEVICE_TYPE_CFCARD
n ・ STORAGE_DEVICE_TYPE_DOC
n ・ STORAGE_DEVICE_TYPE_DVD
n ・ STORAGE_DEVICE_TYPE_FLASH
n ・ STORAGE_DEVICE_TYPE_PCCARD
n ・ STORAGE_DEVICE_TYPE_PCIIDE
n ・ STORAGE_DEVICE_TYPE_REMOVABLE_DRIVE
n ・ STORAGE_DEVICE_TYPE_REMOVABLE_MEDIA
n ・ STORAGE_DEVICE_TYPE_SRAM
n ・ STORAGE_DEVICE_TYPE_UNKNOWN
n ・ STORAGE_DEVICE_TYPE_USB
l 成员sdi为设备在系统中的全局标识
l 成员dwDeviceFlags为对设备的访问支持属性,可取的值有:
n STORAGE_DEVICE_FLAG_MEDIASENSE
n STORAGE_DEVICE_FLAG_READONLY
n STORAGE_DEVICE_FLAG_READWRITE
n STORAGE_DEVICE_FLAG_TRANSACTED
l 成员snNumSectors为设备上包含的扇区个数。
l 成员dwBytesPerSector为该设备上使用的每个扇区的字节数。这样设备总的容量就可以通过snNumSectors乘以dwBytesPerSector得到。
l 成员snFreeSectors为该设备上空闲的未被分配的扇区个数。
l 成员snBiggestPartCreatable为该设备上能够分配给新创建分区的最大扇区个数。
l 成员ftCreated为设备最近一次被格式化的时间。
l 成员ftLastModified为设备的分区表最近一次被更改的时间。
l 成员dwAttributes为卷的属性,可能的取值为:
n STORE_ATTRIBUTE_AUTOFORMAT
n STORE_ATTRIBUTE_AUTOMOUNT
n STORE_ATTRIBUTE_AUTOPART
n STORE_ATTRIBUTE_READONLY
n STORE_ATTRIBUTE_REMOVABLE
n STORE_ATTRIBUTE_UNFORMATTED
l 成员dwPartitionCount为设备所包含的分区个数
l 成员dwMountCount为该设备上已经以卷的形式被挂载到文件系统中的分区个数。为了访问分区中的数据,首先需要将分区挂载到文件系统中。
打开第一个设备之后,函数FindNextStore()将继续往下搜索获取下一个设备:
BOOL WINAPI FindNextStore(
HANDLE hSearch,
PSTOREINFO pStoreInfo
);
l 参数hSearch为调用FindFirstStore()函数返回的搜索句柄。
l 参数pStoreInfo为输出参数,也是一个指向结构体STOREINFO的指针,返回当前搜索到设备信息。
函数调用成功,返回TRUE,否则返回FALSE。
为了遍历每一个设备的信息,需要一直使用FindFirstStore()返回的搜索句柄调用FindNextStore()函数,直到函数返回FALSE为止。
遍历完成后,需要调用FindCloseStore()函数将搜索句柄关闭:
BOOL WINAPI FindCloseStore(
HANDLE hSearch
);
l 参数hSearch必须为FindFirstStore()函数返回的搜索句柄。
除了通过遍历设备获取设备的信息外,还可以直接通过OpenStore()函数打开的设备句柄来访问指定设备的信息,这通过显示调用GetStoreInfo()函数实现:
BOOL WINAPI GetStoreInfo(
HANDLE hStore,
PSTOREINFO pStoreInfo
);
l 参数hStore必须为OpenStore()函数打开过的设备句柄。
l 参数pStoreInfo为输出参数,返回对应设备的信息 (STOREINFO)。
注意对于上面所有用到STOREINFO结构体获取设备信息的函数,在调用函数之前,必须首先将STOREINFO结构体中的成员cbSize设置为sizeof(STOREINFO),否则函数执行将出错。
4. 打开,关闭及枚举存储设备内的分区
打开存储设备之后,就可以访问该设备的所有分区。访问一个分区之前同样需要先将其打开,使用分区完后需要将其关闭,还可以枚举一个设备内包含的所有分区。
打开指定设备的一个分区,使用函数OpenPartition()实现:
HANDLE WINAPI OpenPartition(
HANDLE hStore,
LPCTSTR szPartitionName
);
l 参数hStore必须为OpenStore()函数打开的设备句柄。
l 参数szPartitionName指定将被打开的分区的名称。
如果打开分区成功,函数返回分区的句柄;否则返回INVALID_HANDLE_VALUE。记住使用完分区句柄之后,需要调用CloseHandle()将其关闭。
枚举一个设备内分区需要调用FindFirstPartition()和FindNextPartition()函数实现:
HANDLE WINAPI FindFirstPartition(
HANDLE hStore,
PPARTINFO pPartInfo
);
l 参数hStore指定分区所在的设备句柄。
l 参数pPartInfo为输出参数,用于返回设备内第一个分区的信息。这是一个指向结构体PARTINFO的指针。
如果函数调用成功,将返回一个分区搜索句柄;失败则返回INVALID_HANDLE_VALUE。
得到分区搜索句柄后,不断调用FindNextPartition()函数就能够依次遍历每一个分区,知道该函数返回FALSE为止:
BOOL WINAPI FindNextPartition(
HANDLE hSearch,
PPARTINFO pPartInfo
);
l 参数hSearch必须为上面FindFirstPartition()函数返回的搜索句柄。
l 参数pPartInfo为输出参数,为一个指向结构体PARTINFO的指针,返回当前分区的信息。
对设备内的分区遍历完成后,需要调用FindClosePartition()函数将之前打开的分区搜索句柄关闭:
BOOL WINAPI FindClosePartition(
HANDLE hSearch
);
唯一的参数hSearch指定将被关闭的分区搜索句柄。
下面介绍用于存放分区信息的结构体PARTINFO,它的定义如下:
typedef struct {
DWORD cbSize;
TCHAR szPartitionName[PARTITIONNAMESIZE];
TCHAR szFileSys[FILESYSNAMESIZE];
TCHAR szVolumeName[VOLUMENAMESIZE];
SECTORNUM snNumSectors;
FILETIME ftCreated;
FILETIME ftLastModified;
DWORD dwAttributes;
BYTE bPartType;
} PARTINFO, *PPARTINFO;
l 成员cbSize总是被设置为这个结构体的大小sizeof(PARTINFO)。
l 成员szPartitionName为分区的名称,这是一个字符数组,其最大长度为32。
l 成员szFileSys为分区使用的文件系统名称,其最大长度也为32。
l 成员szVolumeName为分区被挂载到系统中使用的卷名称。
l 成员snNumSectors为分区包含的所有扇区个数。
l 成员ftCreated和ftLastModified分别代表分区的创建时间和最后一次被修改的时间。
l 成员dwAttributes代表分区的属性,可取的值如下:
n PARTITION_ATTRIBUTE_AUTOFORMAT:为自动格式分区
n PARTITION_ATTRIBUTE_BOOT:为可引导分区
n PARTITION_ATTRIBUTE_EXPENDABLE:为可扩展分区
n PARTITION_ATTRIBUTE_MOUNTED:为已经被挂载的分区
n PARTITION_ATTRIBUTE_READONLY:为只读分区
l 成员bPartType代表分区类型或标识,可取的值有。
n DOS下的分区:PART_DOS2_FAT,PART_DOS3_FAT,PART_DOS4_FAT,PART_DOS32 (是FAT32)
n 只被用于逻辑块地址(LBA): PART_DOS32X13, PART_DOSX13, PART_DOSX13X。
n 无效类型:INVALID
n 位置分区类型:PART_UNKNOWN
另外通过打开的分区句柄也能读取相应分区的信息,函数GetPartitionInfo()实现这个功能:
BOOL WINAPI GetPartitionInfo(
HANDLE hPartition,
PPARTINFO pPartInfo
);
l 参数hPartition必须为之前通过OpenPartition()函数打开的分区句柄。
l 参数pPartInfo为输出参数,返回指定分区的信息。
如果函数调用成功,返回TRUE,否则返回FLASE。
打开一个分区句柄之后,可以修改分区的属性,以及修改分区的名称。
函数SetPartitionAttributes()用于设置一个分区的属性:
BOOL WINAPI SetPartitionAttributes(
HANDLE hPartition,
DWORD dwAttrs
);
参数hPartition为已经打开的分区句柄,参数dwAttrs指定新的分区属性,可取值参考前面介绍的分区信息的属性。
对分区进行重命名,使用函数RenamePartition()实现:
BOOL WINAPI RenamePartition(
HANDLE hPartition,
LPCTSTR szNewName
);
参数hPartition指定分区句柄,参数szNewName指定分区将使用的新名称。
5. 创建及删除分区
往打开的设备内新创建一个指定名字,类型和扇区个数的分区,通过调用函数CreatePartitionEx()实现:
BOOL WINAPI CreatePartitionEx(
HANDLE hStore,
LPCTSTR szPartitionName,
BYTE bPartType,
SECTORNUM snNumSectors
);
l 参数hStore为将在其上新创建分区的设备句柄。
l 参数szPartitionName指定新创建分区的名称。
l 参数bPartType指定分区的类型,分区类型将被用于确定预计使用的文件系统。
l 参数snNumSectors指定新创建分区的包含的扇区个数,为一个64位整数值。
删除设备内的指定分区使用函数DeletePartition()实现:
BOOL WINAPI DeletePartition(
HANDLE hStore,
LPCTSTR szPartitionName
);
l 参数hStore指定设备句柄。
l 参数szPartitionName指定将被删除的分区名称。
注意只有当分区没有被挂载到系统中,才能对其进行删除,否则删除操作将失败。
6. 挂载及卸载分区
挂载一个指定的分区都文件系统上,使用函数MountPartition()实现:
BOOL WINAPI MountPartition(
HANDLE hPartition
);
l 参数hPartition必须为之前调用OpenPartition()函数打开的分区句柄。
相对的,访问完分区之后,需要将分区从文件系统中卸载:
BOOL WINAPI DismountPartition(
HANDLE hPartition
);
参数hPartition为打开的分区句柄。
还可以一次性将指定设备上已经挂载的分区全部卸载下来:
BOOL WINAPI DismountStore(
HANDLE hStore
);
参数hStore必须为之前通过OpenStore()函数打开的设备句柄。
7. 格式化分区及卷
函数FormatPartitionEx将指定分区格式化为指定的类型:
BOOL WINAPI FormatPartitionEx(
HANDLE hPartition,
BYTE bPartType,
BOOL bAuto
);
l 参数hPartition指定将被格式化的分区句柄。
l 参数bPartType指定将被格式化成的分区类型。
l 参数bAuto用于确定是否使用参数bPartType指定的分区类型进行格式化。如果这个参数为FALSE,那么使用参数bPartType指定的类型。如果这个参数被设置为TRUE,将忽略参数bPartType,分区驱动将为系统默认使用的文件系统类型自动选择合适的分区类型。
注意这个函数只是将分区的第一个扇区清除,并没有将文件系统写入分区,为了将文件系统格式写入到分区,需要调用FormatVolume()函数直接对分区挂载的卷进行格式化:
DWORD FormatVolume(
HANDLE hVolume,
PDISK_INFO pdi,
PFORMAT_OPTIONS pfo,
PFN_PROGRESS pfnProgress,
PFN_PROGRESS pfnMessage
);
l 参数hVolume指定将被格式化的分区句柄。
l 参数pdi没有被用到。
l 参数pfo为一个指向结构体FORMAT_OPTIONS的指针,指定格式化选项。如果这个参数被设置为NULL,将使用默认选项进行格式化。
l 参数pfnProgress指向一个用于显示格式化进度的回调函数。
l 参数pfnMessage也是一个回调函数,当格式化出错时,将被用于显示错误信息。
如果格式化分区成功,返回ERROR_SUCCESS;失败将返回ERROR_GEN_FAILURE。
结构体FORMAT_OPTIONS主要是针对FAT文件系统进行格式化设置,因为在Windows CE所带的文件系统中,只有FAT是可以格式化卷的,它的定义如下:
typedef struct _FORMAT_OPTIONS {
DWORD dwClusSize;
DWORD dwRootEntries;
DWORD dwFatVersion;
DWORD dwNumFats;
DWORD dwFlags;
} FORMAT_OPTIONS;
l 成员dwClusSize指定分区将使用的簇大小,单位为字节,必须指定为2的幂次方。簇是FAT中空间分配的最小单位。
l 成员dwRootEntries指定根目录入口的个数。
l 成员dwFatVersion指定将使用的FAT版本,可取值有12,16或32。
l 成员dwNumFats指定将被创建的FAT个数。
l 成员dwFlags指定格式化分区的方式,可以取表7-10中的一个值或多个值的组合。
表7-10 成员dwFlags的可取值
值 |
说明 |
FATUTIL_DISABLE_MOUNT_CHK |
关闭检查分区是否被挂载 |
FATUTIL_FULL_FORMAT |
对卷进行完全格式化 |
FATUTIL_SECURE_WIPE |
对卷进行安全的檫除和格式化 |
FATUTIL_FORMAT_EXFAT |
将卷格式化为扩展FAT |
FATUTIL_FORMAT_TFAT |
将卷格式化为支持事务安全的扩展FAT |
另外,函数FormatStore()将删除设备上的所有分区信息:
BOOL WINAPI FormatStore(
HANDLE hStore
);
7.1 注册表
7.1.1 概述
与Windows桌面系统一样,Windows CE系统也使用注册表来存储系统、应用程序、驱动、用户等配置信息,注册表是存储配置信息的核心数据库,目前绝大多数的软件都会使用注册表。注册表被组织成层次化的树形结构,树中每个节点包含主键和值,主键类似于文件系统中的目录,可以包含多个值及子主键,其中最高层的主键称为根主键。Windows CE支持4个根主键,表7-12列出了Windows CE支持的4个根主键以及每个主键下存储的数据类型。
根主键名 |
包含的数据 |
HKEY_LOCAL_MACHINE |
硬件以及驱动的配置数据 |
HKEY_CURRENT_USER |
当前用户独有的配置数据 |
HKEY_CLASSES_ROOT |
OLE和文件类型匹配配置数据 |
HKEY_USERS |
对所有用户都可见的配置数据 |
如果Win CE被配置成支持多用户,HKEY_CURRENT_USER主键下的数据为当前登录用户独有的配置,不同用户将登录时,系统将加载相应的数据。如果希望配置数据对所有的用户可见,就应该放到HKEY_USERS根主键下。
值是注册表中存储数据的基本单元,可以是多种不同的类型,包括字符串或二进制值。这里的值并不是我们通常所理解的数值,比如8是一个整数值。在注册表中,每个值都包含一个名字以及相关的数据,为一个<name, value>对。
Windows CE支持两种不同类型的注册表:基于RAM (RAM-based) 的注册表和基于hive (hive-based) 的注册表。其中基于RAM的注册表将数据以一个对象存储堆放在RAM上,一旦系统掉电,这些数据将丢失,但是由于RAM读写速度远远高于外部设备,使用RAM注册表能够大大提高对注册表的读写访问效率。而基于hive的注册表将数据以文件的形式放到文件系统上,这种特殊的文件被称为hive(蜂箱),hive将数据保存在外部存储器上,所以是非易失性。
对注册表的操作主要包括:打开创建主键,关闭主键,读取设置注册表值,删除主键,及注册表值。在对主键或主键下的值进行操作之前,必须首先打开主键,获取主键句柄,然后才能对其进行其它操作,使用完主键之后,要将主键句柄关闭。
7.2.2 相关API函数
1. 打开和关闭主键
在读取或写入注册表中的一个值之前,需要先打开包含该值的主键。在Windows CE中,打开主键使用Win API函数RegOpenKeyEx():
LONG RegOpenKeyEx(
HKEY hKey,
LPCWSTR lpSubKey,
DWORD ulOptions,
REGSAM samDesired,
PHKEY phkResult
);
l 参数hKey指定将被打开的键的父键句柄,必须指定为之前打开过的主键或者系统保留的根主键常数,可取值在表7-12中所列。
l 参数lpSubKey指定将被打开的子健的名字。如果这个参数被指定为NULL或者为指向空字符串的指针,那么这个函数将再次打开参数hKey指向的主键句柄,注意需要将句柄关闭。子健可以包含多级子健,每个子健之间使用反斜杠隔开。如果指定名字的主键不存在,将导致函数调用失败。
l 参数ulOptions在Win CE中被保留,总是设置为0。
l 参数samDesired在Win CE中不支持,设置为0。
l 参数phkResult为输出参数,返回被打开的主键的句柄。使用完主键句柄之后,需要将其关闭,调用RegCloseKey()函数关闭主键句柄,注意不要使用CloseHandle()。
如果打开指定名字的主键成功,函数将返回ERROR_SUCCESS;打开失败将返回一个非0的错误码。
被打开的主键句柄使用完之后一定要及时关闭,这样系统才能回收资源,关闭主键句柄通过函数RegCloseKey()实现:
LONG RegCloseKey(
HKEY hKey
);
l 参数hKey指定将被关闭的主键句柄。
如果关闭主键句柄成功,函数返回ERROR_SUCCESS;如果失败,将返回非0的错误码。如果对打开的主键进行过修改,关闭主键将自动对其进行保存。
下面的演示如何打开一个指定名字的主键:
HKEY hkResult;
LONG lRes;
lRes = RegOpenKey( HKEY_LOCAL_MACHINE, TEXT("Drivers\\BuiltIn\\Serial"), 0, 0, & hkResult);
if(lRes == ERROR_SUCCESS)
{
//打开主键成功,进行处理
…
//最后需要将主键关闭
RegCloseKey(hkResult);
}
else
{
//打开主键失败,进行出错处理
…
}
8. 创建主键
除了打开指定主键外,还可以向注册表内新建一个主键,这通过函数RegCreateKeyEx()实现:
LONG RegCreateKeyEx(
HKEY hKey,
LPCWSTR lpSubKey,
DWORD Reserved,
LPWSTR lpClass,
DWORD dwOptions,
REGSAM samDesired,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
PHKEY phkResult,
LPDWORD lpdwDisposition
);
与RegOpenKeyEx()函数类似,参数hKey指定父键句柄,参数lpSubKey指定新创建的子主键的名字。
l 参数Reserved被保留,总是设置为0。
l 参数lpClass指定新创建主键的类名称,在Windows CE中,主键类名称的最大字符长度为255。如果不需要指定主键类名称,将这个参数设置为NULL。
l 参数dwOptions指示新创建的主键是易失性的还是非易失性的,可取的值如表7-13所示。
值 |
说明 |
REG_OPTION_NON_VOLATILE |
默认设置,非易失性主键,新创建的主键以文件的形式驻在外部存储器设备上,当系统重启的时候,主键值将保留 |
REG_OPTION_VOLATILE |
易失性主键,数据都放在RAM上,一旦系统掉电,这些数据将丢失。易失性主键的访问速度比较快,因为都在RAM中 |
l 参数samDesired被忽略,设置为0。
l 参数lpSecurityAttributes用于设置主键的安全属性,在Windows CE中不支持,总是设置为NULL。Windows CE将自动为主键分配一个默认的安全描述子。
l 参数phkResult为输出参数,返回新创建或打开的主键句柄。
l 参数lpdwDisposition为输出参数,可能值为REG_CREATED_NEW_KEY和REG_OPENED_EXISTING_KEY,分别表示主键为新创建的还是打开已经存在的。
如果指定名字的主键已经存在,那么这时只是打开主键句柄,并不会再新建;如果为新的主键名,将在注册表的指定位置新创建一个空的不包含任何值的主键,然后打开主键。
函数调用成功,将返回ERROR_SUCCESS;否则返回非0的错误码。通过查询输出参数lpdwDisposition来判断是否向注册表内新创建主键。
9. 读取和设置注册表值
打开主键后,就可以对该主键下的值进行读写访问。函数RegQueryValueEx()读取指定值的类型及数据信息:
LONG RegQueryValueEx(
HKEY hKey,
LPCWSTR lpValueName,
LPDWORD lpReserved,
LPDWORD lpType,
LPBYTE lpData,
LPDWORD lpcbData
);
l 参数hKey为包含指定值(Value)的主键句柄,可以为前面已经打开的主键句柄或为代表系统根主键的保留句柄值。
l 参数lpValueName指定将读取的值的名称。这个参数可以设置为NULL或空字符串,此时将读取hKey主键下名称为”Default”的值信息。
l 参数lpReserved被保留,设置为NULL。
l 参数lpType为输出参数,返回指定值的类型信息,表7-13列出了可能的取值。
类型值 |
说明 |
REG_BINARY |
为任意格式的二进制数据 |
REG_DWORD |
为32位整数 |
REG_DWORD_BIG_ENDIAN |
为大端字节序格式的32位整数。在大端字节序格式中,高字节放在内存中的低地址,低字节放在内存中的高地址。如值0x12345678以大端序在内存中将存储为(0x12 0x34 0x56 0x78) |
REG_DWORD_LITTLE_ENDIAN |
为小端字节序格式的32位整数,与大端序相对,高字节存在高地址,低字节存在低地址。 |
REG_EXPAND_SZ |
为嵌入式环境变量的字符串,比如%PATH% |
REG_LINK |
为Unicode编码的符号连接,为Windows CE内部保留使用,应用程序不能使用该类型 |
REG_MULTI_SZ |
为一个字符串数组,每个字符串以null结束,字符串数组需要以2个null结束。 |
REG_NONE |
为未定义的值类型 |
REG_RESOURCE_LIST |
为一个设备驱动的资源列表 |
REG_SZ |
为一个以null结束的Unicode字符串。 |
如果不需要获取值的类型信息,可以将这个参数设置为NULL。
l 参数lpData为一个用于接收值数据的缓冲区指针,为输出参数。如果不需要读取值数据,将这个参数设置为NULL。
l 参数lpcbData指定值数据缓冲区的大小,单位为字节。为输入输出参数,函数执行完后,这个参数将返回实际读取的值数据的字节数。如果参数lpData指向的缓冲区太小,不足以接收全部的值数据,函数的执行将失败,并返回ERROE_MORE_DATA,参数lpcbData将返回需要的缓冲区字节数。只有当参数lpData被设置为NULL时,这个参数才能设置为NULL,当然也可以不设置为NULL,这时函数如果调用成功,会将值数据的大小返回到这个参数,这样提供了一种在分配缓冲区之前查询值数据大小的方式,保证分配适当大小的缓冲区。
如果读取值信息成功,函数将返回ERROR_SUCCESS;失败将返回非0的错误码。
应用程序还可以设置注册表值,与读取注册表值方法相似,通过指定值名称来设置,函数RegSetValueEx()实现这个功能:
LONG RegSetValueEx(
HKEY hKey,
LPCWSTR lpValueName,
DWORD Reserved,
DWORD dwType,
const BYTE* lpData,
DWORD cbData
);
这些参数的涵义与上面介绍的读取注册表值函数RegQueryValueEx()中的一样。如果参数lpValueName指定的值名称在指定主键下不存在,将新建一个值。还可以将参数lpValueName设置为NULL或指向空字符串,这时将对hKey指向的主键下的”Default”值进行设置。
在Windows CE中,应用程序还可以通过函数RegQueryInfoKey()读取整个主键的信息:
LONG RegQueryInfoKey(
HKEY hKey,
LPWSTR lpClass,
LPDWORD lpcbClass,
LPDWORD lpReserved,
LPDWORD lpcSubKeys,
LPDWORD lpcbMaxSubKeyLen,
LPDWORD lpcbMaxClassLen,
LPDWORD lpcValues,
LPDWORD lpcbMaxValueNameLen,
LPDWORD lpcbMaxValueLen,
LPDWORD lpcbSecurityDescriptor,
PFILETIME lpftLastWriteTime
);
l 参数hKey指定已经被打开的主键句柄或为系统保留的指向根主键的句柄。
l 参数lpClass为输出参数,指向一个用于接收主键的类名称的缓冲区。如果不需要读取类名称,可以将这个参数设置为NULL。
l 参数lpcbClass指定用于接收类名称的缓冲区的字节数。函数调用完成,这个参数将返回实际存入缓冲区的字节数。
l 参数lpReserved为保留,设置为NULL。
l 参数lpcSubKeys为输出参数,用于返回该主键所包含的子主键的个数,这个参数可以设置为NULL。
l 参数lpcbMaxSubKeyLen为输出参数,用于返回该主键的子主键中最长名称的长度,可以设置为NULL。
l 参数lpcbMaxClassLen为输出参数,返回子主键中类名称最长的字符串长度,可以设置为NULL。
l 参数lpcValues为输出参数,返回该主键所包含的值得个数,可以设置为NULL。
l 参数lpcbMaxValueNameLen和lpcbMaxValueLen分别返回该主键包含的值中最长名称的长度和最大值数据部分的长度。
l 参数lpcbSecurityDescriptor和lpftLastWriteTime在Windows CE中都不支持,设置为NULL。
如果函数执行成功,返回ERROR_SUCCESS;否则返回非0的错误码。
10. 删除注册表主键和值
删除注册表主键使用函数RegDeleteKey()实现:
LONG RegDeleteKey(
HKEY hKey,
LPCWSTR lpSubKey
);
l 参数hKey为将被删除的主键的父主键句柄,父主键必须先打开。
l 参数lpSubKey指定将被删除的主键的名称。这个主键在删除时不能处于打开状态。如果指定的主键名称在父主键下并不存在,函数调用将失败。这个参数不能设置为NULL。
如果删除主键成功,返回ERROR_SUCCESS,否则返回非0的错误码。注意不要去删除系统保留的那4个根主键,这是不允许的,也是不可能实现的。
删除注册表值使用函数RegDeleteValue():
LONG RegDeleteValue(
HKEY hKey,
LPCWSTR lpValueName
);
l 参数hKey指定包含将被删除值的主键句柄,这个主键必须先被打开。
l 参数lpValueName指定将被删除的值名称。这个参数可以设置为NULL,此时将删除hKey主键下名称为”Default”的值。
如果删除主键成功,返回ERROR_SUCCESS,否则返回非0的错误码。
11. 枚举注册表主键和值
前面介绍的RegQueryInfoKey()函数只是返回一个主键的整体统计信息(比如包含的子主键个数,最大子主键名称长度等),没有关于子主键和值的具体信息。如果希望进一步获取每个子主键或值的信息,可以通过Windows CE提供的枚举主键或值的功能来实现。
枚举一个父主键下包含的所有子主键信息使用函数RegEnumKeyEx()来实现,这个函数的执行比较特殊,它并不像函数名所标识的那样一次性返回所有的子主健信息,而是需要通过指定下标来获取特定的子健信息。所以如果用户希望遍历所有的子主键,需要重复调用这个函数,并不断加大下标,直到函数返回ERROR_NO_MORE_ITEMS为止。这个函数的原型如下:
LONG RegEnumKeyEx(
HKEY hKey,
DWORD dwIndex,
LPWSTR lpName,
LPDWORD lpcName,
LPDWORD lpReserved,
LPWSTR lpClass,
LPDWORD lpcbClass,
PFILETIME lpftLastWriteTime
);
l 参数hKey指定父主键句柄,父主键必须已经被打开(系统保留的根主键除外)。函数将枚举这个父主键下的子主键。
l 参数dwIndex指定将要读取的子主键的下标,从0开始。注意子主键没有顺序的概念,新创建的子健可能被分配一个任意的下标。所以不要指望通过下标来定位子主键,而应该通过子主键的名称来定位。
l 参数lpName为输出参数,返回指定子主键的名称,而不是子主键所在的完全层次结构路径
l 参数lpcName指定用于接收子主键名称的缓冲区的长度,函数执行完后,这个参数还将返回实际存储到缓冲区中的子主键名称的长度。
l 参数lpReserved为保留,设置为NULL。
l 参数lpClass为输出参数,返回对应子主键的类名称。这个参数可以设置为NULL,如果并不需要类名称的话。
l 参数lpcbClass指定用于接收类名称的缓冲区的长度,函数执行完,将放回实际存入缓冲区的长度。
l 参数lpftLastWriteTime在Windows CE中被忽略,总是设置为NULL。
与枚举子主键的方式类似,函数RegEnumValue()用于枚举指定主键下的值信息:
LONG RegEnumValue(
HKEY hKey,
DWORD dwIndex,
LPWSTR lpValueName,
LPDWORD lpcchValueName,
LPDWORD lpReserved,
LPDWORD lpType,
LPBYTE lpData,
LPDWORD lpcbData
);
l 参数hKey指定主键句柄,必须为被打开过的主键(系统保留的根主键除外)。
l 参数dwIndex指定将被访问的值在主键中的下标,从0开始。同样值在主键中没有顺序可言,新创建的值可能被分配一个任意下标。
l 参数lpValueName和lpcchValueName分别用于返回指定的值名称和值名称长度。
l 参数lpReserved为保留,设置为NULL。
l 参数lpType返回值的类型信息,可能值如表7-xx所列。
l 参数lpData和lpcbData分别用于返回值的数据和数据的长度。
这两个函数都在调用成功时,返回ERROR_SUCCESS,否则返回非0的错误码。
12. 将主键信息写回注册表
前面介绍过,关闭主键句柄会自动将对主键的所有改动写回到注册表中,但是有些时候需要立即将一些改动写回,又希望保持主键为打开状态,Windows CE提供了RegFlushKey()函数实现提前写回的功能:
LONG RegFlushKey(
HKEY hKey
);
l 参数hKey为将被写回的主键句柄。
如果函数调用成功,对主键的所有修改都将写回到持久性存储器中,并返回ERROR_SUCCESS,否则返回非0的错误码。
7.2 小结
本章介绍了Windows CE 7中的文件系统和注册表的基本概念和操作。首先介绍三种类型的文件系统,然后介绍文件操作的API,包括创建或打开,读写文件数据,设置文件读写指针位置,读取设置文件属性信息,复制移动文件,搜索目录下所有文件信息等。最后介绍Windows CE 7中的注册表结构及注册表的操作API,包括打开、关闭、创建主键,读取和设置注册表值,删除注册表主键和值,以及枚举注册表主键和值。