正确编译 Duilib 静态库:避免 ATL 依赖和链接错误

详解如何用 DuiLib_Static.vcxproj 编译 Duilib 静态库,解决 VARIANT 未定义、Unicode 配置不匹配和 ATL 依赖等常见问题。

$2.6k 字/约 12 min👁— views

正确编译 Duilib 静态库:避免 ATL 依赖和链接错误

Duilib 是一个流行的 Windows 界面库,被用于开发具有自定义皮肤的桌面应用(QQ、微信等早期版本均基于此类技术)。但在实际集成中,编译 Duilib 静态库时会遇到各种棘手的 ATL 依赖和链接错误。本文提供完整的解决方案。

Duilib 简介

Duilib(DirectUI Library)是一个开源的 Windows DirectUI 界面库,基于 DirectX/GDI+ 渲染。主要特点:

  • XML 驱动布局:界面用 XML 描述,与代码分离
  • 皮肤/主题支持:通过 XML 和图片资源实现完全自定义外观
  • 轻量:相比 WPF/Qt,运行时依赖少
  • Windows 原生:直接调用 Win32 API,性能好

各 Fork 对比:选哪个版本?

GitHub 上有多个 Duilib 的 fork,主要分三个流派:

1. 原版 duilib(duilib/duilib)

最早的版本,由金山软件工程师 viksoe 维护。代码简洁,适合学习,但功能较少,长期未更新。

特点:
- 代码量小,易于理解
- 无 ATL 依赖(早期版本)
- 缺少现代控件(树形控件、富文本等不完善)
- 不支持 High-DPI

2. DuiLib_Ultimate(qdtroy/DuiLib_Ultimate)

在原版基础上增加了大量控件和特性,是目前维护最活跃的分支之一。

特点:
- 更完整的控件集(WebBrowser 控件、Flash 控件等)
- 支持 Alpha 混合、动画效果
- 部分模块引入了 ATL 依赖(CString、CComPtr 等)
- 项目配置默认使用 /MD,需要修改才能静态编译

3. NIM_Duilib_Framework(netease-kit)

网易云信开源的企业级版本,功能最完整但也最复杂。

特点:
- 完整的 DLL 导出支持(UILIB_API 宏)
- 支持多语言、多主题
- 依赖较多(ATL、GDI+、D2D)
- 有完整的示例项目
- 使用 CMake 构建,跨 VS 版本兼容性好

选择建议

需求 推荐 Fork
学习/小项目 原版 duilib
中型项目,需要丰富控件 DuiLib_Ultimate
企业级应用,需要完整功能 NIM_Duilib_Framework

Visual Studio 2019/2022 工程迁移

老版本 Duilib 通常附带 VS2010/VS2013 工程文件(.sln/.vcxproj),在 VS2019/2022 中打开时需要处理一些问题。

步骤一:升级工程文件

用 Visual Studio 打开旧版 .sln,会弹出"项目升级向导"。直接点"确定",VS 会自动将平台工具集从 v100/v110 升级到最新。

如果升级后报错,手动修改 .vcxproj 文件:

<!-- 找到这行 -->
<PlatformToolset>v100</PlatformToolset>

<!-- 改成(VS2019)-->
<PlatformToolset>v142</PlatformToolset>

<!-- 或(VS2022)-->
<PlatformToolset>v143</PlatformToolset>

步骤二:处理废弃的 Windows SDK 版本

老工程可能指定了 8.110.0.14393.0 等旧版 SDK。

<!-- 修改为使用最新安装的 SDK -->
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>

在 VS2019/2022 中,10.0 表示"使用最新安装的 SDK 版本"。

步骤三:处理废弃的 API

VS2019/2022 对一些老式 C 函数报错(strcpysprintf 等)。两种处理方式:

方式 A:添加预处理器定义(快速,不推荐生产环境)

_CRT_SECURE_NO_WARNINGS
_CRT_NONSTDC_NO_DEPRECATE

方式 B:修改代码使用安全版本(推荐)

// 旧版
strcpy(buf, src);

// 新版
strcpy_s(buf, sizeof(buf), src);

步骤四:处理 ATL 依赖报错

VS2019/2022 Community Edition 默认不安装 ATL,需要在 Visual Studio Installer 中额外勾选:

Visual Studio Installer
→ 修改(你的 VS 版本)
→ 单个组件
→ 搜索 "ATL"
→ 勾选:适用于 v142/v143 生成工具的 C++ ATL(x86 和 x64)
→ 修改

/MT vs /MD 运行时库详解

这是 Duilib 静态库集成中最容易出错的地方。

四个选项

选项 全称 运行时 适用配置
/MT MultiThreaded 静态链接 CRT Release
/MTd MultiThreaded Debug 静态链接 Debug CRT Debug
/MD MultiThreaded DLL 动态链接 VC 运行时 Release
/MDd MultiThreaded Debug DLL 动态链接 Debug 运行时 Debug

为什么混用会报错?

CRT(C 运行时库)包含了 malloc/freenew/delete、标准容器等基础设施。当两个模块使用不同版本的 CRT 时:

  1. 它们各自维护独立的堆(heap)
  2. 在模块 A 中 new 的对象,在模块 B 中 delete 会崩溃
  3. 链接器检测到符号冲突时直接报错:
