693 lines
No EOL
33 KiB
C++
693 lines
No EOL
33 KiB
C++
// ==========================================================================
|
||
// Class Implementation :
|
||
// COXTrace
|
||
// ==========================================================================
|
||
|
||
// Source code file : OXTrace.cpp
|
||
|
||
// Version: 9.3
|
||
|
||
// This software along with its related components, documentation and files ("The Libraries")
|
||
// is ?1994-2007 The Code Project (1612916 Ontario Limited) and use of The Libraries is
|
||
// governed by a software license agreement ("Agreement"). Copies of the Agreement are
|
||
// available at The Code Project (www.codeproject.com), as part of the package you downloaded
|
||
// to obtain this file, or directly from our office. For a copy of the license governing
|
||
// this software, you may contact us at legalaffairs@codeproject.com, or by calling 416-849-8900.
|
||
|
||
#include "stdafx.h"
|
||
#include "OXTrace.h"
|
||
|
||
#include "UTBStrOp.h"
|
||
#include "UTB64Bit.h"
|
||
|
||
#if _MFC_VER<=0x0421
|
||
|
||
#define TCHAR_ARG TCHAR
|
||
#define WCHAR_ARG WCHAR
|
||
#define CHAR_ARG char
|
||
|
||
#ifdef _X86_
|
||
#define DOUBLE_ARG _AFX_DOUBLE
|
||
#else
|
||
#define DOUBLE_ARG double
|
||
#endif
|
||
|
||
#define FORCE_ANSI 0x10000
|
||
#define FORCE_UNICODE 0x20000
|
||
#define FORCE_INT64 0x40000
|
||
// printf-like formatting using variable arguments parameter
|
||
void FormatV(CString& sText, LPCTSTR lpszFormat, va_list argList);
|
||
|
||
#endif
|
||
|
||
//
|
||
// Static member variables
|
||
//
|
||
int COXTrace::m_nIndent = 0; // current indentation level
|
||
int COXTrace::m_nIndentStep = 4; // current indentation step
|
||
BOOL COXTrace::m_bTracing = TRUE; // current tracing state
|
||
int COXTrace::m_nWrapWidth = 120; // wrap width for messages
|
||
BOOL COXTrace::m_bPrefixOn = FALSE; // whether or not mMsg should be prepended to messages
|
||
CFile COXTrace::m_File; // file to copy trace messages to
|
||
BOOL COXTrace::m_bFlushOnWrite = FALSE; // should each line be flushed when written
|
||
|
||
COXTrace::COXTrace(LPCTSTR lpstrMsg)
|
||
: m_strHeader(lpstrMsg)
|
||
{
|
||
if (m_bTracing)
|
||
{
|
||
//
|
||
// Let the user know that a new tracing block has started
|
||
//
|
||
WriteMsg(lpstrMsg);
|
||
}
|
||
|
||
//
|
||
// Increment the indentation level
|
||
//
|
||
m_nIndent += m_nIndentStep;
|
||
}
|
||
|
||
COXTrace::~COXTrace()
|
||
{
|
||
//
|
||
// Decrement the indentation level first
|
||
//
|
||
m_nIndent -= m_nIndentStep;
|
||
|
||
if (m_bTracing)
|
||
{
|
||
//
|
||
// Let the user know that this tracing block has ended
|
||
//
|
||
WriteMsg(_T("==== ")+m_strHeader + _T(" exit"));
|
||
}
|
||
}
|
||
|
||
void COXTrace::SetDumpFile(LPCTSTR lpstrFilename,
|
||
UINT nOpenFlags/*=CFile::modeWrite|
|
||
CFile::shareDenyWrite|
|
||
CFile::modeCreate*/)
|
||
{
|
||
if (m_File.m_hFile != CFile::hFileNull)
|
||
m_File.Close();
|
||
|
||
m_File.Open(lpstrFilename, nOpenFlags);
|
||
}
|
||
|
||
/**
|
||
* Write
|
||
* Write out a text message
|
||
*
|
||
* --- In : lpstrMsg : Pointer to the text to be written out.
|
||
* --- Out :
|
||
* --- Returns :
|
||
* --- Effect : The debugging console, and possiblely the dump file, will have this
|
||
* message sent to them for viewing.
|
||
*/
|
||
void COXTrace::Write(LPCTSTR lpstrMsg)
|
||
{
|
||
if (m_bTracing)
|
||
{
|
||
if (m_bPrefixOn)
|
||
WriteMsg(m_strHeader + _T(" : ") + lpstrMsg);
|
||
else
|
||
WriteMsg(lpstrMsg);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Write
|
||
* Write out a text message, followed by the passed in value.
|
||
*
|
||
* --- In : lpstrMsg : Pointer to the text to be written out.
|
||
* nValue : Integer value to append to the end of lpstrMsg
|
||
* --- Out :
|
||
* --- Returns :
|
||
* --- Effect : The debugging console, and possiblely the dump file, will have this
|
||
* message sent to them for viewing.
|
||
*/
|
||
void COXTrace::Write(LPCTSTR lpstrMsg, int nValue)
|
||
{
|
||
WriteFormatted(_T("%s %d"), (LPCTSTR)lpstrMsg, nValue);
|
||
}
|
||
|
||
/**
|
||
* Write
|
||
* Write out a text message, followed by the passed in value.
|
||
*
|
||
* --- In : lpstrMsg : Pointer to the text to be written out.
|
||
* unValue : Unsigned integer value to append to the end of lpstrMsg
|
||
* --- Out :
|
||
* --- Returns :
|
||
* --- Effect : The debugging console, and possiblely the dump file, will have this
|
||
* message sent to them for viewing.
|
||
*/
|
||
void COXTrace::Write(LPCTSTR lpstrMsg, unsigned int unValue)
|
||
{
|
||
WriteFormatted(_T("%s %u"), (LPCTSTR)lpstrMsg, unValue);
|
||
}
|
||
|
||
/**
|
||
* Write
|
||
* Write out a text message, followed by the passed in value.
|
||
*
|
||
* --- In : lpstrMsg : Pointer to the text to be written out.
|
||
* lValue : Long integer value to append to the end of lpstrMsg
|
||
* --- Out :
|
||
* --- Returns :
|
||
* --- Effect : The debugging console, and possiblely the dump file, will have this
|
||
* message sent to them for viewing.
|
||
*/
|
||
void COXTrace::Write(LPCTSTR lpstrMsg, long lValue)
|
||
{
|
||
WriteFormatted(_T("%s %ld"), (LPCTSTR)lpstrMsg, lValue);
|
||
}
|
||
|
||
/**
|
||
* Write
|
||
* Write out a text message, followed by the passed in value.
|
||
*
|
||
* --- In : lpstrMsg : Pointer to the text to be written out.
|
||
* ulValue : Unsigned long integer value to append to the end of lpstrMsg
|
||
* --- Out :
|
||
* --- Returns :
|
||
* --- Effect : The debugging console, and possiblely the dump file, will have this
|
||
* message sent to them for viewing.
|
||
*/
|
||
void COXTrace::Write(LPCTSTR lpstrMsg, unsigned long ulValue)
|
||
{
|
||
WriteFormatted(_T("%s %lu"), (LPCTSTR)lpstrMsg, ulValue);
|
||
}
|
||
|
||
/**
|
||
* Write
|
||
* Write out a text message, followed by the passed in value.
|
||
*
|
||
* --- In : lpstrMsg : Pointer to the text to be written out.
|
||
* fValue : Floating point value to append to the end of lpstrMsg
|
||
* --- Out :
|
||
* --- Returns :
|
||
* --- Effect : The debugging console, and possiblely the dump file, will have this
|
||
* message sent to them for viewing.
|
||
*/
|
||
void COXTrace::Write(LPCTSTR lpstrMsg, double fValue)
|
||
{
|
||
WriteFormatted(_T("%s %f"), (LPCTSTR)lpstrMsg, fValue);
|
||
}
|
||
|
||
/**
|
||
* Write
|
||
* Write out a text message, followed by the passed in value.
|
||
*
|
||
* --- In : lpstrMsg : Pointer to the text to be written out.
|
||
* lpstrValue : Text string to append to the end of lpstrMsg
|
||
* --- Out :
|
||
* --- Returns :
|
||
* --- Effect : The debugging console, and possibly the dump file, will have this
|
||
* message sent to them for viewing.
|
||
*/
|
||
void COXTrace::Write(LPCTSTR lpstrMsg, LPCTSTR lpstrValue)
|
||
{
|
||
if (m_bPrefixOn)
|
||
WriteMsg(m_strHeader + _T(" : ") + lpstrMsg + lpstrValue);
|
||
else
|
||
{
|
||
CString strMsg(lpstrMsg);
|
||
|
||
WriteMsg(strMsg + lpstrValue);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Write
|
||
* Write out a text message, followed by the passed in value. In this case, the
|
||
* object's Dump() method will be called, if _DEBUG is defined. The data will be
|
||
* dumped only to the console.
|
||
*
|
||
* --- In : lpstrMsg : Pointer to the text to be written out.
|
||
* rObject : Reference to a CObject derived object.
|
||
* --- Out :
|
||
* --- Returns :
|
||
* --- Effect : The debugging console, and possiblely the dump file, will have this
|
||
* message sent to them for viewing.
|
||
*/
|
||
void COXTrace::Write(LPCTSTR lpstrMsg, const CObject& rObject)
|
||
{
|
||
CString strMsg(lpstrMsg);
|
||
CString strSep = _T("==========");
|
||
|
||
//
|
||
// Remove the trailing _T(" == ") added by the macros
|
||
//
|
||
if (strMsg.Right(4).Compare(_T(" == ")) == 0)
|
||
strMsg = strMsg.Left(strMsg.GetLength() - 4);
|
||
|
||
Write(strSep + _T(" ") + strMsg + _T(" dump ") + strSep);
|
||
|
||
if (m_File.m_hFile != CFile::hFileNull)
|
||
{
|
||
//
|
||
// Write the data out to the output file
|
||
//
|
||
#ifdef _DEBUG
|
||
CMemFile memFile;
|
||
CDumpContext context(&memFile);
|
||
context.SetDepth(1);
|
||
rObject.Dump(context);
|
||
|
||
memFile.SeekToBegin();
|
||
TCHAR szSymbolToAdd[2];
|
||
szSymbolToAdd[1]=_T('\0');
|
||
TCHAR chSymbol;
|
||
DWORD dwSymbolCount= (DWORD) memFile.GetLength()/sizeof(TCHAR);
|
||
CString sLineToAdd;
|
||
BOOL bFirstLine=TRUE;
|
||
DWORD dwCurSymbol=0;
|
||
COXTrace* pTrace=NULL;
|
||
while(dwCurSymbol<dwSymbolCount)
|
||
{
|
||
VERIFY(memFile.Read(&chSymbol,sizeof(TCHAR))==sizeof(TCHAR));
|
||
if(chSymbol==_T('\n'))
|
||
{
|
||
if(bFirstLine)
|
||
{
|
||
pTrace=new COXTrace(sLineToAdd);
|
||
bFirstLine=FALSE;
|
||
}
|
||
else
|
||
{
|
||
ASSERT(pTrace!=NULL);
|
||
pTrace->Write(sLineToAdd);
|
||
}
|
||
sLineToAdd.Empty();
|
||
}
|
||
else
|
||
{
|
||
szSymbolToAdd[0]=chSymbol;
|
||
sLineToAdd+=szSymbolToAdd;
|
||
}
|
||
dwCurSymbol++;
|
||
}
|
||
if(pTrace!=NULL)
|
||
{
|
||
if(!sLineToAdd.IsEmpty())
|
||
pTrace->Write(sLineToAdd);
|
||
delete pTrace;
|
||
}
|
||
#else
|
||
UNUSED(rObject);
|
||
Write(_T("Dumping of CObject available only in debug version (_DEBUG must be defined)!"));
|
||
#endif
|
||
}
|
||
|
||
#ifdef _DEBUG
|
||
rObject.Dump(afxDump);
|
||
#endif
|
||
|
||
Write(strSep + _T(" end of ") + strMsg + _T(" dump ") + strSep);
|
||
}
|
||
|
||
/**
|
||
* WriteFormatted
|
||
* Write out a trace message using printf() style formatting for the message.
|
||
*
|
||
* --- In : lpstrFormat : Pointer to a printf() style formatting string
|
||
* ... : additional arguments, as needed by the formatting string
|
||
* --- Out :
|
||
* --- Returns :
|
||
* --- Effect : The debugging console, and possiblely the dump file, will have this
|
||
* message sent to them for viewing.
|
||
*/
|
||
void COXTrace::WriteFormatted(LPCTSTR lpstrFormat, ...)
|
||
{
|
||
CString strMessage;
|
||
va_list args;
|
||
|
||
va_start(args, lpstrFormat);
|
||
|
||
//
|
||
// Format the message
|
||
//
|
||
#if _MFC_VER<=0x0421
|
||
FormatV(strMessage, lpstrFormat, args);
|
||
#else
|
||
strMessage.FormatV(lpstrFormat, args);
|
||
#endif
|
||
|
||
//
|
||
// Write out the message
|
||
//
|
||
if (m_bPrefixOn)
|
||
WriteMsg(m_strHeader + _T(" : ") + strMessage);
|
||
else
|
||
WriteMsg(strMessage);
|
||
|
||
va_end(args);
|
||
}
|
||
|
||
/**
|
||
* WriteMsg
|
||
* This function performs the actual work of outputing the message to the debugging
|
||
* console, as well as to the dump file, if one has been specified.
|
||
*
|
||
* --- In : lpstrMessage : Pointer to the text to be written out.
|
||
* --- Out :
|
||
* --- Returns :
|
||
* --- Effect : The debugging console, and possiblely the dump file, will have this
|
||
* message sent to them for viewing.
|
||
*/
|
||
void COXTrace::WriteMsg(LPCTSTR lpstrMessage)
|
||
{
|
||
if (m_bTracing)
|
||
{
|
||
LPTSTR lpstrLine = new TCHAR[m_nWrapWidth + 3];
|
||
TCHAR tChar;
|
||
int nIndex = 0, nPos = 0;
|
||
int nLength = PtrToInt(_tcslen(lpstrMessage));
|
||
|
||
if (lpstrLine == NULL)
|
||
{
|
||
TRACE(_T("COXTrace::WriteMsg : Unable to allocate %u characters for line buffer\n"), m_nWrapWidth + 3);
|
||
return ;
|
||
}
|
||
|
||
while (nPos < nLength)
|
||
{
|
||
if (m_nIndent > 0 && m_nIndent < (m_nWrapWidth - 10))
|
||
{
|
||
UTBStr::stprintf(lpstrLine, m_nWrapWidth + 3, _T("%*s\r\n"), m_nIndent, _T(" "));
|
||
nIndex = m_nIndent;
|
||
}
|
||
else
|
||
{
|
||
nIndex = 0;
|
||
}
|
||
|
||
while (nIndex < m_nWrapWidth && nPos < nLength)
|
||
{
|
||
tChar = lpstrMessage[nPos++];
|
||
|
||
if (tChar == _T('\n'))
|
||
{
|
||
break;
|
||
}
|
||
else if (tChar == _T('\r'))
|
||
{
|
||
if (lpstrMessage[nPos] == _T('\n'))
|
||
++nPos;
|
||
break;
|
||
}
|
||
else
|
||
{
|
||
lpstrLine[nIndex++] = tChar;
|
||
}
|
||
}
|
||
|
||
lpstrLine[nIndex] = _T('\r');
|
||
lpstrLine[nIndex+1] = _T('\n');
|
||
lpstrLine[nIndex+2] = _T('\0');
|
||
TRACE(lpstrLine);
|
||
|
||
if (m_File.m_hFile != CFile::hFileNull)
|
||
{
|
||
m_File.Write(lpstrLine, (nIndex + 2) * sizeof(TCHAR));
|
||
}
|
||
}
|
||
|
||
// v9.3 Update 01 - change delete to delete[] r.guerzoni
|
||
delete[] lpstrLine;
|
||
|
||
if (m_File.m_hFile != CFile::hFileNull && m_bFlushOnWrite)
|
||
{
|
||
//
|
||
// only fluch at end of each message, instead of at end
|
||
// of each file.
|
||
//
|
||
m_File.Flush();
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
////////////////////////////////////////////////////////////
|
||
|
||
|
||
#if _MFC_VER<=0x0421
|
||
|
||
void FormatV(CString& sText, LPCTSTR lpszFormat, va_list argList)
|
||
{
|
||
ASSERT(AfxIsValidString(lpszFormat));
|
||
|
||
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;
|
||
if (_tcsncmp(lpsz, _T("I64"), 3) == 0)
|
||
{
|
||
lpsz += 3;
|
||
nModifier = FORCE_INT64;
|
||
#if !defined(_X86_) && !defined(_ALPHA_)
|
||
// __int64 is only available on X86 and ALPHA platforms
|
||
ASSERT(FALSE);
|
||
#endif
|
||
}
|
||
else
|
||
{
|
||
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;
|
||
|
||
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;
|
||
}
|
||
|
||
// adjust nItemLen for strings
|
||
if (nItemLen != 0)
|
||
{
|
||
if (nPrecision != 0)
|
||
nItemLen = min(nItemLen, nPrecision);
|
||
nItemLen = max(nItemLen, nWidth);
|
||
}
|
||
else
|
||
{
|
||
switch (*lpsz)
|
||
{
|
||
// integers
|
||
case 'd':
|
||
case 'i':
|
||
case 'u':
|
||
case 'x':
|
||
case 'X':
|
||
case 'o':
|
||
if (nModifier & FORCE_INT64)
|
||
va_arg(argList, __int64);
|
||
else
|
||
va_arg(argList, int);
|
||
nItemLen = 32;
|
||
nItemLen = max(nItemLen, nWidth+nPrecision);
|
||
break;
|
||
|
||
case 'e':
|
||
case 'g':
|
||
case 'G':
|
||
va_arg(argList, DOUBLE_ARG);
|
||
nItemLen = 128;
|
||
nItemLen = max(nItemLen, nWidth+nPrecision);
|
||
break;
|
||
|
||
case 'f':
|
||
va_arg(argList, DOUBLE_ARG);
|
||
nItemLen = 128; // width isn't truncated
|
||
// 312 == strlen("-1+(309 zeroes).")
|
||
// 309 zeroes == max precision of a double
|
||
nItemLen = max(nItemLen, 312+nPrecision);
|
||
break;
|
||
|
||
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;
|
||
}
|
||
|
||
LPTSTR lpszBufer=sText.GetBuffer(nMaxLen);
|
||
_vstprintf(lpszBufer, lpszFormat, argListSave);
|
||
sText.ReleaseBuffer();
|
||
|
||
va_end(argListSave);
|
||
}
|
||
|
||
#endif |