2010年快要过完了,这最后一个月实在不打算搞啥研究了,新换的工作又忙又累,我既不擅长熬夜,也不擅长通宵,周末之余还是搞搞外包,睡睡大觉比较好。。。抱怨完了,进入正题。
PE文件的资源访问通过一组API进行,主要有LoadLibrary, FreeLibrary, FindResource, LoadResource, LockResource, FreeResource, BeginUpdateResource, UpdateResource, EndUpdateResource,这些应该差不多了。其中LoadLibrary和FreeLibrary是加载程序模块的函数,这里主要用来访问程序自身之外的模块文件,exe或dll。
资源名称貌似有很多限制,参数类型是LPCWSTR,但文档说了,如果地址的数值小于64k,就被当做一个16位ID,作为资源的标识名称。我当然倾向于使用实际意义的字符串当资源名称,但是总是有莫名其妙的错误,大概有以下几种情况:含有文件路径非法字符(/,/,*,?之类);含有"."和"_"(这个有点严重了);字符太长的。含有文件路径非法字符时直接保存失败,包含"."和"_"或名称太长时,保存资源返回成功了,用EnumResourceNames遍历也可以查到名称,但是就是取不到数据,SizeofResource()检查资源大小总是0,真是被整的很郁闷。因此打算用ID来保存所有资源,自己来管理资源名称到ID的映射。
设计思路如下:
选一个ID保存一个固定资源,我选的是0xfc00(63k),这个资源可以称作“资源名称映射表”,里面保存其它资源的ID和字符串名称,对每个资源保存一个记录,以字符串名称作为Key,资源ID作为Value,运行时就可以读取出来放在哈希表里供查询使用。
自定一个资源类别,尽量避免和其它类别冲突。资源一律用ID来标识,并保存到这个类别里,而ID通过程序来自动分配和回收。资源所用ID都要大于0xfc00,尽量不和MFC窗口的各种资源ID冲突。
保存资源时,以一个任意字符串做名称,只要不和现有资源的名称重复,就自动分配一个未使用的ID,并记录到映射表。访问资源时,根据字符串名称查找ID,再通过ID获取资源数据。删除资源时,把资源数据和资源记录一并删除。由于资源ID是大于0xfc00,又有0xffff的上限,用户可保存的资源数量不超过1023个,我觉得差不多够用了吧。
实现过程没什么难度我就忽略不提了。
测试后发现有两个问题:
1.读写效率下降。由于名称保存在一个映射表里,任何添加和删除资源都要修改这个表,而表本身也是一个资源,必须整个读取,整个写入,因此进行频繁添加或删除资源时效率低下。由于MSDN说明了资源写入PE文件的实际操作是在调用EndUpdateResource时进行,因此在需要添加或删除多个资源时先连续调用UpdateResource,包括更新映射表本身调用的UpdateResource,最后用一句EndUpdateResource一起写入。于是资源的添加和删除函数都做了两个版本,一个是每次添加一个资源,一个是每次添加多个资源。
2.资源名称浪费存储空间。对每个资源ID和名称字符串的成对保存,程序内部使用了一个数据结构:
struct _RsInternalInfo
{
WORD Id;
wchar_t Name[128];
}
限制了每个资源的名称不超过127字符,保存也是定长记录,这样做方便了程序实现,但是那127字符会浪费大半。为了节约空间,可以使用不定长记录,每个名称字符串前先保存一个WORD,记录名称字符串的长度,然后按这个长度读取后面的字符。试验了一下,这样做又导致解析速度变慢。这个问题最后不打算解决了,名称映射信息直接用RsInternalInfo保存了。
最后做了一个静态库,提供了以下接口:
这部分用来给其它程序模块添加或删除资源
struct RsCreateInfo
{
wchar_t name[128];
size_t data_size;
void* data_bits;
}
bool ModuleResourceList(wchar_t* bin_file_name, vector<RsCreateInfo>* result);
bool CreateModuleResource(wchar_t* bin_file_name, RsCreateInfo& create_info);
bool CreateModuleResources(wchar_t* bin_file_name, vector<RsCreateInfo>* create_list);
bool DeleteModuleResource(wchar_t* bin_file_name, wchar_t* name);
bool DeleteModuleResources(wchar_t* bin_file_name, vector<wstring>* name_list);
这部分用来获得当前运行的程序自身的资源
WORD MyResourceNumber();
wstring MyResourceName(WORD id);
bool GetMyResourceSizeByName(wchar_t* name, size_t* result);
bool GetMyResourceDataByName(wchar_t* name, void** result);
这些函数只能访问用CreateModuleResource和CreateModuleResources添加的资源,但是资源名称可以包含任意Unicode字符,不再受系统限制。尤其可以直接保存文件路径,把资源释放到文件时就方便多了。