找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 127|回复: 6

【Win32API】资源加载Ex版(国际化支持的扩展、资源API的内部实现原理研究)

[复制链接]
发表于 2024-12-11 15:31:50 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×
本帖最后由 YY菌 于 2024-12-11 15:26 编辑

原帖来源:【Win32API】资源加载Ex版(国际化支持的扩展、资源API的内部实现原理研究)https://www.0xaa55.com/thread-26602-1-1.html(出处: 技术宅的结界)

  众所周知,Win32的PE文件内嵌资源有标准资源类型和自定义资源类型两大类,标准资源类型又分为 光标、位图、图标、菜单、对话框、字符串、字体目录、字体、加速键等,根据教程或官方文档等常见的资料,要加载它们分别需要使用以下API:

资源API
光标LoadCursor
位图LoadBitmap
图标LoadIcon
菜单LoadMenu
对话框CreateDialog | DialogBox
字符串LoadString
字体目录
字体
加速键LoadAccelerators
自定义资源FindResource + LoadResource


  最开始我们根据教程或官方文档来使用,认为确实应该如此,并没有想过其中的原理,更没有想过标准资源和自定义资源只是在应用上差别,还是有着本质上区别?随着后来的深入,开始有了国际化方向的编程需求,这时候一般大家都会发现在VS的资源编辑器中可以设置资源的语言,相同的类型和ID可以存放多个不同语言的资源,于是便会想到在Win32编程中设计国际化UI最简单方便的方案便是直接利用Win32资源的语言功能。这个时候又会考虑的新的问题,如何加载多语言的资源?平常我们加载资源都是直接使用API按类型和ID来加载没有考虑过语言相关的问题,经过测试后可以发现如果存在多个语言的资源,系统会根据用户安装的操作系统语言来加载对应的资源(英文系统上加载英文资源,中文简体系统加载中文简体资源,中文繁体系统加载中文繁体资源)符合大部分情况下的需要,但要是想专门加载特定语言资源怎么办呢?查一下相关的API就会发现自定义资源有个叫 FindResourceEx 的扩展API,但是加载标准资源的API居然没有Ex版(是不是非常想打比尔盖茨等人打一顿)。
  后来随着技术的更进一步深入,又发现了 RT_ 开头的常量宏,一看名称这不就是哪些所谓的标准资源类型吗?这也就意味着标准资源类型和自定义资源类型根本就没有任何本质区别,唯一的区别就只是标准资源类型都使用的整数来作为类型编号,而自定义资源类型则是用字符串来作为类型名称的。这个时候终于恍然大悟了,资源ID都有整数编号和字符串名称两种方式,裆燃资源的类型也有啊。但是新的问题又出现了,以前用的哪些标准资源类型加载的API都是返回已经创建好的对象了,自己用 FindResourceEx + LoadResource 加载出现的只是原始数据,并不能直接拿来就用。于是继续深入研究,搞懂了大部分标准资源加载API的实现原理,于是遍封装了一套Ex版的标准资源加载函数:

资源API
菜单LoadMenuEx
对话框CreateDialogEx | DialogBoxEx
字符串LoadStringEx
加速键LoadAcceleratorsEx


  实现原理:
