使用StringTraits給CString自定義全域性的記憶體分配器

blowfish發表於2017-12-19
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)

Implementation of a Custom String Manager (Advanced Method)

第一種方式,需要在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





相關文章