// This is a part of the Microsoft Foundation Classes C++ library. // Copyright (C) 1992 Microsoft Corporation // All rights reserved. // // This source code is only intended as a supplement to the // Microsoft Foundation Classes Reference and Microsoft // QuickHelp and/or WinHelp documentation provided with the library. // See these sources for detailed information regarding the // Microsoft Foundation Classes product. #include #include #include #include "dbg.h" #include "cstr.h" ///////////////////////////////////////////////////////////////////////////// // static class data, special inlines // For an empty string, m_???Data will point here // (note: avoids a lot of NULL pointer tests when we call standard // C runtime libraries TCHAR strChNil = '\0'; // for creating empty key strings const CStr strEmptyString; void CStr::Init() { m_nDataLength = m_nAllocLength = 0; m_pchData = (LPTSTR)&strChNil; } // declared static void CStr::SafeDelete(LPTSTR lpch) { // ISSUE-2002/03/29-JonN also check for NULL? Or is "delete[] NULL" OK? if (lpch != (LPTSTR)&strChNil) delete[] lpch; } ////////////////////////////////////////////////////////////////////////////// // Construction/Destruction CStr::CStr() { Init(); } CStr::CStr(const CStr& stringSrc) { // if constructing a String from another String, we make a copy of the // original string data to enforce value semantics (i.e. each string // gets a copy of its own stringSrc.AllocCopy(*this, stringSrc.m_nDataLength, 0, 0); } void CStr::AllocBuffer(size_t nLen) // always allocate one extra character for '\0' termination // assumes [optimistically] that data length will equal allocation length { if (nLen == 0) { Init(); } else { m_pchData = new TCHAR[nLen+1]; //REVIEW may throw an exception m_pchData[nLen] = '\0'; m_nDataLength = nLen; m_nAllocLength = nLen; } } void CStr::Empty() { SafeDelete(m_pchData); Init(); ASSERT(m_nDataLength == 0); ASSERT(m_nAllocLength == 0); } CStr::~CStr() // free any attached data { SafeDelete(m_pchData); } ////////////////////////////////////////////////////////////////////////////// // Helpers for the rest of the implementation static inline size_t SafeStrlen(LPCTSTR lpsz) { ASSERT(lpsz == NULL || IsValidString(lpsz, FALSE)); return (lpsz == NULL) ? 0 : lstrlen(lpsz); } void CStr::AllocCopy(CStr& dest, size_t nCopyLen, size_t nCopyIndex, size_t 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 size_t nNewLen = nCopyLen + nExtraLen; if (nNewLen == 0) { dest.Init(); } else { dest.AllocBuffer(nNewLen); // ISSUE-2002/03/29-JonN should check against m_nDataLength memcpy(dest.m_pchData, &m_pchData[nCopyIndex], nCopyLen*sizeof(TCHAR)); } } ////////////////////////////////////////////////////////////////////////////// // More sophisticated construction CStr::CStr(LPCTSTR lpsz) { size_t nLen; if ((nLen = SafeStrlen(lpsz)) == 0) Init(); else { AllocBuffer(nLen); memcpy(m_pchData, lpsz, nLen*sizeof(TCHAR)); } } ///////////////////////////////////////////////////////////////////////////// // Special conversion constructors #ifdef UNICODE CStr::CStr(LPCSTR lpsz) { size_t nSrcLen = lpsz != NULL ? lstrlenA(lpsz) : 0; if (nSrcLen == 0) Init(); else { AllocBuffer(nSrcLen); mmc_mbstowcsz(m_pchData, lpsz, nSrcLen+1); } } #else //UNICODE CStr::CStr(LPCWSTR lpsz) { size_t nSrcLen = lpsz != NULL ? wcslen(lpsz) : 0; if (nSrcLen == 0) Init(); else { AllocBuffer(nSrcLen*2); mmc_wcstombsz(m_pchData, lpsz, (nSrcLen*2)+1); ReleaseBuffer(); } } #endif //!UNICODE ////////////////////////////////////////////////////////////////////////////// // 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 CStr&' so that // assigning it again will cause a copy, eg: s1 = s2 = "hi there". // void CStr::AssignCopy(size_t nSrcLen, LPCTSTR lpszSrcData) { // check if it will fit if (nSrcLen > m_nAllocLength) { // it won't fit, allocate another one Empty(); AllocBuffer(nSrcLen); } if (nSrcLen != 0) memcpy(m_pchData, lpszSrcData, nSrcLen*sizeof(TCHAR)); m_nDataLength = nSrcLen; m_pchData[nSrcLen] = '\0'; } const CStr& CStr::operator=(const CStr& stringSrc) { AssignCopy(stringSrc.m_nDataLength, stringSrc.m_pchData); return *this; } const CStr& CStr::operator=(LPCTSTR lpsz) { ASSERT(lpsz == NULL || IsValidString(lpsz, FALSE)); AssignCopy(SafeStrlen(lpsz), lpsz); return *this; } ///////////////////////////////////////////////////////////////////////////// // Special conversion assignment #ifdef UNICODE const CStr& CStr::operator=(LPCSTR lpsz) { size_t nSrcLen = lpsz != NULL ? lstrlenA(lpsz) : 0; // check if it will fit if (nSrcLen > m_nAllocLength) { // it won't fit, allocate another one Empty(); AllocBuffer(nSrcLen); } if (nSrcLen != 0) mmc_mbstowcsz(m_pchData, lpsz, nSrcLen+1); // ISSUE-2002/03/29-JonN "m_nDataLength = nSrcLen" seems wrong m_nDataLength = nSrcLen; m_pchData[nSrcLen] = '\0'; return *this; } #else //!UNICODE const CStr& CStr::operator=(LPCWSTR lpsz) { size_t nSrcLen = lpsz != NULL ? wcslen(lpsz) : 0; nSrcLen *= 2; // check if it will fit if (nSrcLen > m_nAllocLength) { // it won't fit, allocate another one Empty(); AllocBuffer(nSrcLen); } if (nSrcLen != 0) { mmc_wcstombsz(m_pchData, lpsz, nSrcLen+1); ReleaseBuffer(); } return *this; } #endif //!UNICODE ////////////////////////////////////////////////////////////////////////////// // concatenation // NOTE: "operator+" is done as friend functions for simplicity // There are three variants: // String + String // and for ? = TCHAR, LPCTSTR // String + ? // ? + String void CStr::ConcatCopy(size_t nSrc1Len, LPCTSTR lpszSrc1Data, size_t nSrc2Len, LPCTSTR lpszSrc2Data) { // -- master concatenation routine // Concatenate two sources // -- assume that 'this' is a new String object size_t nNewLen = nSrc1Len + nSrc2Len; AllocBuffer(nNewLen); memcpy(m_pchData, lpszSrc1Data, nSrc1Len*sizeof(TCHAR)); memcpy(&m_pchData[nSrc1Len], lpszSrc2Data, nSrc2Len*sizeof(TCHAR)); } CStr STRAPI operator+(const CStr& string1, const CStr& string2) { CStr s; s.ConcatCopy(string1.m_nDataLength, string1.m_pchData, string2.m_nDataLength, string2.m_pchData); return s; } CStr STRAPI operator+(const CStr& string, LPCTSTR lpsz) { ASSERT(lpsz == NULL || IsValidString(lpsz, FALSE)); CStr s; s.ConcatCopy(string.m_nDataLength, string.m_pchData, SafeStrlen(lpsz), lpsz); return s; } CStr STRAPI operator+(LPCTSTR lpsz, const CStr& string) { ASSERT(lpsz == NULL || IsValidString(lpsz, FALSE)); CStr s; s.ConcatCopy(SafeStrlen(lpsz), lpsz, string.m_nDataLength, string.m_pchData); return s; } ////////////////////////////////////////////////////////////////////////////// // concatenate in place void CStr::ConcatInPlace(size_t 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 (m_nDataLength + nSrcLen > m_nAllocLength) { // we have to grow the buffer, use the Concat in place routine LPTSTR lpszOldData = m_pchData; ConcatCopy(m_nDataLength, lpszOldData, nSrcLen, lpszSrcData); ASSERT(lpszOldData != NULL); SafeDelete(lpszOldData); } else { // fast concatenation when buffer big enough memcpy(&m_pchData[m_nDataLength], lpszSrcData, nSrcLen*sizeof(TCHAR)); m_nDataLength += nSrcLen; } ASSERT(m_nDataLength <= m_nAllocLength); m_pchData[m_nDataLength] = '\0'; } const CStr& CStr::operator+=(LPCTSTR lpsz) { ASSERT(lpsz == NULL || IsValidString(lpsz, FALSE)); ConcatInPlace(SafeStrlen(lpsz), lpsz); return *this; } const CStr& CStr::operator+=(TCHAR ch) { ConcatInPlace(1, &ch); return *this; } const CStr& CStr::operator+=(const CStr& string) { ConcatInPlace(string.m_nDataLength, string.m_pchData); return *this; } /////////////////////////////////////////////////////////////////////////////// // Advanced direct buffer access LPTSTR CStr::GetBuffer(size_t nMinBufLength) { if (nMinBufLength > m_nAllocLength) { // we have to grow the buffer LPTSTR lpszOldData = m_pchData; size_t nOldLen = m_nDataLength; // AllocBuffer will tromp it AllocBuffer(nMinBufLength); memcpy(m_pchData, lpszOldData, nOldLen*sizeof(TCHAR)); m_nDataLength = nOldLen; m_pchData[m_nDataLength] = '\0'; SafeDelete(lpszOldData); } // return a pointer to the character storage for this string ASSERT(m_pchData != NULL); return m_pchData; } void CStr::ReleaseBuffer(size_t nNewLength) { if (nNewLength == -1) nNewLength = lstrlen(m_pchData); // zero terminated // ISSUE-2002/03/29-JonN handle this case ASSERT(nNewLength <= m_nAllocLength); m_nDataLength = nNewLength; m_pchData[m_nDataLength] = '\0'; } LPTSTR CStr::GetBufferSetLength(int nNewLength) { // ISSUE-2002/03/29-JonN handle this case ASSERT(nNewLength >= 0); GetBuffer(nNewLength); m_nDataLength = nNewLength; m_pchData[m_nDataLength] = '\0'; return m_pchData; } void CStr::FreeExtra() { ASSERT(m_nDataLength <= m_nAllocLength); if (m_nDataLength != m_nAllocLength) { LPTSTR lpszOldData = m_pchData; AllocBuffer(m_nDataLength); memcpy(m_pchData, lpszOldData, m_nDataLength*sizeof(TCHAR)); ASSERT(m_pchData[m_nDataLength] == '\0'); SafeDelete(lpszOldData); } ASSERT(m_pchData != NULL); } /////////////////////////////////////////////////////////////////////////////// // Commonly used routines (rarely used routines in STREX.CPP) int CStr::Find(TCHAR ch) const { // find first single character LPTSTR lpsz = _tcschr(m_pchData, ch); // return -1 if not found and index otherwise return (lpsz == NULL) ? -1 : (int)(lpsz - m_pchData); } int CStr::FindOneOf(LPCTSTR lpszCharSet) const { ASSERT(IsValidString(lpszCharSet, FALSE)); LPTSTR lpsz = _tcspbrk(m_pchData, lpszCharSet); return (lpsz == NULL) ? -1 : (int)(lpsz - m_pchData); } /////////////////////////////////////////////////////////////////////////////// // String conversion helpers (these use the current system locale) size_t mmc_wcstombsz(char* mbstr, const wchar_t* wcstr, size_t count) { if (count == 0 && mbstr != NULL) return 0; size_t result = ::WideCharToMultiByte(CP_ACP, 0, wcstr, -1, mbstr, (int) count, NULL, NULL); ASSERT(mbstr == NULL || result <= count); if (result > 0) mbstr[result-1] = 0; return result; } size_t mmc_mbstowcsz(wchar_t* wcstr, const char* mbstr, size_t count) { if (count == 0 && wcstr != NULL) return 0; size_t result = ::MultiByteToWideChar(CP_ACP, 0, mbstr, -1, wcstr, (int) count); ASSERT(wcstr == NULL || result <= count); if (result > 0) wcstr[result-1] = 0; return result; } ///////////////////////////////////////////////////////////////////////////// // Windows extensions to strings BOOL CStr::LoadString(HINSTANCE hInst, UINT nID) { ASSERT(nID != 0); // 0 is an illegal string ID // Note: resource strings limited to 511 characters TCHAR szBuffer[512]; UINT nSize = StrLoadString(hInst, nID, szBuffer); AssignCopy(nSize, szBuffer); return nSize > 0; } // ISSUE-2002/04/01-JonN should take cch parameter int STRAPI StrLoadString(HINSTANCE hInst, UINT nID, LPTSTR lpszBuf) { // ISSUE-2002/04/01-JonN should test lpszBuf==NULL in retail ASSERT(IsValidAddressz(lpszBuf, 512)); // must be big enough for 512 bytes #ifdef DBG // LoadString without annoying warning from the Debug kernel if the // segment containing the string is not present if (::FindResource(hInst, MAKEINTRESOURCE((nID>>4)+1), RT_STRING) == NULL) { lpszBuf[0] = '\0'; return 0; // not found } #endif //DBG int nLen = ::LoadString(hInst, nID, lpszBuf, 511); if (nLen == 0) lpszBuf[0] = '\0'; return nLen; } BOOL STRAPI IsValidAddressz(const void* lp, UINT nBytes, BOOL bReadWrite) { // simple version using Win-32 APIs for pointer validation. return (lp != NULL && !IsBadReadPtr(lp, nBytes) && (!bReadWrite || !IsBadWritePtr((LPVOID)lp, nBytes))); } BOOL STRAPI IsValidString(LPCSTR lpsz, UINT_PTR nLength) { if (lpsz == NULL) return FALSE; return ::IsBadStringPtrA(lpsz, nLength) == 0; } BOOL STRAPI IsValidString(LPCWSTR lpsz, UINT_PTR nLength) { if (lpsz == NULL) return FALSE; return ::IsBadStringPtrW(lpsz, nLength) == 0; } #ifdef OLE_AUTOMATION #ifdef UNICODE BSTR CStr::AllocSysString() { BSTR bstr = ::SysAllocStringLen(m_pchData, m_nDataLength); if (bstr == NULL) ;//REVIEW AfxThrowMemoryException(); return bstr; } BSTR CStr::SetSysString(BSTR* pbstr) { ASSERT(IsValidAddressz(pbstr, sizeof(BSTR))); if (!::SysReAllocStringLen(pbstr, m_pchData, m_nDataLength)) ; //REVIEW AfxThrowMemoryException(); ASSERT(*pbstr != NULL); return *pbstr; } #endif #endif // #ifdef OLE_AUTOMATION /////////////////////////////////////////////////////////////////////////////// // Orginally from StrEx.cpp CStr::CStr(TCHAR ch, int nLength) { #ifndef UNICODE // ISSUE-2002/04/01-JonN This is wrong, we just shouldn't support this ASSERT(!IsDBCSLeadByte(ch)); // can't create a lead byte string #endif if (nLength < 1) { // return empty string if invalid repeat count Init(); } else { AllocBuffer(nLength); #ifdef UNICODE for (int i = 0; i < nLength; i++) m_pchData[i] = ch; #else memset(m_pchData, ch, nLength); #endif } } CStr::CStr(LPCTSTR lpch, int nLength) { if (nLength == 0) Init(); else { ASSERT(IsValidAddressz(lpch, nLength, FALSE)); AllocBuffer(nLength); memcpy(m_pchData, lpch, nLength*sizeof(TCHAR)); } } ////////////////////////////////////////////////////////////////////////////// // Assignment operators const CStr& CStr::operator=(TCHAR ch) { #ifndef UNICODE ASSERT(!IsDBCSLeadByte(ch)); // can't set single lead byte #endif AssignCopy(1, &ch); return *this; } ////////////////////////////////////////////////////////////////////////////// // less common string expressions CStr STRAPI operator+(const CStr& string1, TCHAR ch) { CStr s; s.ConcatCopy(string1.m_nDataLength, string1.m_pchData, 1, &ch); return s; } CStr STRAPI operator+(TCHAR ch, const CStr& string) { CStr s; s.ConcatCopy(1, &ch, string.m_nDataLength, string.m_pchData); return s; } ////////////////////////////////////////////////////////////////////////////// // Very simple sub-string extraction CStr CStr::Mid(size_t nFirst) const { return Mid(nFirst, m_nDataLength - nFirst); } CStr CStr::Mid(size_t nFirst, size_t nCount) const { // out-of-bounds requests return sensible things if (nFirst + nCount > m_nDataLength) nCount = m_nDataLength - nFirst; if (nFirst > m_nDataLength) nCount = 0; CStr dest; AllocCopy(dest, nCount, nFirst, 0); return dest; } CStr CStr::Right(size_t nCount) const { if (nCount > m_nDataLength) nCount = m_nDataLength; CStr dest; AllocCopy(dest, nCount, m_nDataLength-nCount, 0); return dest; } CStr CStr::Left(size_t nCount) const { if (nCount > m_nDataLength) nCount = m_nDataLength; CStr dest; AllocCopy(dest, nCount, 0, 0); return dest; } // strspn equivalent CStr CStr::SpanIncluding(LPCTSTR lpszCharSet) const { ASSERT(IsValidString(lpszCharSet, FALSE)); return Left(_tcsspn(m_pchData, lpszCharSet)); } // strcspn equivalent CStr CStr::SpanExcluding(LPCTSTR lpszCharSet) const { ASSERT(IsValidString(lpszCharSet, FALSE)); return Left(_tcscspn(m_pchData, lpszCharSet)); } ////////////////////////////////////////////////////////////////////////////// // Finding int CStr::ReverseFind(TCHAR ch) const { // find last single character LPTSTR lpsz = _tcsrchr(m_pchData, ch); // return -1 if not found, distance from beginning otherwise return (lpsz == NULL) ? -1 : (int)(lpsz - m_pchData); } // find a sub-string (like strstr) int CStr::Find(LPCTSTR lpszSub) const { ASSERT(IsValidString(lpszSub, FALSE)); // find first matching substring LPTSTR lpsz = _tcsstr(m_pchData, lpszSub); // return -1 for not found, distance from beginning otherwise return (lpsz == NULL) ? -1 : (int)(lpsz - m_pchData); } ///////////////////////////////////////////////////////////////////////////// // String formatting #define FORCE_ANSI 0x10000 #define FORCE_UNICODE 0x20000 void CStr::TrimRight() { // find beginning of trailing spaces by starting at beginning (DBCS aware) LPTSTR lpsz = m_pchData; LPTSTR lpszLast = NULL; while (*lpsz != '\0') { if (_istspace(*lpsz)) { if (lpszLast == NULL) lpszLast = lpsz; } else lpszLast = NULL; lpsz = _tcsinc(lpsz); } if (lpszLast != NULL) { // truncate at trailing space start *lpszLast = '\0'; m_nDataLength = (int)(lpszLast - m_pchData); } } void CStr::TrimLeft() { // find first non-space character LPCTSTR lpsz = m_pchData; while (_istspace(*lpsz)) lpsz = _tcsinc(lpsz); // fix up data and length size_t nDataLength = m_nDataLength - (int)(lpsz - m_pchData); memmove(m_pchData, lpsz, (nDataLength+1)*sizeof(TCHAR)); m_nDataLength = nDataLength; } #if 0 /////////////////////////////////////////////////////////////////////////////// // String support for template collections template<> void STRAPI ConstructElements(CStr* pElements, int nCount) { ASSERT(IsValidAddressz(pElements, nCount * sizeof(CStr))); for (; nCount--; ++pElements) memcpy(pElements, &strEmptyString, sizeof(*pElements)); } template<> void STRAPI DestructElements(CStr* pElements, int nCount) { ASSERT(IsValidAddressz(pElements, nCount * sizeof(CStr))); for (; nCount--; ++pElements) pElements->Empty(); } template<> UINT STRAPI HashKey(LPCTSTR key) { UINT nHash = 0; while (*key) nHash = (nHash<<5) + nHash + *key++; return nHash; } #endif