API实现ID备注
CreateDialogExFindResourceEx + LoadResource + CreateDialogIndirectParam加载资源为非模态对话框
DialogBoxExFindResourceEx + LoadResource + DialogBoxIndirectParam加载资源为模态对话框
LoadMenuExFindResourceEx + LoadResource + LoadMenuIndirect加载菜单资源
LoadAcceleratorsExFindResourceEx + LoadResource + CreateAcceleratorTableW加载加速键资源(提示PE资源中保存的加速键始终是Unicode数据,所以不管是LoadAcceleratorsExA还是LoadAcceleratorsExW加速键句柄都必须用CreateAcceleratorTableW创建,唯一区别只是查找资源时用FindResourceExA还是FindResourceExW)
LoadStringExAFindResourceEx + LoadResource + WideCharToMultiByte加载字符串资源(由于资源中始终是Unicode文本,因此需要用WideCharToMultiByte转换字符串编码)
LoadStringExWFindResourceEx + LoadResource + CopyMemory加载字符串资源(由于资源中始终是Unicode文本,因此直接拷贝内存到缓冲区并添加\0结束标记即可)


  菜单、对话框、加速键都是在原来的基础上增加一个语言参数(就和 FindResource  跟 FindResourceEx 一样),加载字符串资源的 LoadStringEx 函数则是稍微有一些调整,增加的是更高级一些 LCID 参数,这个操作主要是针对 LoadStringExA 设计考虑的,可以想想一下只是简简单单的切换一下资源语言,而不是切换 ANSI 对应的字符集会大概率产生乱码?LCID 的低16位就是LANGID,而 GetLocaleInfo 可以获取 LCID 对应的字符集代码页,因此使用 LCID 更好的解决 LoadStringExA 的问题。至于 LoadStringExW 嘛为了保证参数的一致性以及对通用字符集(TCHAR系列)的兼容,那自然也使用 LCID 来作为扩展参数,只是W版就只使用低16位的语言ID,高位全部忽略。
  标准的 LCID 只使用了20bit,算上 VERSION 字段总共最多也就24bit,还有8bit空闲区域,于是我就把这块区域用来做一些扩展标记。用法如下:

  1.         const LCID lcid = MAKESORTLCID(MAKELANGID(LANG_主语言, SUBLANG_主语言_子语言), 分类ID, 版本);        // 将要加载的字符串语言和区域信息
  2.         const int 缓冲区长度 = …;    // 注意:需包括\0结束标记
  3.         int 字符串长度;               // 提示:不包括\0结束标记
  4.         CHAR ANSI字符串缓冲区[缓冲区长度];
  5.         WCHAR WIDE字符串缓冲区[缓冲区长度];
  6.         TCHAR AUTO字符串缓冲区[缓冲区长度];
  7.         LPCWSTR 原始字符串地址;

  8.         // 加载不同语言和不同字符集的处理(此方案仅适用于 LoadStringExA)
  9.         字符串长度 = LoadStringExA(hInstance, IDS_字符串ID, lcid, ANSI字符串缓冲区, 缓冲区长度);                 // 加载指定语言的字符串资(使用当前线程的ACP)
  10.         字符串长度 = LoadStringExA(hInstance, IDS_字符串ID, LOCALE_USE_CP_LCID | lcid, ANSI字符串缓冲区, 缓冲区长度);        // 加载指定语言的字符串资(使用LCID对应的CP,简体中文是GB2312,繁体中文是BIG-5)
  11.         字符串长度 = LoadStringExA(hInstance, IDS_字符串ID, LOCALE_USE_CP_UTF8 | lcid, ANSI字符串缓冲区, 缓冲区长度);        // 加载指定语言的字符串资(使用Unicode的CP UTF-8)

  12.         // 加载Unicode文本(此方案仅适用于 LoadStringExW,dwLocaleCP 只使用LANGID部分)
  13.         字符串长度 = LoadStringExW(hInstance, IDS_字符串ID, lcid, WIDE字符串缓冲区, 缓冲区长度);           // 加载指定语言的字符串资源到WIDE缓冲区(加载完成会自动补充\0结束标记)
  14.         字符串长度 = LoadStringExW(hInstance, IDS_字符串ID, lcid, LPWSTR(原始字符串地址), 0);             // 获取指定语言的字符串资源的原始数据(不包括\0结束标记,需自己手动根据返回的字符串长度来处理)

  15.         // 新增功能(LoadString不支持)A和W均适用(ANSI返回字节长度,WIDE返回字符个数)
  16.         字符串长度 = LoadStringEx(hInstance, IDS_字符串ID, lcid, nullptr, 0);        // 仅获取指定语言的文本长度,不加载文本内容(可用于计算所需缓冲区大小,需注意缓存区分配时的长度要在此基础上 + 1)
  17.         字符串长度 = LoadStringEx(nullptr, 对话框按钮ID, lcid, AUTO字符串缓冲区, 缓冲区长度);                // 获取指定语言的对话框按钮文本(IDOK为"确定"、IDCANCEL为"取消"、…),模仿 LoadImage 在使用空 hInstance 时的加载系统预设图像的功能,LoadStringEx 对应的也就是加载系统预设文本的功能。
复制代码

