Duilib 源码分析之 xml 加载篇
2017-04-27 11:24
357 查看
大家都知道, Duilib 的界面内容除了在代码中动态加入外,最常用的就是通过写好 xml 来加载了。今天就介绍一下 Duilib 是如何读取到 xml 并将 xml 内容加载到内存中的。
我们来按加载的流程来分析一下,流程如下:
Created with Raphaël 2.1.0开始创建 WindowsOnCreate加载 xml结束
其实这个流程图并没有太大意义,在这里只不过是想说明一下,xml 加载的时机是在 WM_CREATE 的消息相应函数中处理的。
首先贴出加载资源的代码如下:
以上代码共处理了资源加载的 4 种方式
UILIB_FILE (默认)
加载资源时使用绝对路径或者相对路径。使用这种方式要指定 xml 所在文件夹和 xml 的文件名,也就是要重载 GetSkinFolder() 和 GetSkinFile()。 实际读取 xml 资源时,xml 路径 = GetResourcePath+SkinFile,而 ResourcePath 在未主动设置的情况下 = 模块所在路径+ SkinFolder(上面代码前 5 行),所以我们只需指定目录和文件名即可,当然要将目录放在和模块同一级别的目录下。
UILIB_ZIP
这种方式和第一种差不多,只不过 xml 要从 zip 中读取了。使用这种方式要重载 GetSkinFolder() , GetZIPFileName() 和 GetSkinFile() ,查找 zip 文件的方式和第一种方式一样,在模块所在文件夹下的 SkinFolder + ZIPFileName。上述代码的第 12 行
UILIB_RESOURCE
这种方式是将 xml 文件作为 exe 的资源来进行加载。首先要将 xml 文件添加到 rc 中,资源类型为 “xml”,因为后续从资源中读取 xml 时是从 “xml” 类型中查找的,其次要重载 GetSkinFile(),但要注意,此时不能再返回文件名了,而是要返回 xml 对应的资源 ID,其实是一个通过
UILIB_ZIPRESOURCE
这种方式相当于 UILIB_RESOURCE + UILIB_ZIP。首先要将资源文件压缩到一个 zip 文件中,然后将此 zip 添加到 rc 中,资源类型为 “ZIPRES”,然后重载 GetResourceID(), 返回值为:(例)
到这里,如果读者只是想了解如何使用上述 4 中资源加载方式的话,下面的可以不必再读。如果你还想知道以上 4 中方式到底是如何将 xml 的内容加载到内存的话,下面会给予介绍:
这部分实现主要是依靠以下 3 个函数
下面分类型进行分析:
UILIB_FILE
调用
UILIB_ZIP
解压由类 TUZip 实现
先调用 OpenZip 打开 zip 文件,传入参数为 zip 路径和打开类型
然后调用 FindZipItem ——传入 zip 中所查找的文件名,查询 zip 中是否存在所查文件
存在所查找文件的情况下,调用 UnzipItem 将 xml 的内容读取到已申请的 Buffer 中——pByte,然后调用 LoadFromMem
UILIB_RESOURCE
调用 HRSRC FindResource(HMODULE hModule, LPCWSTR lpName, LPCWSTR lpType) 查找资源,参数分别为 exe 的实例句柄、资源的 ID 信息、资源类型(UILIB_RESOURCE 加载形式的情况下,这个参数为 “xml”,所以在添加资源的时候要指定类型为 “xml”),确定指定模块中指定类型和名称的资源所在位置
调用 HGLOBAL LoadResource(HMODULE hModule,HRSRC hResInfo), 装载指定资源到全局存储器
调用 LPVOID LockResource(HGLOBAL hResData) 获取到资源在内存中的第一个字节的指针,调用 DWORD SizeofResource(HMODULE hModule,HRSRC hResInfo) 获取资源的字节数
根据上面函数获取到的指针和字节大小,再调用 LoadFromMem
UILIB_ZIPRESOURCE
实现方式相当于 UILIB_RESOURCE + UILIB_ZIP,这里不再赘述了
以上就是 4 中方式 xml 内容加载到内存中的实现方法,首地址为 m_pstrXML,后续的解析就是针对 m_pstrXML 来进行,相关实现原理会在后续的帖子中进行介绍。
知识点小清单:
对于 UTF-8 编码,类似 WINDOWS 自带的记事本等软件保存文件时,会在文件开头加上 0xEF 0xBB 0xBF 三个字节,也就是所谓的 BOM(Byte Order Mark), 所以如果发现开头有这三个字节,则略过这三个字节处理剩下的部分。
我们来按加载的流程来分析一下,流程如下:
Created with Raphaël 2.1.0开始创建 WindowsOnCreate加载 xml结束
其实这个流程图并没有太大意义,在这里只不过是想说明一下,xml 加载的时机是在 WM_CREATE 的消息相应函数中处理的。
首先贴出加载资源的代码如下:
CDialogBuilder builder; CDuiString strResourcePath=m_PaintManager.GetResourcePath(); if (strResourcePath.IsEmpty()) { strResourcePath=m_PaintManager.GetInstancePath(); strResourcePath+=GetSkinFolder().GetData(); } m_PaintManager.SetResourcePath(strResourcePath.GetData()); switch(GetResourceType()) { case UILIB_ZIP: m_PaintManager.SetResourceZip(GetZIPFileName().GetData(), true); break; case UILIB_ZIPRESOURCE: { HRSRC hResource = ::FindResource(m_PaintManager.GetResourceDll(), GetResourceID(), _T("ZIPRES")); if( hResource == NULL ) return 0L; DWORD dwSize = 0; HGLOBAL hGlobal = ::LoadResource(m_PaintManager.GetResourceDll(), hResource); if( hGlobal == NULL ) { #if defined(WI 4000 N32) && !defined(UNDER_CE) ::FreeResource(hResource); #endif return 0L; } dwSize = ::SizeofResource(m_PaintManager.GetResourceDll(), hResource); if( dwSize == 0 ) return 0L; m_lpResourceZIPBuffer = new BYTE[ dwSize ]; if (m_lpResourceZIPBuffer != NULL) { ::CopyMemory(m_lpResourceZIPBuffer, (LPBYTE)::LockResource(hGlobal), dwSize); } #if defined(WIN32) && !defined(UNDER_CE) ::FreeResource(hResource); #endif m_PaintManager.SetResourceZip(m_lpResourceZIPBuffer, dwSize); } break; } CControlUI* pRoot=NULL; if (GetResourceType()==UILIB_RESOURCE) { STRINGorID xml(_ttoi(GetSkinFile().GetData())); pRoot = builder.Create(xml, _T("xml"), this, &m_PaintManager); } else pRoot = builder.Create(GetSkinFile().GetData(), (UINT)0, this, &m_PaintManager);
以上代码共处理了资源加载的 4 种方式
UILIB_FILE (默认)
加载资源时使用绝对路径或者相对路径。使用这种方式要指定 xml 所在文件夹和 xml 的文件名,也就是要重载 GetSkinFolder() 和 GetSkinFile()。 实际读取 xml 资源时,xml 路径 = GetResourcePath+SkinFile,而 ResourcePath 在未主动设置的情况下 = 模块所在路径+ SkinFolder(上面代码前 5 行),所以我们只需指定目录和文件名即可,当然要将目录放在和模块同一级别的目录下。
UILIB_ZIP
这种方式和第一种差不多,只不过 xml 要从 zip 中读取了。使用这种方式要重载 GetSkinFolder() , GetZIPFileName() 和 GetSkinFile() ,查找 zip 文件的方式和第一种方式一样,在模块所在文件夹下的 SkinFolder + ZIPFileName。上述代码的第 12 行
SetResourceZip就是预先设置了 zip 文件名,后续会通过
GetResourceZip得到 zip 名,这个函数和 UILIB_FILE 加载方式中的
GetSkinFile()作用是一样的,都是为了找到指定文件夹下的文件。
UILIB_RESOURCE
这种方式是将 xml 文件作为 exe 的资源来进行加载。首先要将 xml 文件添加到 rc 中,资源类型为 “xml”,因为后续从资源中读取 xml 时是从 “xml” 类型中查找的,其次要重载 GetSkinFile(),但要注意,此时不能再返回文件名了,而是要返回 xml 对应的资源 ID,其实是一个通过
MAKEINTRESOURCE得到的地址为 ID 的指针 。
UILIB_ZIPRESOURCE
这种方式相当于 UILIB_RESOURCE + UILIB_ZIP。首先要将资源文件压缩到一个 zip 文件中,然后将此 zip 添加到 rc 中,资源类型为 “ZIPRES”,然后重载 GetResourceID(), 返回值为:(例)
MAKEINTRESOURCE(IDR_ZIPRES1),其中 IDR_ZIPRES1 为 zip 文件的资源 ID。
到这里,如果读者只是想了解如何使用上述 4 中资源加载方式的话,下面的可以不必再读。如果你还想知道以上 4 中方式到底是如何将 xml 的内容加载到内存的话,下面会给予介绍:
这部分实现主要是依靠以下 3 个函数
CControlUI CDialogBuilder::Create(STRINGorID xml, LPCTSTR type, IDialogBuilderCallback* pCallback,CPaintManagerUI* pManager, CControlUI* pParent);
bool CMarkup::LoadFromMem(BYTE* pByte, DWORD dwSize, int encoding = XMLFILE_ENCODING_UTF8);
bool CMarkup::LoadFromFile(LPCTSTR pstrFilename, int encoding = XMLFILE_ENCODING_UTF8);
下面分类型进行分析:
UILIB_FILE
调用
CMarkup::LoadFromFile,在已经知道了 xml 的全路径的情况下,通过 CreateFile 、 GetFileSize 、 ReadFile 将文件内容读取到内存,然后调用 LoadFromMem 将 xml 内容赋值给 m_pstrXML, 后续解析 xml 时就是读取的
m_pstrXML中的内容
UILIB_ZIP
解压由类 TUZip 实现
先调用 OpenZip 打开 zip 文件,传入参数为 zip 路径和打开类型
ZIP_FILENAME, 得到 HZIP 类型的文件句柄
然后调用 FindZipItem ——传入 zip 中所查找的文件名,查询 zip 中是否存在所查文件
存在所查找文件的情况下,调用 UnzipItem 将 xml 的内容读取到已申请的 Buffer 中——pByte,然后调用 LoadFromMem
UILIB_RESOURCE
调用 HRSRC FindResource(HMODULE hModule, LPCWSTR lpName, LPCWSTR lpType) 查找资源,参数分别为 exe 的实例句柄、资源的 ID 信息、资源类型(UILIB_RESOURCE 加载形式的情况下,这个参数为 “xml”,所以在添加资源的时候要指定类型为 “xml”),确定指定模块中指定类型和名称的资源所在位置
调用 HGLOBAL LoadResource(HMODULE hModule,HRSRC hResInfo), 装载指定资源到全局存储器
调用 LPVOID LockResource(HGLOBAL hResData) 获取到资源在内存中的第一个字节的指针,调用 DWORD SizeofResource(HMODULE hModule,HRSRC hResInfo) 获取资源的字节数
根据上面函数获取到的指针和字节大小,再调用 LoadFromMem
UILIB_ZIPRESOURCE
实现方式相当于 UILIB_RESOURCE + UILIB_ZIP,这里不再赘述了
以上就是 4 中方式 xml 内容加载到内存中的实现方法,首地址为 m_pstrXML,后续的解析就是针对 m_pstrXML 来进行,相关实现原理会在后续的帖子中进行介绍。
知识点小清单:
CDialogBuilder::Create函数中有一个宏 :
HIWORD,定义为
((WORD)((((DWORD_PTR)(_dw)) >> 16) & 0xffff)), 含义是取 4 字节内存的高 16 位,为什么要这么做呢 ? 这是因为只有当 xml 加载类型为 UILIB_RESOURCE 时,
HIWORD(xml.m_lpstr) != NULL才会为 false,所以 else 中执行的是读取 Resource 中的内容,而对于 UILIB_RESOURCE 类型的 CDialogBuilder::Create,第一个参数传入的是 MAKEINTRESOURCE(nID),而资源的 ID 不会超过 65535,也就是最大为 2 个字节,所以高 16 位肯定为 0. 而另外 3 种 xml 的加载方式, 第一个参数传入的是指向 xml 名称的字符串首地址,高 16 不可能为 0 (0x0000-0xFFFF 属于内存预留区,进程内存区不会存在高 16 为 0 的情况)。
CMarkup::LoadFromMem中有以下代码:
if( dwSize >= 3 && pByte[0] == 0xEF && pByte[1] == 0xBB && pByte[2] == 0xBF ) { pByte += 3; dwSize -= 3; }
对于 UTF-8 编码,类似 WINDOWS 自带的记事本等软件保存文件时,会在文件开头加上 0xEF 0xBB 0xBF 三个字节,也就是所谓的 BOM(Byte Order Mark), 所以如果发现开头有这三个字节,则略过这三个字节处理剩下的部分。
相关文章推荐
- Mybatis 源码分析--Configuration.xml配置文件加载到内存
- Mybatis3源码分析(02)-加载Configuration-XMLConfigBuilder
- Duilib 学习源码系列1-加载XML
- Hibernate_hibernate.cfg.xml加载流程源码分析
- Duilib 源码分析之 xml 解析篇
- Mybatis3源码分析(04)-加载Configuration-XMLMapperBuilder加载ResultMap
- Spring IOC 源码分析-xml配置文件加载-注册
- 蔡军生先生第二人生的源码分析(七十二)LLFeatureManager类加载显示特性
- 蔡军生先生第二人生的源码分析(十六)保存人物角色的XML文件
- 第二人生的源码分析(六十七)LLXMLNode使用Expat库打开文件
- 蔡军生先生第二人生的源码分析(六十九)使用LLXmlTree类来分析XML配置文件
- 第二人生的源码分析(六十九)使用LLXmlTree类来分析XML配置文件
- 第二人生的源码分析(六十九)使用LLXmlTree类来分析XML配置文件
- .NET / Rotor源码分析5 - 开始使用WinDbg+SOS调试,sscoree.dll,加载SOS并设置JIT断点
- 第二人生的源码分析(七十二)LLFeatureManager类加载显示特性
- 蔡军生先生第二人生的源码分析(六十七)LLXMLNode使用Expat库打开文件
- 第二人生的源码分析(七十)LLXmlTreeParser类生成XML树
- .NET / Rotor源码分析5 - 开始使用WinDbg+SOS调试,sscoree.dll,加载SOS并设置JIT断点
- 第二人生的源码分析(七十)LLXmlTreeParser类生成XML树
- 第二人生的源码分析(六十七)LLXMLNode使用Expat库打开文件