error LNK2038: mismatch detected for 'RuntimeLibrary': 
  value 'MT_StaticRelease' doesn't match value 'MD_DynamicRelease' in duilib.lib(xxx.obj)

如何统一配置

方案一:全部使用 /MT(推荐用于独立分发)

优点:可执行文件不依赖 VC 运行时,用户无需安装额外组件。 缺点:可执行文件体积增大(通常增加 ~500KB)。

方案二:全部使用 /MD(推荐用于内部工具)

优点:编译速度快,链接快,可执行文件小。 缺点:需要用户安装对应版本的 Visual C++ Redistributable。

最重要原则:所有参与链接的 .lib 和 .obj 必须使用相同选项。


ATL 依赖的根因分析

ATL 是什么

ATL(Active Template Library)是微软提供的 C++ 模板库,主要用于 COM 编程。但 ATL 中的一些工具类(CStringCRectCPointCComPtr 等)非常好用,所以很多非 COM 代码也引用了它们。

Duilib 中常见的 ATL 引用:

#include <atlbase.h>   // CComPtr, CComBSTR, CComVariant
#include <atlstr.h>    // CString (ATL 版本)
#include <atlapp.h>    // CMessageLoop, CAppModule
#include <atlwin.h>    // CWindow, CWindowImpl
#include <atltypes.h>  // CRect, CPoint, CSize

ATL 的两种链接方式

静态 ATLatls.lib):

  • 定义宏 _ATL_STATIC_REGISTRY
  • 不需要单独分发 ATL DLL
  • 但需要与 CRT 保持一致(/MT 对应静态 ATL)

动态 ATLatl.dll):

  • 系统自带或随 VC 运行时分发
  • 较新版本的 Windows 已内置

去掉 ATL 依赖的方法

如果你的项目不需要 COM 功能,可以用等价的 WTL 类或 STL 类替换 ATL:

ATL 类 替代方案
CString std::wstring / std::string
CRect 自定义 RECT 包装 / 直接用 RECT
CPoint POINT 结构体
CSize SIZE 结构体
CComPtr<T> std::shared_ptr<T> / 手动 Release

在 Duilib 代码中找到所有 ATL 头文件引用,逐一替换。大多数 Duilib fork 在纯界面场景下不需要真正的 ATL COM 功能。


完整的 Visual Studio 项目属性配置

Duilib 静态库项目配置

以下是 VS2019/2022 中 Duilib.vcxproj 应有的关键配置(Release|x64 为例):

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
  <ConfigurationType>StaticLibrary</ConfigurationType>
  <UseDebugLibraries>false</UseDebugLibraries>
  <PlatformToolset>v143</PlatformToolset>
  <WholeProgramOptimization>true</WholeProgramOptimization>
  <CharacterSet>Unicode</CharacterSet>
</PropertyGroup>

<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
  <ClCompile>
    <!-- 运行时库:静态多线程 -->
    <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
    <!-- 预处理器定义 -->
    <PreprocessorDefinitions>
      NDEBUG;
      _LIB;
      UNICODE;
      _UNICODE;
      UILIB_STATIC;
      WIN32_LEAN_AND_MEAN;
      %(PreprocessorDefinitions)
    </PreprocessorDefinitions>
    <!-- 优化级别 -->
    <Optimization>MaxSpeed</Optimization>
    <!-- 警告级别 -->
    <WarningLevel>Level3</WarningLevel>
    <!-- 安全检查(Release 下关闭加快速度)-->
    <BasicRuntimeChecks>Default</BasicRuntimeChecks>
  </ClCompile>
</ItemDefinitionGroup>

主项目配置

<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
  <ClCompile>
    <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
    <PreprocessorDefinitions>
      NDEBUG;UNICODE;_UNICODE;UILIB_STATIC;%(PreprocessorDefinitions)
    </PreprocessorDefinitions>
  </ClCompile>
  <Link>
    <AdditionalDependencies>
      duilib.lib;
      gdiplus.lib;
      msimg32.lib;
      imm32.lib;
      %(AdditionalDependencies)
    </AdditionalDependencies>
    <AdditionalLibraryDirectories>
      $(SolutionDir)lib\$(Platform)\$(Configuration);
      %(AdditionalLibraryDirectories)
    </AdditionalLibraryDirectories>
    <!-- 子系统:Windows(不显示控制台窗口)-->
    <SubSystem>Windows</SubSystem>
  </Link>
</ItemDefinitionGroup>

静态库 vs 动态库选择

对比项 静态库(.lib) 动态库(.dll)
部署 无需额外文件 需要附带 .dll
体积 可执行文件较大 多程序可共享
版本管理 简单(一体化) 可独立升级
调试 方便(源码集成) 稍复杂
ATL 冲突风险 较高(需统一配置) 较低(独立命名空间)

对于单独分发的桌面应用,静态库通常是更好的选择:部署简单,不用担心 DLL Hell。


ATL 依赖问题根因

