我们定义一个CString变量,虽然我们工程用的是Unicode设置。但是”abc”是ANSI字符串。因为我们没有加_T限制啊。
CString str("abc");由此宏
typedef ATL::CStringT< TCHAR, StrTraitMFC< TCHAR > > CString; CSTRING_EXPLICIT CStringT(_In_opt_z_ const YCHAR* pszSrc) : CThisSimpleString( StringTraits::GetDefaultManager() ) { if( !CheckImplicitLoad( pszSrc ) ) { *this = pszSrc; } }StringTraits就是上面的的那个StrTraitMFC模板类。
static ATL::IAtlStringMgr* GetDefaultManager() throw() { return( AfxGetStringManager() ); } IAtlStringMgr* AFXAPI AfxGetStringManager() { return &afxStringManager; }这个afxStringManager是一个全局变量。对,每个CString居然对应同一个字符串管理器。
CAfxStringMgr afxStringManager;
下面进入CSTringT的父类CSimpleStringT这个构造函数。
explicit CSimpleStringT(_Inout_ IAtlStringMgr* pStringMgr) { ATLENSURE( pStringMgr != NULL ); CStringData* pData = pStringMgr->GetNilString(); Attach( pData ); }这个pStringMgr指向刚才那个全局的afxStringManager。
CStringData* CAfxStringMgr::GetNilString() throw() { m_nil.AddRef(); return &m_nil; }m_nil是一个全局变量afxStringManager的成员变量,那么它也是全局的喽。
在这里面m_nil的成员变量+1,表示有多少个CSTring变量用到了此CStringData,即m_nil。
void AddRef() throw() { ATLASSERT(nRefs > 0); AtlInterlockedIncrement(&nRefs); }再把此CStringData关联到我们的CSimpleStringT。
void Attach(_Inout_ CStringData* pData) throw() { m_pszData = static_cast< PXSTR >( pData->data() ); }CSimpleStringT类中定义了成员变量:PXSTR m_pszData;在Unicode环境下,但我们传的是ANSI字符,所以PXSTR应该是char*类型。
看看的CStringData的data函数,返回的是一个sizeof( CStringData )大小后面的内存。那一块内存放着我们的字符串。那岂不是现在有N个CString变量共享这块内存?是的,后面继续看。
void* data() throw() { return (this+1); }返回到StringT的构造函数,来看这一句,*this = pszSrc;然后进入赋值构造函数:
CStringT& operator=(_In_opt_z_ PCYSTR pszSrc) { // nDestLength is in XCHARs int nDestLength = (pszSrc != NULL) ? StringTraits::GetBaseTypeLength( pszSrc ) : 0; if( nDestLength > 0 ) { PXSTR pszBuffer = GetBuffer( nDestLength ); StringTraits::ConvertToBaseType( pszBuffer, nDestLength, pszSrc); ReleaseBufferSetLength( nDestLength ); } else { Empty(); } return( *this ); }nDestLength ,就是多字节字符串转换为宽字符字符串后的字符数,比如多字节的”abc”转为宽字节后,nDestLength 的值为3。
static int __cdecl GetBaseTypeLength(_In_z_ LPCSTR pszSrc) throw() { // Returns required buffer size in wchar_ts return ::MultiByteToWideChar( _AtlGetConversionACP(), 0, pszSrc, -1, NULL, 0 )-1; }然后看看CSimpleStringT的重量级成员函数吧:
_Ret_cap_(nMinBufferLength + 1) PXSTR GetBuffer(_In_ int nMinBufferLength) { return( PrepareWrite( nMinBufferLength ) ); } PXSTR PrepareWrite(_In_ int nLength) { if (nLength < 0) AtlThrow(E_INVALIDARG); CStringData* pOldData = GetData(); int nShared = 1-pOldData->nRefs; // nShared < 0 means true, >= 0 means false int nTooShort = pOldData->nAllocLength-nLength; // nTooShort < 0 means true, >= 0 means false if( (nShared|nTooShort) < 0 ) // If either sign bit is set (i.e. either is less than zero), we need to copy data { PrepareWrite2( nLength ); } return( m_pszData ); }nRefs变量至少被现在我们的str变量AddRef了一次,所以至少为1,若是大于一,说明和其它一个变量share,但是share的话,只能共读,不能共写(共写的话岂不是一份内存要保存两份字符串,矛盾了)。而我们这个函数叫PrepareWrite,要Write了。只有nRefs==1,或<1(有<1这种情况吗?)并且nAllocLength>nLength(已分配内存大于要分配内存)的情况才不会进入PrepareWrite2。
ATL_NOINLINE void PrepareWrite2(_In_ int nLength) { CStringData* pOldData = GetData(); if( pOldData->nDataLength > nLength ) { nLength = pOldData->nDataLength; } if( pOldData->IsShared() ) { Fork( nLength ); } else if( pOldData->nAllocLength < nLength ) { // Grow exponentially, until we hit 1G, then by 1M thereafter. int nNewLength = pOldData->nAllocLength; if( nNewLength > 1024 * 1024 * 1024 ) { nNewLength += 1024 * 1024; } else { // Exponential growth factor is 1.5. nNewLength = nNewLength + nNewLength / 2; } if( nNewLength < nLength ) { nNewLength = nLength; } Reallocate( nNewLength ); } }我们这边会进入Fork( nLength )这个分支。
ATL_NOINLINE void Fork(_In_ int nLength) { CStringData* pOldData = GetData(); int nOldLength = pOldData->nDataLength; CStringData* pNewData = pOldData->pStringMgr->Clone()->Allocate( nLength, sizeof( XCHAR ) ); if( pNewData == NULL ) { ThrowMemoryException(); } int nCharsToCopy = ((nOldLength < nLength) ? nOldLength : nLength)+1; // Copy '\0' CopyChars( PXSTR( pNewData->data() ), nCharsToCopy,PCXSTR( pOldData->data() ), nCharsToCopy ); pNewData->nDataLength = nOldLength; pOldData->Release(); Attach( pNewData ); } IAtlStringMgr* CAfxStringMgr::Clone() throw() { return this; }从这个Clone()就可以看出IAtlStringMgr还是之前全局的那个。
CStringData* CAfxStringMgr::Allocate( int nChars, int nCharSize ) throw() { size_t nTotalSize; CStringData* pData; size_t nDataBytes; ASSERT(nCharSize > 0); if(nChars < 0) { ASSERT(FALSE); return NULL; } nDataBytes = (nChars+1)*nCharSize; nTotalSize = sizeof( CStringData )+nDataBytes; pData = (CStringData*)malloc( nTotalSize ); if (pData == NULL) return NULL; pData->pStringMgr = this; pData->nRefs = 1; pData->nAllocLength = nChars; pData->nDataLength = 0; return pData; }我们这边的”abc”是多字节,但是我们的工程是Unicode的,所以最终我们的字符以Unicode格式存储于内存。nCharSize 此处则为2。多分配了sizeof( CStringData ),以存放CStringData 信息,这里存放的不是一个CStringData 指针哟,而是sizeof( CStringData )大的内存,以存放一整个CStringData信息。不过要注意的一点是,nAllocLength 为字符数,而不是字节数。回到Fork函数:
为什么这个Copy '\0'要像代码中这么搞呢?不太理解,想想可能是因为现在的StringT与之前的StringT共享一块内存,现在要自立内存了,也要把之前的数据拷过来吧。
CStringData的Release()也比较简单,就是引用计数<=0就把内存释放掉。
void Release() throw() { ATLASSERT( nRefs != 0 ); if( _AtlInterlockedDecrement( &nRefs ) <= 0 ) { pStringMgr->Free( this ); } } void CAfxStringMgr::Free( CStringData* pData ) throw() { free(pData); }现在回到CStringT的operator= 函数:
在Fork函数的Attach调用中已经将m_pszData指向了用于存放字符串的内存。GetBuffer函数返回的是m_pszData。
static void __cdecl ConvertToBaseType( _Out_cap_(nDestLength) LPWSTR pszDest, _In_ int nDestLength, _In_ LPCSTR pszSrc, _In_ int nSrcLength = -1) throw() { // nLen is in wchar_ts ::MultiByteToWideChar( _AtlGetConversionACP(), 0, pszSrc, nSrcLength, pszDest, nDestLength ); }现在再把多字节的”abc”转换为宽字节的字符串并存到m_pszData中。再看看ReleaseBufferSetLength:
void ReleaseBufferSetLength(_In_ int nNewLength) { ATLASSERT( nNewLength >= 0 ); SetLength( nNewLength ); } void SetLength(_In_ int nLength) { ATLASSERT( nLength >= 0 ); ATLASSERT( nLength <= GetData()->nAllocLength ); if( nLength < 0 || nLength > GetData()->nAllocLength) AtlThrow(E_INVALIDARG); GetData()->nDataLength = nLength; m_pszData[nLength] = 0; }这个作用也比较明显,就是设置nDataLength ,并且把字符串结尾置0。
一些关键函数
void ReleaseBuffer(_In_ int nNewLength = -1) { if( nNewLength == -1 ) { int nAlloc = GetData()->nAllocLength; nNewLength = StringLengthN( m_pszData, nAlloc); } SetLength( nNewLength ); }如果ReleaseBuffer不传参数,那么nNewLength 就是m_pszData字符数与nAlloc之间的小者。否则nNewLength 由你自己设置。StringLengthN函数的功能就是取。SetLength( nNewLength )把nNewLength 位置的字符置为0,并把CStringData的nDataLength字段设置为nNewLength 。所以说ReleaseBuffer并不是用来释放内存的,函数名甚是迷惑人。