Duilib 源码分析之 xml 加载篇

来源:互联网 发布:淘宝主词是什么 编辑:程序博客网 时间:2024/06/03 22:53

大家都知道, 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(WIN32) && !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), 所以如果发现开头有这三个字节,则略过这三个字节处理剩下的部分。

3 0
原创粉丝点击