您的位置:首页 > 产品设计 > UI/UE

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 的消息相应函数中处理的。

首先贴出加载资源的代码如下:

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 的全路径的情况下,通过 CreateFileGetFileSizeReadFile 将文件内容读取到内存,然后调用 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), 所以如果发现开头有这三个字节,则略过这三个字节处理剩下的部分。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  xml 源码 界面