Duilib 源码分析之 xml 加载篇

大家都知道, Duilib 的界面内容除了在代码中动态加入外,最常用的就是通过写好 xml 来加载了。今天就介绍一下 Duilib 是如何读取到 xml 并将 xml 内容加载到内存中的。

我们来按加载的流程来分析一下,流程如下:

Created with Raphaël 2.1.0 开始 创建 Windows OnCreate 加载 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), 所以如果发现开头有这三个字节,则略过这三个字节处理剩下的部分。

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
duilib是一个开源的C++图形界面库,它提供了丰富的控件和布局管理功能,以及良好的跨平台支持。下面我将对duilib源码进行简要分析。 1. 基础架构:duilib源码采用了模块化的设计,主要分为“Core”和“UI”两个部分。其中,“Core”模块是duilib的核心部分,提供了基础的窗口、消息循环等功能;而“UI”模块则提供了各种控件和布局管理等高级功能。 2. 控件类别:duilib提供了丰富的控件类别,包括基础的窗口类(如窗口、对话框)、容器类(如水平布局、垂直布局)、常用控件类(如按钮、文本框)、自定义控件类等。每个控件类都有相应的成员函数和消息处理函数,以便实现对控件的创建、设置属性和处理事件等操作。 3. 消息处理机制:duilib使用了消息映射的机制来处理控件的事件。每个控件类都有自己的消息映射表,用于将消息和相应的处理函数关联起来。当控件接收到特定的消息时,duilib会根据映射表找到对应的处理函数进行处理。 4. 布局管理:duilib提供了灵活且强大的布局管理功能,可以通过设置布局属性实现控件的自动适应和自动排列。布局管理器可以根据指定的规则对子控件进行自动布局,以适应不同的窗口尺寸。 5. 绘制引擎:duilib使用了自定义的绘制引擎来实现界面绘制。该绘制引擎可以根据控件的属性和状态来决定绘制的方式,以实现不同的视觉效果。 总结来说,duilib源码分析涉及到基础架构、控件类别、消息处理机制、布局管理和绘制引擎等方面。通过深入研究这些内容,我们可以更好地理解duilib的设计理念和工作原理,以便能够更好地使用和定制duilib提供的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值