跳转至

utilities 模块的 DLL 编译机制

本文详细介绍了 SOUI 中 utilities 模块采用 DLL 编译的原因及其解决方案。

问题背景

在 SOUI 中,与 DuiEngine 相比一个重要的变化是很多模块变成了独立的 DLL。这在某些场景下可能会引发一些疑问,比如:

  • 为什么不能像 DuiEngine 那样提供 LIB 编译模式?
  • 为什么 utilities 模块默认只提供 DLL 编译?
  • 如何在需要单一 EXE 的项目中处理这个问题?

技术原因分析

字符串对象的跨模块传递

utilities 模块之所以默认采用 DLL 编译,主要是因为其中实现了 SString 类。字符串作为最基本的对象,在不同编译条件下有不同的考虑:

  1. 动态运行库编译 (MD/MDd)
  2. 所有模块使用相同的 CRT
  3. 模块间对象传递相对简单

  4. 静态运行库编译 (MT/MTd)

  5. 不同模块可能使用不同的 CRT
  6. 模块间对象传递需要特别处理

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 在跨模块使用时存在的问题:

  1. 模板实例化导致代码分散
  2. 每个模块都会包含实例化代码
  3. 不同模块的代码不同

  4. 内存管理风险

  5. A 模块分配的内存
  6. B 模块释放导致崩溃

解决方案

1. 需要跨模块传递的情况

  • 保持使用 DLL 方式编译
  • 利用字符串对象的单一指针特性
  • 通过模板导出确保内存管理一致

2. 单一 EXE 项目

  • 直接修改 utilities 模块为 LIB 编译
  • 不存在跨模块传递问题
  • 简化部署和分发

最佳实践

  1. 评估项目需求
  2. 是否需要跨模块传递字符串
  3. 是否追求单一 EXE

  4. 选择合适的编译方式

  5. 跨模块传递:使用 DLL
  6. 单一 EXE:使用 LIB

  7. 注意事项

  8. 统一运行库选择
  9. 关注内存管理
  10. 避免跨模块内存释放

总结

utilities 模块采用 DLL 编译是一个经过深思熟虑的设计决策,主要考虑了:

✓ 跨模块字符串传递的安全性
✓ 统一的内存管理机制
✓ 不同编译条件下的兼容性

对于不需要跨模块功能的项目,可以轻松地切换到 LIB 编译模式,既保证了灵活性,又满足了不同场景的需求。