Duilib 的某些模块使用了 ATL 类(如 CStringCRect、ATL 智能指针等)。ATL 有两个版本:

  1. ATL 动态链接版:需要 atl<N>.dll(随 Visual C++ 运行时分发)
  2. ATL 静态链接版:通过预处理器宏 _ATL_STATIC_REGISTRY/_ATL_DLL 控制

当你编译 Duilib 静态库并链接到你的项目时,如果运行时库设置(/MD vs /MT)或 ATL 配置不一致,就会出现以下错误:

error LNK2038: mismatch detected for 'RuntimeLibrary': 
  value 'MT_StaticRelease' doesn't match value 'MD_DynamicRelease'

error LNK2019: unresolved external symbol __imp_AtlWinModuleRegisterWndClassInfoW

error LNK2019: unresolved external symbol _AtlBaseModule

fatal error LNK1120: 3 unresolved externals

预处理器宏配置

不同 Duilib fork 可能需要不同的宏。通常需要:

// 告诉 Duilib 以静态库方式编译(不导出 DLL 符号)
#define UILIB_STATIC

// 如果 Duilib 有 DLL 导出宏定义(常见于 NIM Duilib)
// 在编译 .lib 时需要定义此宏来关闭 __declspec(dllexport)

查看 Duilib 的 UIlib.hStdAfx.h,找到类似以下代码:

#ifdef UILIB_EXPORTS
#  define UILIB_API __declspec(dllexport)
#elif defined(UILIB_STATIC)
#  define UILIB_API
#else
#  define UILIB_API __declspec(dllimport)
#endif

编译静态库时,必须定义 UILIB_STATIC(或类似宏),否则所有接口都会是 __declspec(dllimport),链接时会出错。


常见链接错误及解决

错误 1:RuntimeLibrary mismatch

error LNK2038: mismatch detected for 'RuntimeLibrary': 
  value 'MT_StaticRelease' doesn't match value 'MD_DynamicRelease' in main.obj

解决:统一所有项目(Duilib + 主项目 + 所有第三方库)的运行时库设置。

错误 2:ATL 符号未解析

error LNK2019: unresolved external symbol _AtlBaseModule
error LNK2019: unresolved external symbol __AtlBaseModuleClass::Init

解决

// 在主项目的 stdafx.cpp(预编译头源文件)中添加:
#include <atlbase.h>
#include <atlapp.h>

// 或者确保主项目链接了正确的 ATL 库
// 项目属性 → 链接器 → 输入 → 附加依赖项
// 添加:atls.lib(静态 ATL)或 atl.lib

错误 3:_ITERATOR_DEBUG_LEVEL mismatch

error LNK2038: mismatch detected for '_ITERATOR_DEBUG_LEVEL': 
  value '0' doesn't match value '2'

解决:Duilib 和主项目必须用相同的配置(都 Debug 或都 Release)编译。

错误 4:重复定义

error LNK2005: _DllMain@12 already defined in MSVCRT.lib
error LNK2005: __setargv already defined

解决

项目属性 → 链接器 → 命令行 → 其他选项:
添加:/NODEFAULTLIB:MSVCRT

或者检查是否混用了 /MT/MD

错误 5:GDI+ 未链接

error LNK2019: unresolved external symbol __imp__GdiplusStartup@12

解决:主项目添加依赖项 gdiplus.lib

项目属性 → 链接器 → 输入 → 附加依赖项:
gdiplus.lib

错误 6:找不到 atlbase.h

fatal error C1083: Cannot open include file: 'atlbase.h': No such file or directory

解决:在 Visual Studio Installer 中安装 ATL 组件:

VS Installer → 修改 → 单个组件
→ 搜索 "ATL"
→ 勾选 C++ ATL for latest v14x build tools (x86 & x64)

错误 7:x86/x64 架构不匹配

error LNK2019: unresolved external symbol xxx referenced in function yyy
fatal error LNK1120: N unresolved externals

当 Duilib.lib 编译为 x86 但主项目是 x64 时发生(或反之)。

解决:确保 Duilib 和主项目使用相同的目标平台,分别编译 x86 和 x64 版本的 Duilib.lib。


最终配置清单

使用以下清单逐项检查:

Duilib 项目配置

  • [ ] 配置类型:静态库(.lib)
  • [ ] 运行时库:/MT(Release)或 /MTd(Debug)
  • [ ] 预处理器:UILIB_STATIC_LIBUNICODE_UNICODE
  • [ ] 字符集:Unicode
  • [ ] ATL 组件已在 VS Installer 中安装

主项目配置

  • [ ] 运行时库与 Duilib 一致(/MT/MD
  • [ ] 附加依赖项:duilib.libgdiplus.libmsimg32.lib
  • [ ] 附加库目录:Duilib.lib 所在路径
  • [ ] 预处理器:UILIB_STATICUNICODE
  • [ ] 目标平台(x86/x64)与 Duilib 一致

编译顺序

  1. 先编译 Duilib 静态库
  2. 再编译主项目

验证

  • Debug 和 Release 都能正常编译链接
  • 运行时不崩溃、字体/资源正常加载

遵循这份清单,你的 Duilib 静态库集成就能顺利完成,避免绝大多数链接错误。