// CSTRING.CPP // // Based on the original MFC source file. // // This is a part of the Microsoft Foundation Classes C++ library. // Copyright (C) 1992-1995 Microsoft Corporation // All rights reserved. // // This source code is only intended as a supplement to the // Microsoft Foundation Classes Reference and related // electronic documentation provided with the library. // See these sources for detailed information regarding the // Microsoft Foundation Classes product. #include "precomp.h" #include #ifdef AFX_CORE1_SEG #pragma code_seg(AFX_CORE1_SEG) #endif #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // static class data, special inlines // afxChNil is left for backward compatibility REMAFX_DATADEF TCHAR AFXChNil = '\0'; // For an empty string, m_pchData will point here // (note: avoids special case of checking for NULL m_pchData) // empty string data (and locked) static int RGInitData[] = { -1, 0, 0, 0 }; static REMAFX_DATADEF CSTRINGData* AFXDataNil = (CSTRINGData*)&RGInitData; static LPCTSTR AFXPchNil = (LPCTSTR)(((BYTE*)&RGInitData)+sizeof(CSTRINGData)); // special function to make AFXEmptyString work even during initialization const CSTRING& REMAFXAPI AFXGetEmptyString() { return *(CSTRING*)&AFXPchNil; } ////////////////////////////////////////////////////////////////////////////// // Construction/Destruction CSTRING::CSTRING() { Init(); } CSTRING::CSTRING(const CSTRING& stringSrc) { ASSERT(stringSrc.GetData()->nRefs != 0); if (stringSrc.GetData()->nRefs >= 0) { ASSERT(stringSrc.GetData() != AFXDataNil); m_pchData = stringSrc.m_pchData; InterlockedIncrement(&GetData()->nRefs); } else { Init(); *this = stringSrc.m_pchData; } } CSTRING::CSTRING(LPCTSTR lpch, int nLength) { Init(); if (nLength != 0) { // ASSERT(AfxIsValidAddress(lpch, nLength, FALSE)); AllocBuffer(nLength); memcpy(m_pchData, lpch, nLength*sizeof(TCHAR)); } } void CSTRING::AllocBuffer(int nLen) // always allocate one extra character for '\0' termination // assumes [optimistically] that data length will equal allocation length { ASSERT(nLen >= 0); ASSERT(nLen <= INT_MAX-1); // max size (enough room for 1 extra) if (nLen == 0) Init(); else { CSTRINGData* pData = (CSTRINGData*)new BYTE[sizeof(CSTRINGData) + (nLen+1)*sizeof(TCHAR)]; pData->nRefs = 1; pData->data()[nLen] = '\0'; pData->nDataLength = nLen; pData->nAllocLength = nLen; m_pchData = pData->data(); } } void CSTRING::Release() { if (GetData() != AFXDataNil) { ASSERT(GetData()->nRefs != 0); if (InterlockedDecrement(&GetData()->nRefs) <= 0) delete[] (BYTE*)GetData(); Init(); } } void PASCAL CSTRING::Release(CSTRINGData* pData) { if (pData != AFXDataNil) { ASSERT(pData->nRefs != 0); if (InterlockedDecrement(&pData->nRefs) <= 0) delete[] (BYTE*)pData; } } void CSTRING::Empty() { if (GetData()->nRefs >= 0) Release(); else *this = &AFXChNil; ASSERT(GetData()->nDataLength == 0); ASSERT(GetData()->nRefs < 0 || GetData()->nAllocLength == 0); } void CSTRING::CopyBeforeWrite() { if (GetData()->nRefs > 1) { CSTRINGData* pData = GetData(); Release(); AllocBuffer(pData->nDataLength); memcpy(m_pchData, pData->data(), (pData->nDataLength+1)*sizeof(TCHAR)); } ASSERT(GetData()->nRefs <= 1); } void CSTRING::AllocBeforeWrite(int nLen) { if (GetData()->nRefs > 1 || nLen > GetData()->nAllocLength) { Release(); AllocBuffer(nLen); } ASSERT(GetData()->nRefs <= 1); } CSTRING::~CSTRING() // free any attached data { if (GetData() != AFXDataNil) { if (InterlockedDecrement(&GetData()->nRefs) <= 0) delete[] (BYTE*)GetData(); } } ////////////////////////////////////////////////////////////////////////////// // Helpers for the rest of the implementation void CSTRING::AllocCopy(CSTRING& dest, int nCopyLen, int nCopyIndex, int nExtraLen) const { // will clone the data attached to this string // allocating 'nExtraLen' characters // Places results in uninitialized string 'dest' // Will copy the part or all of original data to start of new string int nNewLen = nCopyLen + nExtraLen; if (nNewLen == 0) { dest.Init(); } else { dest.AllocBuffer(nNewLen); memcpy(dest.m_pchData, m_pchData+nCopyIndex, nCopyLen*sizeof(TCHAR)); } } ////////////////////////////////////////////////////////////////////////////// // More sophisticated construction CSTRING::CSTRING(LPCTSTR lpsz) { Init(); // if (lpsz != NULL && HIWORD(lpsz) == NULL) // { // UINT nID = LOWORD((DWORD)lpsz); // if (!LoadString(nID)) { // ;// TRACE1("Warning: implicit LoadString(%u) failed\n", nID); // } // } // else { int nLen = SafeStrlen(lpsz); if (nLen != 0) { AllocBuffer(nLen); memcpy(m_pchData, lpsz, nLen*sizeof(TCHAR)); } } } ///////////////////////////////////////////////////////////////////////////// // Special conversion constructors #ifdef _UNICODE CSTRING::CSTRING(LPCSTR lpsz) { Init(); int nSrcLen = lpsz != NULL ? lstrlenA(lpsz) : 0; if (nSrcLen != 0) { AllocBuffer(nSrcLen); _mbstowcsz(m_pchData, lpsz, nSrcLen+1); ReleaseBuffer(); } } #else //_UNICODE CSTRING::CSTRING(LPCWSTR lpsz) { Init(); int nSrcLen = lpsz != NULL ? LStrLenW(lpsz) : 0; if (nSrcLen != 0) { AllocBuffer(nSrcLen*2); _wcstombsz(m_pchData, lpsz, (nSrcLen*2)+1); ReleaseBuffer(); } } #endif //!_UNICODE ////////////////////////////////////////////////////////////////////////////// // Diagnostic support //#ifdef _DEBUG //CDumpContext& REMAFXAPI operator<<(CDumpContext& dc, const CSTRING& string) //{ // dc << string.m_pchData; // return dc; //} //#endif //_DEBUG ////////////////////////////////////////////////////////////////////////////// // Assignment operators // All assign a new value to the string // (a) first see if the buffer is big enough // (b) if enough room, copy on top of old buffer, set size and type // (c) otherwise free old string data, and create a new one // // All routines return the new string (but as a 'const CSTRING&') so that // assigning it again will cause a copy, eg: s1 = s2 = "hi there". // void CSTRING::AssignCopy(int nSrcLen, LPCTSTR lpszSrcData) { AllocBeforeWrite(nSrcLen); memcpy(m_pchData, lpszSrcData, nSrcLen*sizeof(TCHAR)); GetData()->nDataLength = nSrcLen; m_pchData[nSrcLen] = '\0'; } const CSTRING& CSTRING::operator=(const CSTRING& stringSrc) { if (m_pchData != stringSrc.m_pchData) { if ((GetData()->nRefs < 0 && GetData() != AFXDataNil) || stringSrc.GetData()->nRefs < 0) { // actual copy necessary since one of the strings is locked AssignCopy(stringSrc.GetData()->nDataLength, stringSrc.m_pchData); } else { // can just copy references around Release(); ASSERT(stringSrc.GetData() != AFXDataNil); m_pchData = stringSrc.m_pchData; InterlockedIncrement(&GetData()->nRefs); } } return *this; } const CSTRING& CSTRING::operator=(LPCTSTR lpsz) { //lts ASSERT(lpsz == NULL || AfxIsValidString(lpsz, FALSE)); AssignCopy(SafeStrlen(lpsz), lpsz); return *this; } ///////////////////////////////////////////////////////////////////////////// // Special conversion assignment #ifdef _UNICODE const CSTRING& CSTRING::operator=(LPCSTR lpsz) { int nSrcLen = lpsz != NULL ? lstrlenA(lpsz) : 0; AllocBeforeWrite(nSrcLen); _mbstowcsz(m_pchData, lpsz, nSrcLen+1); ReleaseBuffer(); return *this; } #else //!_UNICODE const CSTRING& CSTRING::operator=(LPCWSTR lpsz) { int nSrcLen = lpsz != NULL ? LStrLenW(lpsz) : 0; AllocBeforeWrite(nSrcLen*2); _wcstombsz(m_pchData, lpsz, (nSrcLen*2)+1); ReleaseBuffer(); return *this; } #endif //!_UNICODE ////////////////////////////////////////////////////////////////////////////// // concatenation // NOTE: "operator+" is done as friend functions for simplicity // There are three variants: // CSTRING + CSTRING // and for ? = TCHAR, LPCTSTR // CSTRING + ? // ? + CSTRING void CSTRING::ConcatCopy(int nSrc1Len, LPCTSTR lpszSrc1Data, int nSrc2Len, LPCTSTR lpszSrc2Data) { // -- master concatenation routine // Concatenate two sources // -- assume that 'this' is a new CSTRING object int nNewLen = nSrc1Len + nSrc2Len; if (nNewLen != 0) { AllocBuffer(nNewLen); memcpy(m_pchData, lpszSrc1Data, nSrc1Len*sizeof(TCHAR)); memcpy(m_pchData+nSrc1Len, lpszSrc2Data, nSrc2Len*sizeof(TCHAR)); } } CSTRING REMAFXAPI operator+(const CSTRING& string1, const CSTRING& string2) { CSTRING s; s.ConcatCopy(string1.GetData()->nDataLength, string1.m_pchData, string2.GetData()->nDataLength, string2.m_pchData); return s; } CSTRING REMAFXAPI operator+(const CSTRING& string, LPCTSTR lpsz) { // ASSERT(lpsz == NULL || AfxIsValidString(lpsz, FALSE)); CSTRING s; s.ConcatCopy(string.GetData()->nDataLength, string.m_pchData, CSTRING::SafeStrlen(lpsz), lpsz); return s; } CSTRING REMAFXAPI operator+(LPCTSTR lpsz, const CSTRING& string) { // ASSERT(lpsz == NULL || AfxIsValidString(lpsz, FALSE)); CSTRING s; s.ConcatCopy(CSTRING::SafeStrlen(lpsz), lpsz, string.GetData()->nDataLength, string.m_pchData); return s; } ////////////////////////////////////////////////////////////////////////////// // concatenate in place void CSTRING::ConcatInPlace(int nSrcLen, LPCTSTR lpszSrcData) { // -- the main routine for += operators // if the buffer is too small, or we have a width mis-match, just // allocate a new buffer (slow but sure) if (GetData()->nRefs > 1 || GetData()->nDataLength + nSrcLen > GetData()->nAllocLength) { // we have to grow the buffer, use the ConcatCopy routine CSTRINGData* pOldData = GetData(); ConcatCopy(GetData()->nDataLength, m_pchData, nSrcLen, lpszSrcData); ASSERT(pOldData != NULL); CSTRING::Release(pOldData); } else { // fast concatenation when buffer big enough memcpy(m_pchData+GetData()->nDataLength, lpszSrcData, nSrcLen*sizeof(TCHAR)); GetData()->nDataLength += nSrcLen; ASSERT(GetData()->nDataLength <= GetData()->nAllocLength); m_pchData[GetData()->nDataLength] = '\0'; } } const CSTRING& CSTRING::operator+=(LPCTSTR lpsz) { // ASSERT(lpsz == NULL || AfxIsValidString(lpsz, FALSE)); ConcatInPlace(SafeStrlen(lpsz), lpsz); return *this; } const CSTRING& CSTRING::operator+=(TCHAR ch) { ConcatInPlace(1, &ch); return *this; } const CSTRING& CSTRING::operator+=(const CSTRING& string) { ConcatInPlace(string.GetData()->nDataLength, string.m_pchData); return *this; } /* * Length-sensitive comparison * * NOTE: FEqual returns TRUE if the 2 CSTRINGS have the same length and contain * the same characters, and FALSE, otherwise. */ BOOL CSTRING::FEqual (const CSTRING &s2) const { int length; // Compare the lengths first length = GetData()->nDataLength; if (length != s2.GetData()->nDataLength) return FALSE; #ifdef _UNICODE // adjust the length in bytes length *= sizeof (TCHAR); #endif /* * Now, compare the strings themselves * We use memcmp and not lstrcmp because the stings may * have embedded null characters. */ if (memcmp ((const void *) m_pchData, (const void *) s2.m_pchData, length)) return FALSE; else return TRUE; } /////////////////////////////////////////////////////////////////////////////// // Advanced direct buffer access LPTSTR CSTRING::GetBuffer(int nMinBufLength) { ASSERT(nMinBufLength >= 0); if (GetData()->nRefs > 1 || nMinBufLength > GetData()->nAllocLength) { // we have to grow the buffer CSTRINGData* pOldData = GetData(); int nOldLen = GetData()->nDataLength; // AllocBuffer will tromp it if (nMinBufLength < nOldLen) nMinBufLength = nOldLen; AllocBuffer(nMinBufLength); memcpy(m_pchData, pOldData->data(), (nOldLen+1)*sizeof(TCHAR)); GetData()->nDataLength = nOldLen; CSTRING::Release(pOldData); } ASSERT(GetData()->nRefs <= 1); // return a pointer to the character storage for this string ASSERT(m_pchData != NULL); return m_pchData; } void CSTRING::ReleaseBuffer(int nNewLength) { CopyBeforeWrite(); // just in case GetBuffer was not called if (nNewLength == -1) nNewLength = lstrlen(m_pchData); // zero terminated ASSERT(nNewLength <= GetData()->nAllocLength); GetData()->nDataLength = nNewLength; m_pchData[nNewLength] = '\0'; } LPTSTR CSTRING::GetBufferSetLength(int nNewLength) { ASSERT(nNewLength >= 0); GetBuffer(nNewLength); GetData()->nDataLength = nNewLength; m_pchData[nNewLength] = '\0'; return m_pchData; } void CSTRING::FreeExtra() { ASSERT(GetData()->nDataLength <= GetData()->nAllocLength); if (GetData()->nDataLength != GetData()->nAllocLength) { CSTRINGData* pOldData = GetData(); AllocBuffer(GetData()->nDataLength); memcpy(m_pchData, pOldData->data(), pOldData->nDataLength*sizeof(TCHAR)); ASSERT(m_pchData[GetData()->nDataLength] == '\0'); CSTRING::Release(pOldData); } ASSERT(GetData() != NULL); } LPTSTR CSTRING::LockBuffer() { LPTSTR lpsz = GetBuffer(0); GetData()->nRefs = -1; return lpsz; } void CSTRING::UnlockBuffer() { ASSERT(GetData()->nRefs == -1); if (GetData() != AFXDataNil) GetData()->nRefs = 1; } /////////////////////////////////////////////////////////////////////////////// // Commonly used routines (rarely used routines in STREX.CPP) // find position of the first character match (or -1 on failure) int CSTRING::Find(TCHAR ch) const { for (TCHAR * pch = m_pchData; _T('\0') != *pch; pch = CharNext(pch)) { if (ch == *pch) return ((int)(pch - m_pchData) / sizeof(TCHAR)); } return -1; } CSTRING CSTRING::Left(int nCount) const { if (nCount < 0) nCount = 0; else if (nCount > GetData()->nDataLength) nCount = GetData()->nDataLength; CSTRING dest; AllocCopy(dest, nCount, 0, 0); return dest; } CSTRING CSTRING::Mid(int nFirst) const { return Mid(nFirst, GetData()->nDataLength - nFirst); } CSTRING CSTRING::Mid(int nFirst, int nCount) const { // out-of-bounds requests return sensible things if (nFirst < 0) nFirst = 0; if (nCount < 0) nCount = 0; if (nFirst + nCount > GetData()->nDataLength) nCount = GetData()->nDataLength - nFirst; if (nFirst > GetData()->nDataLength) nCount = 0; CSTRING dest; AllocCopy(dest, nCount, nFirst, 0); return dest; } void CSTRING::MakeUpper() { CopyBeforeWrite(); ::CharUpper(m_pchData); } void CSTRING::MakeLower() { CopyBeforeWrite(); ::CharLower(m_pchData); } void CSTRING::SetAt(int nIndex, TCHAR ch) { ASSERT(nIndex >= 0); ASSERT(nIndex < GetData()->nDataLength); CopyBeforeWrite(); m_pchData[nIndex] = ch; } #ifndef _UNICODE void CSTRING::AnsiToOem() { CopyBeforeWrite(); ::AnsiToOem(m_pchData, m_pchData); } #endif /////////////////////////////////////////////////////////////////////////////// // CSTRING conversion helpers (these use the current system locale) int REMAFX_CDECL _wcstombsz(char* mbstr, const wchar_t* wcstr, size_t count) { if (count == 0 && mbstr != NULL) return 0; int result = ::WideCharToMultiByte(CP_ACP, 0, wcstr, -1, mbstr, count, NULL, NULL); ASSERT(mbstr == NULL || result <= (int)count); if (result > 0) mbstr[result-1] = 0; return result; } int REMAFX_CDECL _mbstowcsz(wchar_t* wcstr, const char* mbstr, size_t count) { if (count == 0 && wcstr != NULL) return 0; int result = ::MultiByteToWideChar(CP_ACP, 0, mbstr, -1, wcstr, count); ASSERT(wcstr == NULL || result <= (int)count); if (result > 0) wcstr[result-1] = 0; return result; } LPWSTR REMAFXAPI AfxA2WHelper(LPWSTR lpw, LPCSTR lpa, int nChars) { if (lpa == NULL) return NULL; ASSERT(lpw != NULL); // verify that no illegal character present // since lpw was allocated based on the size of lpa // don't worry about the number of chars lpw[0] = '\0'; //lts VERIFY(MultiByteToWideChar(CP_ACP, 0, lpa, -1, lpw, nChars)); MultiByteToWideChar(CP_ACP, 0, lpa, -1, lpw, nChars); return lpw; } LPSTR REMAFXAPI AfxW2AHelper(LPSTR lpa, LPCWSTR lpw, int nChars) { if (lpw == NULL) return NULL; ASSERT(lpa != NULL); // verify that no illegal character present // since lpa was allocated based on the size of lpw // don't worry about the number of chars lpa[0] = '\0'; //lts VERIFY(WideCharToMultiByte(CP_ACP, 0, lpw, -1, lpa, nChars, NULL, NULL)); WideCharToMultiByte(CP_ACP, 0, lpw, -1, lpa, nChars, NULL, NULL); return lpa; } /////////////////////////////////////////////////////////////////////////////// BOOL CSTRING::LoadString(HINSTANCE hInstance, UINT nID) { // try buffer size of 256, then larger size until entire string is retrieved int nSize = -1; int nLen; do { nSize += 256; nLen = ::LoadString(hInstance, nID, GetBuffer(nSize), nSize+1); } while (nLen == nSize); ReleaseBuffer(); return nLen > 0; }