语言名称语言ID代码页备注
非特定语言(系统用户语言)MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)0加载无语言类型的资源(如果不存在则会依次查找线程、用户、系统、英语(美国)的资源)
英语(美国)MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US)1252
中文简体(中国大陆)MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED)936
中文繁体(台湾)MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL)950
俄语(俄罗斯)MAKELANGID(LANG_RUSSIAN, SUBLANG_RUSSIAN_RUSSIA)1251
英语(英国)MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_UK)1252
法语(法国)MAKELANGID(LANG_FRENCH, SUBLANG_FRENCH)1252
中文简体(新加坡)MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SINGAPORE)936
中文繁体(香港)MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_HONGKONG)950
中文繁体(澳门)MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_MACAU)950
日语(日本)MAKELANGID(LANG_JAPANESE, SUBLANG_JAPANESE_JAPAN)932

LCID组合参数备注
LOCALE_USE_CP_THREAD使用当前线程ANSI代码页(默认)
LOCALE_USE_CP_ACP使用系统用户ANSI代码页
LOCALE_USE_CP_OEMCP使用 OEM 代码页
LOCALE_USE_CP_MACCP使用 Macintosh 代码页
LOCALE_USE_CP_UTF7使用 UTF-7 代码页
LOCALE_USE_CP_UTF8使用 UTF-8 代码页
LOCALE_USE_CP_LCID使用 LCID 语言对应的代码页

对话框按钮ID英文中文简体中文繁体日语俄语备注
0Error错误錯誤エラーОшибка消息框默认标题
IDOK | 1OK确定確定OKОК确定按钮文本
IDCANCEL | 2Cancel取消取消キャンセルОтмена取消按钮文本
IDABORT | 3&Abort中止(&A)中止(&A)中止(&A)Пр&ервать中止按钮文本
IDRETRY | 4&Retry重试(&R)重試(&R)再試行(&R)По&втор重试按钮文本
IDIGNORE | 5&Ignore忽略(&I)略過(&I)無視(&I)&Пропустить忽略按钮文本
IDYES | 6&Yes是(&Y)是(&Y)はい(&Y)&Да是按钮文本
IDNO | 7&No否(&N)否(&N)いいえ(&N)&Нет否按钮文本
IDCLOSE | 8&Close关闭(&C)關閉(&C)閉じる(&C)&Закрыть关闭按钮文本
IDHELP | 9Help帮助說明ヘルプСправка帮助按钮文本
IDTRYAGAIN | 10&Try Again重试(&T)重試(&T)再実行(&T)&Повторить另一种重试按钮文本
IDCONTINUE | 11&Continue继续(&C)繼續(&C)続行(&C)&Продолжить继续按钮文本
ExLoadRes.7z (19.08 KB, 下载次数: 4) 压缩包密码在隐藏内容中(回帖可见或者没有密码
游客,如果您要查看本帖隐藏内容请回复

测试

测试
回复

使用道具 举报

发表于 2024-12-14 13:26:41 | 显示全部楼层
高明! 怎么自己创建资源 也给整一套方案 可好?
回复 赞! 靠!

使用道具 举报

发表于 2024-12-16 09:09:45 | 显示全部楼层
啥也不说了,帖子就是带劲!
回复 赞! 靠!

使用道具 举报

发表于 2024-12-16 09:36:26 | 显示全部楼层
tlwh163 发表于 2024-12-14 13:26
高明! 怎么自己创建资源 也给整一套方案 可好?

自己创建资源是指啥?要导入自定义资源类型的数据直接用字符串的类型名不就好了?代码就直接用 FindResourceEx + LoadResources 就好了。
回复 赞! 靠!

使用道具 举报

发表于 2024-12-16 11:12:22 | 显示全部楼层
YY菌 发表于 2024-12-16 09:36
自己创建资源是指啥?要导入自定义资源类型的数据直接用字符串的类型名不就好了?代码就直接用 FindResou ...

对话框模版啥的 。。。
回复 赞! 靠!

使用道具 举报

发表于 2024-12-16 11:30:37 | 显示全部楼层
tlwh163 发表于 2024-12-16 11:12
对话框模版啥的 。。。

对话框模板用资源编辑器添加一个对话框资源,然后在里面拖控件。最后代码用 DialogBox 或者劳资的 DialogBoxEx 传入指定的hInstance、资源编号、父窗口、对话框消息函数(Ex版再多传一个资源的语言ID)就可以创建了。
回复 赞! 靠!

使用道具 举报

发表于 2024-12-20 15:11:52 | 显示全部楼层
好东西,先收藏
回复 赞! 靠!

使用道具 举报

本版积分规则

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-12-31 01:08 , Processed in 0.048023 second(s), 27 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表