过去,MFC 开发人员从 CString 派生出专门化自己的字符串类。 在 Microsoft Visual C++.NET (MFC 8.0) 中,CString 类被称为 CStringT 的模板类取代。 这有若干优势:
允许在 ATL 项目中使用 MFC
CString类,而无需链接到更大的 MFC 静态库或 DLL。使用新的
CStringT模板类,可以使用指定字符特征的模板参数来自定义CString行为,类似于 C++ 标准库中的模板。使用
CStringT从 DLL 导出自己的字符串类时,编译器还会自动导出CString基类。 由于CString本身是模板类,因此在使用时,编译器可能会将其实例化,除非编译器知道CString是从 DLL 导入的。 如果你已将项目从 Visual C++ 6.0 迁移到 Visual C++.NET,你可能会看到多重定义的CString的链接器符号错误,因为从 DLL 导入的CString与本地实例化的版本发生冲突。 下面介绍了执行此操作的正确方法。
以下方案将导致链接器生成用于多重定义的类的符号错误。 假设从 MFC 扩展 DLL 导出 CString 派生型类 (CMyString):
// MyString.h
class AFX_EXT_CLASS CMyString : public CString
{
// Your implementation code
};
使用者代码混用 CString 和 CMyString。 预编译标头中不包含“MyString.h”,并且使用 CString 不会让 CMyString 可见。
假设在单独的源文件 Source1.cpp 和 Source2.cpp 中使用 CString 和 CMyString 类。 在 Source1.cpp 中,使用 CMyString 和#include MyString.h。 在 Source2.cpp 中,使用 CString,但不使用 #include MyString.h。 在这种情况下,链接器会报错:CStringT 被多重定义。 原因是,CString 既是从导出 CMyString 的 DLL 导入的,又是由编译器通过 CStringT 模板在本地实例化的。
若要解决此问题,请执行下列操作:
从 MFC90.DLL 导出 CStringA 和 CStringW(以及必要的基类)。 与以前的 MFC 实现一样,包含 MFC 的项目将始终使用导出的 MFC DLL CStringA 和 CStringW。
然后使用 CStringT 模板创建一个可导出的派生类,例如下面的 CStringT_Exported:
#ifdef _AFXDLL
#define AFX_EXT_CSTRING AFX_EXT_CLASS
#else
#define AFX_EXT_CSTRING
#endif
template< typename BaseType, class StringTraits >
class AFX_EXT_CSTRING CStringT_Exported
: public CStringT< BaseType, StringTraits >
{
// Reimplement all CStringT<> constructors and
// forward to the base class implementation
};
在 AfxStr.h 中,将之前的 CString、CStringA 和 CStringW typedef 替换如下:
typedef CStringT_Exported< wchar_t,
StrTraitMFC< wchar_t > > CStringW;
typedef CStringT_Exported< char,
StrTraitMFC< char > > CStringA;
typedef CStringT_Exported< TCHAR,
StrTraitMFC< TCHAR > > CString;
有几个注意事项:
不应导出
CStringT自身,因为这将导致仅 ATL 项目导出专用CStringT类。使用
CStringT中的可导出派生类可以最大限度地减少重新实现CStringT功能的必要性。 其他代码仅限于将构造函数转发到CStringT基类。CString、CStringA和CStringW仅应在使用 MFC 共享 DLL 构建时标记为__declspec(dllexport/dllimport)。 如果与 MFC 静态库链接,则不应将这些类标记为已导出;否则,在用户 DLL 内部使用CString、CStringA和CStringW也会将CString标记为已导出。