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

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

Duilib 是一个轻量级的 Windows 界面库,常用于制作带自定义皮肤的桌面程序。编译它的静态库版本时,有几个坑非常容易踩,本文把完整流程和常见错误一次讲清楚。

⚠️ 注意:Duilib 原仓库(duilib/duilib)自 2019 年后已基本停止维护。新项目建议考虑更活跃的替代方案,如 DuiLib_UltimateDirectUI 或直接使用 Qt/WinUI 3。本文内容适用于需要维护存量 Duilib 项目的场景。


为什么要用静态库

Duilib 有两种使用方式:

  • DLL 动态库:程序体积小,但分发时需要附带 .dll 文件
  • 静态库:将 Duilib 代码直接编译进 exe,单文件分发,无运行时依赖

对于要分发给用户的桌面程序,静态库几乎是更优选择。


编译步骤

1. 打开正确的工程文件

Duilib 仓库里有多个 .vcxproj,要用静态库版本:

DuiLib\DuiLib_Static.vcxproj

不要用 DuiLib.vcxproj(那是 DLL 版本)。

2. 复制 .filters 文件(重要)

Visual Studio 需要 .vcxproj.filters 文件来显示项目文件树结构。如果没有 .filters 文件,VS 可能报错或无法加载:

DuiLib\DuiLib_Static.vcxproj.filters

如果该文件不存在,从 DuiLib.vcxproj.filters 复制一份并重命名。

3. 选择正确的编译配置

Duilib 静态库有四个配置:

配置名字符集说明
Debug多字节调试,多字节
Release多字节发布,多字节
UnicodeDebugUnicode调试,Unicode
UnicodeReleaseUnicode发布,Unicode

新项目默认用 Unicode,因此选 UnicodeDebugUnicodeRelease

⚠️ 关键:你的主项目字符集必须与 Duilib 静态库的字符集完全一致,否则链接时会出现符号不匹配。

点击 生成 → 生成 DuiLib_Static,等待编译完成,产出 DuiLib.lib


新建测试项目

配置包含目录和库目录

在主项目属性中:

  1. C/C++ → 常规 → 附加包含目录:添加 Duilib 根目录
  2. 链接器 → 常规 → 附加库目录:添加 DuiLib\lib 或你的输出目录
  3. 链接器 → 输入 → 附加依赖项:添加 DuiLib.lib

还需要链接 Windows 基础库:

DuiLib.lib
imm32.lib
shlwapi.lib

必须添加:ATL 依赖

编译主项目时,经常会遇到这样的错误:

error C2079: 'tagVARIANT::bstrVal' uses undefined struct 'tagBSTR'
error C2065: 'VARIANT': undeclared identifier

这是因为 Duilib 内部使用了 VARIANTBSTR 等 COM 类型,这些定义来自 ATL 头文件,但默认的 Win32 项目不包含 ATL。

解决方案:在 stdafx.h 中添加 ATL 头文件

// stdafx.h(预编译头文件)

// 防止 ATL CString 构造函数的警告
#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS

// ATL 基础(提供 COM 支持)
#include <atlbase.h>

// ATL 字符串(提供 CString,替代 MFC 版本)
#include <atlstr.h>

放在其他 #include 之前,重新编译即可解决 VARIANT 未定义的问题。

为什么需要 ATL

  • atlbase.h:提供 COM 智能指针(CComPtr)、VARIANT 封装等
  • atlstr.h:提供独立于 MFC 的 CString 实现
  • VARIANT 是 COM 的核心数据类型,BSTR 是 COM 字符串类型,Duilib 的脚本引擎和属性系统用到了它们

Unicode vs 多字节配置匹配

这是最常见的链接错误来源之一。

错误现象

error LNK2019: unresolved external symbol "public: static class ATL::CStringT<wchar_t,...>"

或者链接成功但运行时乱码。

根本原因

Unicode 配置下,CStringCStringWwchar_t),多字节配置下是 CStringAchar)。如果 Duilib 用 Unicode 编译,但主项目用多字节,两边的符号表不匹配。

解决方法

确保主项目的字符集与 Duilib 一致:

  1. 右键主项目 → 属性 → 常规 → 字符集
  2. 改为 使用 Unicode 字符集(对应 UnicodeDebug/UnicodeRelease)

或者将整个解决方案的字符集统一:

  • 菜单 → 生成 → 批生成,检查所有项目配置是否一致

常见链接错误排查

错误 1:LNK2001 无法解析的外部符号

error LNK2001: unresolved external symbol _DuiLib_xxx

原因:库路径未正确配置,或 .lib 文件未添加到附加依赖项。

排查:确认 DuiLib.lib 存在于配置的库目录,且已加入附加依赖项。

错误 2:LNK2038 检测到不匹配项

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

原因:Debug 版主项目链接了 Release 版 Duilib 库(或反之)。

解决:确保 Debug 配置链接 Debug 版本的 .lib,Release 同理。可在属性中针对不同配置指定不同的库路径。

错误 3:LIBCMT 与 MSVCRT 冲突

warning LNK4098: defaultlib 'LIBCMT' conflicts with use of other libs

原因:运行时库类型不一致。

解决:统一运行时库设置(C/C++ → 代码生成 → 运行库),项目和 Duilib 必须一致:

配置运行时库
Debug + 动态/MDd
Release + 动态/MD
Debug + 静态/MTd
Release + 静态/MT

完整 stdafx.h 示例

// stdafx.h

#pragma once

// 防止 Windows 头文件包含不常用的内容
#define WIN32_LEAN_AND_MEAN

// Windows 最低版本要求(Vista+)
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0600
#endif

// ATL 配置(必须在 ATL 头文件之前)
#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS
#define _ATL_NO_AUTOMATIC_NAMESPACE

#include <atlbase.h>
#include <atlstr.h>

// Duilib 主头文件
#include "UIlib.h"

using namespace DuiLib;

总结

编译 Duilib 静态库的关键要点:

  1. 使用 DuiLib_Static.vcxproj,复制 .filters 文件
  2. 字符集选 Unicode(UnicodeDebug / UnicodeRelease)
  3. 主项目也必须用 Unicode,并与 Duilib 的 Debug/Release 配置对应
  4. stdafx.h 中加 atlbase.hatlstr.h,定义 _ATL_CSTRING_EXPLICIT_CONSTRUCTORS
  5. 附加依赖项加上 imm32.libshlwapi.lib