947 lines
24 KiB
C++
947 lines
24 KiB
C++
//
|
|
// MODULE: APGTSSTR.CPP
|
|
//
|
|
// PURPOSE: implements DLL Growable string object CString
|
|
// (pretty much a la MFC, but avoids all that MFC overhead)
|
|
//
|
|
// PROJECT: Generic Troubleshooter DLL for Microsoft AnswerPoint
|
|
//
|
|
// COMPANY: Saltmine Creative, Inc. (206)-284-7511 support@saltmine.com
|
|
//
|
|
// AUTHOR: Joe Mabel (reworked code from Microsoft's MFC sources)
|
|
//
|
|
// ORIGINAL DATE: 8-2-96 Roman Mach; totally re-implemented 1/15/99 Joe Mabel
|
|
//
|
|
// NOTES:
|
|
// 1. As of 1/99, re-implemented based on MFC's implementation. Pared down
|
|
// to what we use.
|
|
// 2. This file modified 5/26/01 by Davide Massarenti from MS to remove reference counting
|
|
// from CString implementation to resolve thread safety issue discovered on dual processor
|
|
// systems when dll compiled for WinXP by MS compiler.
|
|
//
|
|
// Version Date By Comments
|
|
//--------------------------------------------------------------------
|
|
// V0.1 - RM Original
|
|
// V3.0 7-24-98 JM Abstracted this out as a separate header.
|
|
// V3.1 1-15-99 JM Redo based on MFC implementation
|
|
//
|
|
|
|
#include "stdafx.h"
|
|
#include <stdio.h>
|
|
#include "apgtsstr.h"
|
|
#include "apgtsmfc.h"
|
|
|
|
// Windows extensions to strings
|
|
#ifdef _UNICODE
|
|
#define CHAR_FUDGE 1 // one TCHAR unused is good enough
|
|
#else
|
|
#define CHAR_FUDGE 2 // two BYTES unused for case of DBC last char
|
|
#endif
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// static class data, special inlines
|
|
|
|
// afxChNil is left for backward compatibility
|
|
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)
|
|
// [BC - 20010529] - change below array from int to long and add extra 0 for padding
|
|
// to avoid problems indicated by Davide Massarenti in 64-bit environments
|
|
static long rgInitData[] = { -1, 0, 0, 0, 0 };
|
|
static CStringData* afxDataNil = (CStringData*)&rgInitData;
|
|
static LPCTSTR afxPchNil = (LPCTSTR)(((BYTE*)&rgInitData)+sizeof(CStringData));
|
|
// special function to make afxEmptyString work even during initialization
|
|
const CString& AfxGetEmptyString()
|
|
{ return *(CString*)&afxPchNil; }
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Construction/Destruction
|
|
|
|
CString::CString()
|
|
{
|
|
Init();
|
|
}
|
|
|
|
CString::CString(const CString& stringSrc)
|
|
{
|
|
// REMOVING REF COUNTING: ASSERT(stringSrc.GetData()->nRefs != 0);
|
|
// REMOVING REF COUNTING: if (stringSrc.GetData()->nRefs >= 0)
|
|
// REMOVING REF COUNTING: {
|
|
// REMOVING REF COUNTING: ASSERT(stringSrc.GetData() != afxDataNil);
|
|
// REMOVING REF COUNTING: m_pchData = stringSrc.m_pchData;
|
|
// REMOVING REF COUNTING: InterlockedIncrement(&GetData()->nRefs);
|
|
// REMOVING REF COUNTING: }
|
|
// REMOVING REF COUNTING: else
|
|
// REMOVING REF COUNTING: {
|
|
Init();
|
|
*this = stringSrc.m_pchData;
|
|
// REMOVING REF COUNTING: }
|
|
}
|
|
|
|
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);
|
|
|
|
// MFC had the following assertion. I've killed it because we
|
|
// don't have INT_MAX. JM 1/15/99
|
|
//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)];
|
|
//[BC-03022001] - added check for NULL ptr to satisfy MS code analysis tool.
|
|
if(pData)
|
|
{
|
|
pData->nRefs = 1;
|
|
pData->data()[nLen] = '\0';
|
|
pData->nDataLength = nLen;
|
|
pData->nAllocLength = nLen;
|
|
m_pchData = pData->data();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CString::Release()
|
|
{
|
|
if (GetData() != afxDataNil)
|
|
{
|
|
// REMOVING REF COUNTING: ASSERT(GetData()->nRefs != 0);
|
|
// REMOVING REF COUNTING: if (InterlockedDecrement(&GetData()->nRefs) <= 0)
|
|
delete[] (BYTE*)GetData();
|
|
Init();
|
|
}
|
|
}
|
|
|
|
void PASCAL CString::Release(CStringData* pData)
|
|
{
|
|
if (pData != afxDataNil)
|
|
{
|
|
// REMOVING REF COUNTING: ASSERT(pData->nRefs != 0);
|
|
// REMOVING REF COUNTING: if (InterlockedDecrement(&pData->nRefs) <= 0)
|
|
delete[] (BYTE*)pData;
|
|
}
|
|
}
|
|
|
|
void CString::CopyBeforeWrite()
|
|
{
|
|
// REMOVING REF COUNTING: if (GetData()->nRefs > 1)
|
|
// REMOVING REF COUNTING: {
|
|
// REMOVING REF COUNTING: CStringData* pData = GetData();
|
|
// REMOVING REF COUNTING: Release();
|
|
// REMOVING REF COUNTING: AllocBuffer(pData->nDataLength);
|
|
// REMOVING REF COUNTING: memcpy(m_pchData, pData->data(), (pData->nDataLength+1)*sizeof(TCHAR));
|
|
// REMOVING REF COUNTING: }
|
|
// REMOVING REF COUNTING: ASSERT(GetData()->nRefs <= 1);
|
|
}
|
|
|
|
void CString::AllocBeforeWrite(int nLen)
|
|
{
|
|
// REMOVING REF COUNTING: if (GetData()->nRefs > 1 || nLen > GetData()->nAllocLength)
|
|
if (nLen > GetData()->nAllocLength)
|
|
{
|
|
Release();
|
|
AllocBuffer(nLen);
|
|
}
|
|
// REMOVING REF COUNTING: ASSERT(GetData()->nRefs <= 1);
|
|
}
|
|
|
|
CString::~CString()
|
|
// free any attached data
|
|
{
|
|
if (GetData() != afxDataNil)
|
|
{
|
|
// REMOVING REF COUNTING: if (InterlockedDecrement(&GetData()->nRefs) <= 0)
|
|
delete[] (BYTE*)GetData();
|
|
}
|
|
}
|
|
|
|
void CString::Empty()
|
|
{
|
|
if (GetData()->nDataLength == 0)
|
|
return;
|
|
// REMOVING REF COUNTING: if (GetData()->nRefs >= 0)
|
|
Release();
|
|
// REMOVING REF COUNTING: else
|
|
// REMOVING REF COUNTING: *this = &afxChNil;
|
|
ASSERT(GetData()->nDataLength == 0);
|
|
// REMOVING REF COUNTING: ASSERT(GetData()->nRefs < 0 || GetData()->nAllocLength == 0);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// 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();
|
|
// Unlike MFC, no implicit LoadString offered - JM 1/15/99
|
|
int nLen = SafeStrlen(lpsz);
|
|
if (nLen != 0)
|
|
{
|
|
AllocBuffer(nLen);
|
|
memcpy(m_pchData, lpsz, nLen*sizeof(TCHAR));
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// 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)
|
|
{
|
|
// REMOVING REF COUNTING: if (m_pchData != stringSrc.m_pchData)
|
|
// REMOVING REF COUNTING: {
|
|
// REMOVING REF COUNTING: if ((GetData()->nRefs < 0 && GetData() != afxDataNil) ||
|
|
// REMOVING REF COUNTING: stringSrc.GetData()->nRefs < 0)
|
|
// REMOVING REF COUNTING: {
|
|
// actual copy necessary since one of the strings is locked
|
|
AssignCopy(stringSrc.GetData()->nDataLength, stringSrc.m_pchData);
|
|
// REMOVING REF COUNTING: }
|
|
// REMOVING REF COUNTING: else
|
|
// REMOVING REF COUNTING: {
|
|
// REMOVING REF COUNTING: // can just copy references around
|
|
// REMOVING REF COUNTING: Release();
|
|
// REMOVING REF COUNTING: ASSERT(stringSrc.GetData() != afxDataNil);
|
|
// REMOVING REF COUNTING: m_pchData = stringSrc.m_pchData;
|
|
// REMOVING REF COUNTING: InterlockedIncrement(&GetData()->nRefs);
|
|
// REMOVING REF COUNTING: }
|
|
// REMOVING REF COUNTING: }
|
|
return *this;
|
|
}
|
|
|
|
const CString& CString::operator=(LPCTSTR lpsz)
|
|
{
|
|
// Suppress the following Assert from MFC - JM 1/15/99
|
|
// ASSERT(lpsz == NULL || AfxIsValidString(lpsz, FALSE));
|
|
AssignCopy(SafeStrlen(lpsz), lpsz);
|
|
return *this;
|
|
}
|
|
|
|
const CString& CString::operator=(TCHAR ch)
|
|
{
|
|
AssignCopy(1, &ch);
|
|
return *this;
|
|
}
|
|
|
|
#ifdef _UNICODE
|
|
const CString& CString::operator=(LPCSTR lpsz)
|
|
{
|
|
int nSrcLen = lpsz != NULL ? lstrlenA(lpsz) : 0;
|
|
AllocBeforeWrite(nSrcLen);
|
|
mbstowcs(m_pchData, lpsz, nSrcLen+1);
|
|
ReleaseBuffer();
|
|
return *this;
|
|
}
|
|
#else //!_UNICODE
|
|
const CString& CString::operator=(LPCWSTR lpsz)
|
|
{
|
|
int nSrcLen = lpsz != NULL ? wcslen(lpsz) : 0;
|
|
AllocBeforeWrite(nSrcLen*2);
|
|
wcstombs(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
|
|
|
|
// we (Saltmine) have switched away from friend because VC 6/0 doesn't like it. - JM 1/15/99
|
|
// we (Saltmine) do LPCTSTR but not TCHAR - JM 1/15/99
|
|
|
|
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 CString::operator+(const CString& string2)
|
|
{
|
|
CString s;
|
|
s.ConcatCopy(GetData()->nDataLength, m_pchData,
|
|
string2.GetData()->nDataLength, string2.m_pchData);
|
|
return s;
|
|
}
|
|
|
|
CString CString::operator+(LPCTSTR lpsz)
|
|
{
|
|
// Suppress the following Assert from MFC - JM 1/15/99
|
|
// ASSERT(lpsz == NULL || AfxIsValidString(lpsz, FALSE));
|
|
CString s;
|
|
s.ConcatCopy(GetData()->nDataLength, m_pchData,
|
|
CString::SafeStrlen(lpsz), lpsz);
|
|
return s;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// concatenate in place
|
|
|
|
void CString::ConcatInPlace(int nSrcLen, LPCTSTR lpszSrcData)
|
|
{
|
|
// -- the main routine for += operators
|
|
|
|
// concatenating an empty string is a no-op!
|
|
if (nSrcLen == 0)
|
|
return;
|
|
|
|
// if the buffer is too small, or we have a width mis-match, just
|
|
// allocate a new buffer (slow but sure)
|
|
// REMOVING REF COUNTING: if (GetData()->nRefs > 1 || GetData()->nDataLength + nSrcLen > GetData()->nAllocLength)
|
|
if (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)
|
|
{
|
|
// Suppress the following Assert from MFC - JM 1/15/99
|
|
// ASSERT(lpsz == NULL || AfxIsValidString(lpsz, FALSE));
|
|
ConcatInPlace(SafeStrlen(lpsz), lpsz);
|
|
return *this;
|
|
}
|
|
|
|
const CString& CString::operator+=(const CString& string)
|
|
{
|
|
ConcatInPlace(string.GetData()->nDataLength, string.m_pchData);
|
|
return *this;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Advanced direct buffer access
|
|
|
|
// CString::GetBuffer() and CString::ReleaseBuffer() calls should be matched.
|
|
LPTSTR CString::GetBuffer(int nMinBufLength)
|
|
{
|
|
ASSERT(nMinBufLength >= 0);
|
|
|
|
// REMOVING REF COUNTING: if (GetData()->nRefs > 1 || nMinBufLength > GetData()->nAllocLength)
|
|
if (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);
|
|
}
|
|
// REMOVING REF COUNTING: ASSERT(GetData()->nRefs <= 1);
|
|
|
|
// return a pointer to the character storage for this string
|
|
ASSERT(m_pchData != NULL);
|
|
return m_pchData;
|
|
}
|
|
|
|
// CString::GetBuffer() and CString::ReleaseBuffer() calls should be matched.
|
|
void CString::ReleaseBuffer(int nNewLength /* = -1 */)
|
|
{
|
|
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;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Commonly used routines
|
|
|
|
int CString::Find(TCHAR ch) const
|
|
{
|
|
// find first single character
|
|
LPTSTR lpsz = _tcschr(m_pchData, (_TUCHAR)ch);
|
|
|
|
// return -1 if not found and index otherwise
|
|
return (lpsz == NULL) ? CString::FIND_FAILED : (int)(lpsz - m_pchData);
|
|
}
|
|
|
|
void CString::MakeLower()
|
|
{
|
|
CopyBeforeWrite();
|
|
_tcslwr(m_pchData);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Very simple sub-string extraction
|
|
|
|
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;
|
|
}
|
|
|
|
CString CString::Right(int nCount) const
|
|
{
|
|
if (nCount < 0)
|
|
nCount = 0;
|
|
else if (nCount > GetData()->nDataLength)
|
|
nCount = GetData()->nDataLength;
|
|
|
|
CString dest;
|
|
AllocCopy(dest, nCount, GetData()->nDataLength-nCount, 0);
|
|
return dest;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Finding
|
|
|
|
int CString::ReverseFind(TCHAR ch) const
|
|
{
|
|
// find last single character
|
|
LPTSTR lpsz = _tcsrchr(m_pchData, (_TUCHAR)ch);
|
|
|
|
// return -1 if not found, distance from beginning otherwise
|
|
return (lpsz == NULL) ? CString::FIND_FAILED : (int)(lpsz - m_pchData);
|
|
}
|
|
|
|
// find a sub-string (like strstr)
|
|
int CString::Find(LPCTSTR lpszSub) const
|
|
{
|
|
// Suppress the following Assert from MFC - JM 1/15/99
|
|
// ASSERT(AfxIsValidString(lpszSub, FALSE));
|
|
|
|
// find first matching substring
|
|
LPTSTR lpsz = _tcsstr(m_pchData, lpszSub);
|
|
|
|
// return -1 for not found, distance from beginning otherwise
|
|
return (lpsz == NULL) ? CString::FIND_FAILED : (int)(lpsz - m_pchData);
|
|
}
|
|
|
|
// find a sub-string (like strstr). Added function - RAB19991112.
|
|
int CString::Find(LPCTSTR lpszSub, int nStart) const
|
|
{
|
|
// Suppress the following Assert from MFC - RAB19991112.
|
|
//ASSERT(AfxIsValidString(lpszSub));
|
|
|
|
int nLength = GetData()->nDataLength;
|
|
if (nStart > nLength)
|
|
return CString::FIND_FAILED;
|
|
|
|
// find first matching substring
|
|
LPTSTR lpsz = _tcsstr(m_pchData + nStart, lpszSub);
|
|
|
|
// return -1 for not found, distance from beginning otherwise
|
|
return (lpsz == NULL) ? CString::FIND_FAILED : (int)(lpsz - m_pchData);
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CString formatting
|
|
|
|
#ifdef _MAC
|
|
#define TCHAR_ARG int
|
|
#define WCHAR_ARG unsigned
|
|
#define CHAR_ARG int
|
|
#else
|
|
#define TCHAR_ARG TCHAR
|
|
#define WCHAR_ARG WCHAR
|
|
#define CHAR_ARG char
|
|
#endif
|
|
|
|
#if defined(_68K_) || defined(_X86_)
|
|
#define DOUBLE_ARG _AFX_DOUBLE
|
|
#else
|
|
#define DOUBLE_ARG double
|
|
#endif
|
|
|
|
#define FORCE_ANSI 0x10000
|
|
#define FORCE_UNICODE 0x20000
|
|
|
|
void CString::FormatV(LPCTSTR lpszFormat, va_list argList)
|
|
{
|
|
// Suppress the following Assert from MFC - JM 1/15/99
|
|
// ASSERT(AfxIsValidString(lpszFormat, FALSE));
|
|
|
|
va_list argListSave = argList;
|
|
|
|
// make a guess at the maximum length of the resulting string
|
|
int nMaxLen = 0;
|
|
for (LPCTSTR lpsz = lpszFormat; *lpsz != '\0'; lpsz = _tcsinc(lpsz))
|
|
{
|
|
// handle '%' character, but watch out for '%%'
|
|
if (*lpsz != '%' || *(lpsz = _tcsinc(lpsz)) == '%')
|
|
{
|
|
nMaxLen += _tclen(lpsz);
|
|
continue;
|
|
}
|
|
|
|
int nItemLen = 0;
|
|
|
|
// handle '%' character with format
|
|
int nWidth = 0;
|
|
for (; *lpsz != '\0'; lpsz = _tcsinc(lpsz))
|
|
{
|
|
// check for valid flags
|
|
if (*lpsz == '#')
|
|
nMaxLen += 2; // for '0x'
|
|
else if (*lpsz == '*')
|
|
nWidth = va_arg(argList, int);
|
|
else if (*lpsz == '-' || *lpsz == '+' || *lpsz == '0' ||
|
|
*lpsz == ' ')
|
|
;
|
|
else // hit non-flag character
|
|
break;
|
|
}
|
|
// get width and skip it
|
|
if (nWidth == 0)
|
|
{
|
|
// width indicated by
|
|
nWidth = _ttoi(lpsz);
|
|
for (; *lpsz != '\0' && _istdigit(*lpsz); lpsz = _tcsinc(lpsz))
|
|
;
|
|
}
|
|
ASSERT(nWidth >= 0);
|
|
|
|
int nPrecision = 0;
|
|
if (*lpsz == '.')
|
|
{
|
|
// skip past '.' separator (width.precision)
|
|
lpsz = _tcsinc(lpsz);
|
|
|
|
// get precision and skip it
|
|
if (*lpsz == '*')
|
|
{
|
|
nPrecision = va_arg(argList, int);
|
|
lpsz = _tcsinc(lpsz);
|
|
}
|
|
else
|
|
{
|
|
nPrecision = _ttoi(lpsz);
|
|
for (; *lpsz != '\0' && _istdigit(*lpsz); lpsz = _tcsinc(lpsz))
|
|
;
|
|
}
|
|
ASSERT(nPrecision >= 0);
|
|
}
|
|
|
|
// should be on type modifier or specifier
|
|
int nModifier = 0;
|
|
switch (*lpsz)
|
|
{
|
|
// modifiers that affect size
|
|
case 'h':
|
|
nModifier = FORCE_ANSI;
|
|
lpsz = _tcsinc(lpsz);
|
|
break;
|
|
case 'l':
|
|
nModifier = FORCE_UNICODE;
|
|
lpsz = _tcsinc(lpsz);
|
|
break;
|
|
|
|
// modifiers that do not affect size
|
|
case 'F':
|
|
case 'N':
|
|
case 'L':
|
|
lpsz = _tcsinc(lpsz);
|
|
break;
|
|
}
|
|
|
|
// now should be on specifier
|
|
switch (*lpsz | nModifier)
|
|
{
|
|
// single characters
|
|
case 'c':
|
|
case 'C':
|
|
nItemLen = 2;
|
|
va_arg(argList, TCHAR_ARG);
|
|
break;
|
|
case 'c'|FORCE_ANSI:
|
|
case 'C'|FORCE_ANSI:
|
|
nItemLen = 2;
|
|
va_arg(argList, CHAR_ARG);
|
|
break;
|
|
case 'c'|FORCE_UNICODE:
|
|
case 'C'|FORCE_UNICODE:
|
|
nItemLen = 2;
|
|
va_arg(argList, WCHAR_ARG);
|
|
break;
|
|
|
|
// strings
|
|
case 's':
|
|
{
|
|
LPCTSTR pstrNextArg = va_arg(argList, LPCTSTR);
|
|
if (pstrNextArg == NULL)
|
|
nItemLen = 6; // "(null)"
|
|
else
|
|
{
|
|
nItemLen = lstrlen(pstrNextArg);
|
|
nItemLen = max(1, nItemLen);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 'S':
|
|
{
|
|
#ifndef _UNICODE
|
|
LPWSTR pstrNextArg = va_arg(argList, LPWSTR);
|
|
if (pstrNextArg == NULL)
|
|
nItemLen = 6; // "(null)"
|
|
else
|
|
{
|
|
nItemLen = wcslen(pstrNextArg);
|
|
nItemLen = max(1, nItemLen);
|
|
}
|
|
#else
|
|
LPCSTR pstrNextArg = va_arg(argList, LPCSTR);
|
|
if (pstrNextArg == NULL)
|
|
nItemLen = 6; // "(null)"
|
|
else
|
|
{
|
|
nItemLen = lstrlenA(pstrNextArg);
|
|
nItemLen = max(1, nItemLen);
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
case 's'|FORCE_ANSI:
|
|
case 'S'|FORCE_ANSI:
|
|
{
|
|
LPCSTR pstrNextArg = va_arg(argList, LPCSTR);
|
|
if (pstrNextArg == NULL)
|
|
nItemLen = 6; // "(null)"
|
|
else
|
|
{
|
|
nItemLen = lstrlenA(pstrNextArg);
|
|
nItemLen = max(1, nItemLen);
|
|
}
|
|
break;
|
|
}
|
|
|
|
#ifndef _MAC
|
|
case 's'|FORCE_UNICODE:
|
|
case 'S'|FORCE_UNICODE:
|
|
{
|
|
LPWSTR pstrNextArg = va_arg(argList, LPWSTR);
|
|
if (pstrNextArg == NULL)
|
|
nItemLen = 6; // "(null)"
|
|
else
|
|
{
|
|
nItemLen = wcslen(pstrNextArg);
|
|
nItemLen = max(1, nItemLen);
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// adjust nItemLen for strings
|
|
if (nItemLen != 0)
|
|
{
|
|
nItemLen = max(nItemLen, nWidth);
|
|
if (nPrecision != 0)
|
|
nItemLen = min(nItemLen, nPrecision);
|
|
}
|
|
else
|
|
{
|
|
switch (*lpsz)
|
|
{
|
|
// integers
|
|
case 'd':
|
|
case 'i':
|
|
case 'u':
|
|
case 'x':
|
|
case 'X':
|
|
case 'o':
|
|
va_arg(argList, int);
|
|
nItemLen = 32;
|
|
nItemLen = max(nItemLen, nWidth+nPrecision);
|
|
break;
|
|
#if 0
|
|
// We (Saltmine) are not currently supporting formatting of real numbers 1/15/99
|
|
case 'e':
|
|
case 'f':
|
|
case 'g':
|
|
case 'G':
|
|
va_arg(argList, DOUBLE_ARG);
|
|
nItemLen = 128;
|
|
nItemLen = max(nItemLen, nWidth+nPrecision);
|
|
break;
|
|
#endif
|
|
|
|
case 'p':
|
|
va_arg(argList, void*);
|
|
nItemLen = 32;
|
|
nItemLen = max(nItemLen, nWidth+nPrecision);
|
|
break;
|
|
|
|
// no output
|
|
case 'n':
|
|
va_arg(argList, int*);
|
|
break;
|
|
|
|
default:
|
|
ASSERT(FALSE); // unknown formatting option
|
|
}
|
|
}
|
|
|
|
// adjust nMaxLen for output nItemLen
|
|
nMaxLen += nItemLen;
|
|
}
|
|
|
|
GetBuffer(nMaxLen);
|
|
|
|
// Got rid of MFC's VERIFY in next line - JM 1/15/99
|
|
//VERIFY(_vstprintf(m_pchData, lpszFormat, argListSave) <= GetAllocLength());
|
|
_vstprintf(m_pchData, lpszFormat, argListSave);
|
|
|
|
ReleaseBuffer();
|
|
|
|
va_end(argListSave);
|
|
}
|
|
|
|
// formatting (using wsprintf style formatting)
|
|
void CString::Format(LPCTSTR lpszFormat, ...)
|
|
{
|
|
// Suppress the following Assert from MFC - JM 1/15/99
|
|
// ASSERT(AfxIsValidString(lpszFormat, FALSE));
|
|
|
|
va_list argList;
|
|
va_start(argList, lpszFormat);
|
|
FormatV(lpszFormat, argList);
|
|
va_end(argList);
|
|
}
|
|
|
|
void CString::TrimRight()
|
|
{
|
|
CopyBeforeWrite();
|
|
|
|
// 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';
|
|
GetData()->nDataLength = static_cast<int>(lpszLast - m_pchData);
|
|
}
|
|
}
|
|
|
|
void CString::TrimLeft()
|
|
{
|
|
CopyBeforeWrite();
|
|
|
|
// find first non-space character
|
|
LPCTSTR lpsz = m_pchData;
|
|
while (_istspace(*lpsz))
|
|
lpsz = _tcsinc(lpsz);
|
|
|
|
// fix up data and length
|
|
int nDataLength = GetData()->nDataLength - static_cast<int>(lpsz - m_pchData);
|
|
memmove(m_pchData, lpsz, (nDataLength+1)*sizeof(TCHAR));
|
|
GetData()->nDataLength = nDataLength;
|
|
}
|
|
|
|
BOOL CString::LoadString(UINT nID)
|
|
{
|
|
// try fixed buffer first (to avoid wasting space in the heap)
|
|
TCHAR szTemp[256];
|
|
int nLen = ::AfxLoadString(nID, szTemp, _countof(szTemp));
|
|
if (_countof(szTemp) - nLen > CHAR_FUDGE)
|
|
{
|
|
*this = szTemp;
|
|
return nLen > 0;
|
|
}
|
|
|
|
// try buffer size of 512, then larger size until entire string is retrieved
|
|
int nSize = 256;
|
|
do
|
|
{
|
|
nSize += 256;
|
|
nLen = ::AfxLoadString(nID, GetBuffer(nSize-1), nSize);
|
|
} while (nSize - nLen <= CHAR_FUDGE);
|
|
ReleaseBuffer();
|
|
|
|
return nLen > 0;
|
|
}
|
|
|
|
bool __stdcall operator ==(const CString& s1, const CString& s2)
|
|
{
|
|
return (s1.GetLength() == s2.GetLength() && ! _tcscmp((LPCTSTR)s1, (LPCTSTR)s2) );
|
|
}
|
|
|
|
bool __stdcall operator ==(const CString& s1, LPCTSTR s2)
|
|
{
|
|
return (! _tcscmp((LPCTSTR)s1, s2) );
|
|
}
|
|
|
|
bool __stdcall operator ==(LPCTSTR s1, const CString& s2)
|
|
{
|
|
return (! _tcscmp(s1, (LPCTSTR)s2) );
|
|
}
|
|
|
|
bool __stdcall operator !=(const CString& s1, const CString& s2)
|
|
{
|
|
return (s1.GetLength() != s2.GetLength() || _tcscmp((LPCTSTR)s1, (LPCTSTR)s2) );
|
|
}
|
|
|
|
bool __stdcall operator !=(const CString& s1, LPCTSTR s2)
|
|
{
|
|
return (_tcscmp((LPCTSTR)s1, s2) ) ? true : false;
|
|
}
|
|
|
|
bool __stdcall operator !=(LPCTSTR s1, const CString& s2)
|
|
{
|
|
return (_tcscmp(s1, (LPCTSTR)s2) ) ? true : false;
|
|
}
|
|
|
|
bool __stdcall operator < (const CString& s1, const CString& s2)
|
|
{
|
|
return (_tcscmp((LPCTSTR)s1, (LPCTSTR)s2) <0 );
|
|
}
|
|
|
|
bool __stdcall operator < (const CString& s1, LPCTSTR s2)
|
|
{
|
|
return (_tcscmp((LPCTSTR)s1, s2) <0 );
|
|
}
|
|
|
|
bool __stdcall operator < (LPCTSTR s1, const CString& s2)
|
|
{
|
|
return (_tcscmp(s1, (LPCTSTR)s2) <0 );
|
|
}
|
|
|
|
CString operator+(LPCTSTR lpsz, const CString& string)
|
|
{
|
|
return CString(lpsz) + string;
|
|
}
|
|
|
|
void CString::Init()
|
|
{
|
|
m_pchData = afxDataNil->data();
|
|
}
|
|
|
|
CStringData* CString::GetData() const
|
|
{
|
|
if(m_pchData != NULL)
|
|
return ((CStringData*)m_pchData)-1;
|
|
return afxDataNil;
|
|
}
|