使用StringTraits给CString自定义全局的内存分配器

发布者:blowfish
发布于:2017-12-19 14:56
CString这个超级老古董,还是有不少Windows程序员爱用的,无他,但手熟尔。记得早些年CodeGuru还是什么地方曾经有人专门搞了一个VC6的宏,用来把VC6中的CString提取出来脱离ATL/MFC单独使用。

MFC的CString默认用的 CCRTHeap,从当前DLL/EXE的CRT堆中分配、释放内存;而ATL的CString默认用的CWin32Heap,从进程默认堆中分配、释放内存,进程默认堆是进程中的全部DLL/EXE模块共享的,没有实现隔离。要实现一定的隔离的话,还是使用 CCRTHeap较好,或者自定义一个分配器。

如何将ATL的CString默认切换到 CCRTHeap呢?MSDN上给出了Basic、Advanced两种方式:

Implementation of a Custom String Manager (Basic Method)
https://msdn.microsoft.com/en-us/library/e604s100.aspx

Implementation of a Custom String Manager (Advanced Method)
https://msdn.microsoft.com/en-us/library/exbhk60h.aspx

第一种方式,需要在CString构造时传递内存分配器,这样在同一个DLL/EXE模块中,不同的CString对象可以用不同的内存分配器,通常我们并无这种需求。我们的需求是将自己所关心的CString对象都统一切换成某种内存分配器,如果要实现这点,用这种方式是可以的,但是可能比较麻烦,方法之一是从CStringT派生出子类,并重新定义子类的相关的全部构造函数。

第二种方式,微软是暗示可以操作CStringT内部隐藏的那个CStringData结构。通常我们不需要搞这种细节又底层的hack。

其实看一下ATL CString的代码,发现它提供了StringTraits扩展机制,通过这种机制,就可以统一切换CString的默认内存分配器,而且比较优雅。

CStringT的一部分构造函数使用了StringTraits的GetDefaultManager()方法:

template< typename BaseType, class StringTraits >
class CStringT
{
public:
	CStringT() throw() :
		CThisSimpleString( StringTraits::GetDefaultManager() )
	{
	}

VC头文件中的CString默认提供三种StringTraits,分别用于三种不同的编译场合:

第一种是StrTraitATL,用于ATL,头文件是<atlmfc\include\atlstr.h>:

template< typename _BaseType = char, class StringIterator = ChTraitsOS< _BaseType > >
class StrTraitATL :
	public StringIterator
{
public:
	static HINSTANCE FindStringResourceInstance(_In_ UINT nID) throw()
	{
		return( AtlFindStringResourceInstance( nID ) );
	}

	static IAtlStringMgr* GetDefaultManager() throw()
	{
		return CAtlStringMgr::GetInstance();
	}
};

第二种和第三种是StrTraitMFC、StrTraitMFC_DLL,用于MFC的静态链接、动态链接,头文件是<atlmfc\include\afxstr.h>:

template< typename _CharType = char, class StringIterator = ATL::ChTraitsCRT< _CharType > >
class StrTraitMFC : 
	public StringIterator
{
public:
	static HINSTANCE FindStringResourceInstance( UINT nID ) throw()
	{
		return( AfxFindStringResourceHandle( nID ) );
	}

	static ATL::IAtlStringMgr* GetDefaultManager() throw()
	{
		return( AfxGetStringManager() );
	}
};

template< typename _CharType, class StringIterator>
class StrTraitMFC_DLL : public StringIterator
{
public:
	static HINSTANCE FindStringResourceInstance( UINT nID ) throw()
	{
		return( AfxFindStringResourceHandle( nID ) );
	}

	static ATL::IAtlStringMgr* GetDefaultManager() throw()
	{
		return( AfxGetStringManager() );
	}
};

显然,我们只需要基于 StrTraitATL改出一个自己的traits,并且采用 CCRTHeap就好。
(下面的代码假定编译时采用了/Zc:threadSafeInit-选项禁用了编译器自带的静态对象的线程安全初始化特性以支持Windows XP)

#include <atlstr.h>

//这个单例类,参考:https://zhuanlan.kanxue.com/article-649.htm
#include "CSingletonT.h"

template< typename _BaseType = char, class StringIterator = ChTraitsCRT< _BaseType > >
class StrTraitCRT :
	public StringIterator,
	public CSingletonT<StrTraitCRT<_BaseType, StringIterator>>
{
public:

	static HINSTANCE FindStringResourceInstance(_In_ UINT nID) throw()
	{
		return(AtlFindStringResourceInstance(nID));
	}

	//CSingletonT<>保证本函数只执行一次,且是线程安全的。
	bool InitOnce(void)
	{
		static CCRTHeap        s_CrtHeap;
		static CAtlStringMgr   s_AtlStringMgr(&s_CrtHeap);

		m_pAtlStringMgr = &s_AtlStringMgr;

		return true;
	}

	IAtlStringMgr *GetStringManager()
	{
		return m_pAtlStringMgr;
	}

	static IAtlStringMgr* GetDefaultManager() throw()
	{
		StrTraitCRT<_BaseType, StringIterator>::Instance().Init();
		return StrTraitCRT<_BaseType, StringIterator>::Instance().GetStringManager();
	}

private:

	CAtlStringMgr * m_pAtlStringMgr;
};

typedef CStringT<wchar_t, StrTraitCRT<wchar_t>> CCrtStringW;
typedef CStringT<char,    StrTraitCRT<char>>    CCrtStringA;
typedef CStringT<TCHAR,   StrTraitCRT<TCHAR>>   CCrtString;

//以下的宏,只是为了避免去批量修改老代码中已经大量使用的CString、CStringA、CStringW拼写。
//把这些都放在"stdafx.h"中即可。
#define CStringA CCrtStringA
#define CStringW CCrtStringW
#define CString  CCrtString






声明:该文观点仅代表作者本人,转载请注明来自看雪