utilities 模块的 DLL 编译机制¶
本文详细介绍了 SOUI 中 utilities 模块采用 DLL 编译的原因及其解决方案。
问题背景¶
在 SOUI 中,与 DuiEngine 相比一个重要的变化是很多模块变成了独立的 DLL。这在某些场景下可能会引发一些疑问,比如:
- 为什么不能像 DuiEngine 那样提供 LIB 编译模式?
- 为什么 utilities 模块默认只提供 DLL 编译?
- 如何在需要单一 EXE 的项目中处理这个问题?
技术原因分析¶
字符串对象的跨模块传递¶
utilities 模块之所以默认采用 DLL 编译,主要是因为其中实现了 SString
类。字符串作为最基本的对象,在不同编译条件下有不同的考虑:
- 动态运行库编译 (MD/MDd)
- 所有模块使用相同的 CRT
-
模块间对象传递相对简单
-
静态运行库编译 (MT/MTd)
- 不同模块可能使用不同的 CRT
- 模块间对象传递需要特别处理
SString 的特殊设计¶
SOUI 中的字符串实现采用了特殊的技巧:
template <class tchar, class tchar_traits>
class TStringT
{
public:
typedef tchar _tchar;
typedef const _tchar* pctstr;
protected:
tchar* m_pszData; // 指向引用计数字符串数据的指针
};
关键特性: - 每个字符串对象只包含一个指针成员 - 通过模板类导出机制确保内存管理
模板类导出¶
#ifdef UTILITIES_EXPORTS
#define EXPIMP_TEMPLATE
#else
#define EXPIMP_TEMPLATE extern
#endif
#pragma warning (disable : 4231)
EXPIMP_TEMPLATE template class UTILITIES_API TStringT<char, char_traits>;
EXPIMP_TEMPLATE template class UTILITIES_API TStringT<wchar_t, wchar_traits>;
这样处理确保了: - 字符串的所有运行代码都在 utilities 模块内部 - 内存的分配和释放固定在 utilities 模块中 - 实现了安全的跨模块对象传递
与 std::string 的比较¶
std::string 在跨模块使用时存在的问题:
- 模板实例化导致代码分散
- 每个模块都会包含实例化代码
-
不同模块的代码不同
-
内存管理风险
- A 模块分配的内存
- B 模块释放导致崩溃
解决方案¶
1. 需要跨模块传递的情况¶
- 保持使用 DLL 方式编译
- 利用字符串对象的单一指针特性
- 通过模板导出确保内存管理一致
2. 单一 EXE 项目¶
- 直接修改 utilities 模块为 LIB 编译
- 不存在跨模块传递问题
- 简化部署和分发
最佳实践¶
- 评估项目需求
- 是否需要跨模块传递字符串
-
是否追求单一 EXE
-
选择合适的编译方式
- 跨模块传递:使用 DLL
-
单一 EXE:使用 LIB
-
注意事项
- 统一运行库选择
- 关注内存管理
- 避免跨模块内存释放
总结¶
utilities 模块采用 DLL 编译是一个经过深思熟虑的设计决策,主要考虑了:
✓ 跨模块字符串传递的安全性
✓ 统一的内存管理机制
✓ 不同编译条件下的兼容性
对于不需要跨模块功能的项目,可以轻松地切换到 LIB 编译模式,既保证了灵活性,又满足了不同场景的需求。