277 lines
7.9 KiB
C
277 lines
7.9 KiB
C
/*
|
|
* Process Hacker -
|
|
* string formatting
|
|
*
|
|
* Copyright (C) 2010-2015 wj32
|
|
*
|
|
* This file is part of Process Hacker.
|
|
*
|
|
* Process Hacker is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Process Hacker is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/*
|
|
* This module provides a high-performance string formatting mechanism. Instead of using format
|
|
* strings, the user supplies an array of structures. This system is 2-5 times faster than
|
|
* printf-based functions.
|
|
*
|
|
* This file contains the public interfaces, while including the real formatting code from
|
|
* elsewhere. There are currently two functions: PhFormat, which returns a string object containing
|
|
* the formatted string, and PhFormatToBuffer, which writes the formatted string to a buffer. The
|
|
* latter is a bit faster due to the lack of resizing logic.
|
|
*/
|
|
|
|
#include <phbase.h>
|
|
#include <locale.h>
|
|
|
|
extern ULONG PhMaxSizeUnit;
|
|
|
|
#define SMALL_BUFFER_LENGTH (PH_OBJECT_SMALL_OBJECT_SIZE - FIELD_OFFSET(PH_STRING, Data) - sizeof(WCHAR))
|
|
#define BUFFER_SIZE 512
|
|
|
|
#define PHP_FORMAT_NEGATIVE 0x1
|
|
#define PHP_FORMAT_POSITIVE 0x2
|
|
#define PHP_FORMAT_PAD 0x4
|
|
|
|
// Internal CRT routine needed for floating-point conversion
|
|
|
|
errno_t __cdecl _cfltcvt_l(double *arg, char *buffer, size_t sizeInBytes,
|
|
int format, int precision, int caps, _locale_t plocinfo);
|
|
|
|
// Keep in sync with PhSizeUnitNames
|
|
static PH_STRINGREF PhpSizeUnitNamesCounted[7] =
|
|
{
|
|
PH_STRINGREF_INIT(L"B"),
|
|
PH_STRINGREF_INIT(L"kB"),
|
|
PH_STRINGREF_INIT(L"MB"),
|
|
PH_STRINGREF_INIT(L"GB"),
|
|
PH_STRINGREF_INIT(L"TB"),
|
|
PH_STRINGREF_INIT(L"PB"),
|
|
PH_STRINGREF_INIT(L"EB")
|
|
};
|
|
|
|
static PH_INITONCE PhpFormatInitOnce = PH_INITONCE_INIT;
|
|
static WCHAR PhpFormatDecimalSeparator = '.';
|
|
static WCHAR PhpFormatThousandSeparator = ',';
|
|
static _locale_t PhpFormatUserLocale = NULL;
|
|
|
|
#if (_MSC_VER >= 1900)
|
|
|
|
// See Source\10.0.10150.0\ucrt\convert\cvt.cpp in SDK v10.
|
|
errno_t __cdecl __acrt_fp_format(
|
|
double const* const value,
|
|
char* const result_buffer,
|
|
size_t const result_buffer_count,
|
|
char* const scratch_buffer,
|
|
size_t const scratch_buffer_count,
|
|
int const format,
|
|
int const precision,
|
|
UINT64 const options,
|
|
_locale_t const locale
|
|
);
|
|
|
|
static errno_t __cdecl _cfltcvt_l(double *arg, char *buffer, size_t sizeInBytes,
|
|
int format, int precision, int caps, _locale_t plocinfo)
|
|
{
|
|
char scratch_buffer[_CVTBUFSIZE + 1];
|
|
|
|
if (caps & 1)
|
|
format -= 32; // Make uppercase
|
|
|
|
return __acrt_fp_format(arg, buffer, sizeInBytes, scratch_buffer, sizeof(scratch_buffer),
|
|
format, precision, 0, plocinfo);
|
|
}
|
|
|
|
#endif
|
|
|
|
// From Source\10.0.10150.0\ucrt\inc\corecrt_internal_stdio_output.h in SDK v10.
|
|
VOID PhpCropZeros(
|
|
_Inout_ PCHAR Buffer,
|
|
_In_ _locale_t Locale
|
|
)
|
|
{
|
|
CHAR decimalSeparator = (CHAR)PhpFormatDecimalSeparator;
|
|
|
|
while (*Buffer && *Buffer != decimalSeparator)
|
|
++Buffer;
|
|
|
|
if (*Buffer++)
|
|
{
|
|
while (*Buffer && *Buffer != 'e' && *Buffer != 'E')
|
|
++Buffer;
|
|
|
|
PCHAR stop = Buffer--;
|
|
|
|
while (*Buffer == '0')
|
|
--Buffer;
|
|
|
|
if (*Buffer == decimalSeparator)
|
|
--Buffer;
|
|
|
|
while ((*++Buffer = *stop++) != '\0')
|
|
NOTHING;
|
|
}
|
|
}
|
|
|
|
PPH_STRING PhpResizeFormatBuffer(
|
|
_In_ PPH_STRING String,
|
|
_Inout_ PSIZE_T AllocatedLength,
|
|
_In_ SIZE_T UsedLength,
|
|
_In_ SIZE_T NeededLength
|
|
)
|
|
{
|
|
PPH_STRING newString;
|
|
SIZE_T allocatedLength;
|
|
|
|
allocatedLength = *AllocatedLength;
|
|
allocatedLength *= 2;
|
|
|
|
if (allocatedLength < UsedLength + NeededLength)
|
|
allocatedLength = UsedLength + NeededLength;
|
|
|
|
newString = PhCreateStringEx(NULL, allocatedLength);
|
|
memcpy(newString->Buffer, String->Buffer, UsedLength);
|
|
PhDereferenceObject(String);
|
|
|
|
*AllocatedLength = allocatedLength;
|
|
|
|
return newString;
|
|
}
|
|
|
|
/**
|
|
* Creates a formatted string.
|
|
*
|
|
* \param Format An array of format structures.
|
|
* \param Count The number of structures supplied in \a Format.
|
|
* \param InitialCapacity The number of bytes to reserve initially for the string. If 0 is
|
|
* specified, a default value is used.
|
|
*/
|
|
PPH_STRING PhFormat(
|
|
_In_reads_(Count) PPH_FORMAT Format,
|
|
_In_ ULONG Count,
|
|
_In_opt_ SIZE_T InitialCapacity
|
|
)
|
|
{
|
|
PPH_STRING string;
|
|
SIZE_T allocatedLength;
|
|
PWSTR buffer;
|
|
SIZE_T usedLength;
|
|
|
|
// Set up the buffer.
|
|
|
|
// If the specified initial capacity is too small (or zero), use the largest buffer size which
|
|
// will still be eligible for allocation from the small object free list.
|
|
if (InitialCapacity < SMALL_BUFFER_LENGTH)
|
|
InitialCapacity = SMALL_BUFFER_LENGTH;
|
|
|
|
string = PhCreateStringEx(NULL, InitialCapacity);
|
|
allocatedLength = InitialCapacity;
|
|
buffer = string->Buffer;
|
|
usedLength = 0;
|
|
|
|
#undef ENSURE_BUFFER
|
|
#undef OK_BUFFER
|
|
#undef ADVANCE_BUFFER
|
|
|
|
#define ENSURE_BUFFER(NeededLength) \
|
|
do { \
|
|
if (allocatedLength < usedLength + (NeededLength)) \
|
|
{ \
|
|
string = PhpResizeFormatBuffer(string, &allocatedLength, usedLength, (NeededLength)); \
|
|
buffer = string->Buffer + usedLength / sizeof(WCHAR); \
|
|
} \
|
|
} while (0)
|
|
|
|
#define OK_BUFFER (TRUE)
|
|
|
|
#define ADVANCE_BUFFER(Length) \
|
|
do { buffer += (Length) / sizeof(WCHAR); usedLength += (Length); } while (0)
|
|
|
|
#include "format_i.h"
|
|
|
|
string->Length = usedLength;
|
|
// Null-terminate the string.
|
|
string->Buffer[usedLength / sizeof(WCHAR)] = 0;
|
|
|
|
return string;
|
|
}
|
|
|
|
/**
|
|
* Writes a formatted string to a buffer.
|
|
*
|
|
* \param Format An array of format structures.
|
|
* \param Count The number of structures supplied in \a Format.
|
|
* \param Buffer A buffer. If NULL, no data is written.
|
|
* \param BufferLength The number of bytes available in \a Buffer, including space for the null
|
|
* terminator.
|
|
* \param ReturnLength The number of bytes required to hold the string, including the null
|
|
* terminator.
|
|
*
|
|
* \return TRUE if the buffer was large enough and the string was written (i.e. \a BufferLength >=
|
|
* \a ReturnLength), otherwise FALSE. In either case, the required number of bytes is stored in
|
|
* \a ReturnLength.
|
|
*
|
|
* \remarks If the function fails but \a BufferLength != 0, a single null byte is written to the
|
|
* start of \a Buffer.
|
|
*/
|
|
BOOLEAN PhFormatToBuffer(
|
|
_In_reads_(Count) PPH_FORMAT Format,
|
|
_In_ ULONG Count,
|
|
_Out_writes_bytes_opt_(BufferLength) PWSTR Buffer,
|
|
_In_opt_ SIZE_T BufferLength,
|
|
_Out_opt_ PSIZE_T ReturnLength
|
|
)
|
|
{
|
|
PWSTR buffer;
|
|
SIZE_T usedLength;
|
|
BOOLEAN overrun;
|
|
|
|
buffer = Buffer;
|
|
usedLength = 0;
|
|
overrun = FALSE;
|
|
|
|
// Make sure we don't try to write anything if we don't have a buffer.
|
|
if (!Buffer)
|
|
overrun = TRUE;
|
|
|
|
#undef ENSURE_BUFFER
|
|
#undef OK_BUFFER
|
|
#undef ADVANCE_BUFFER
|
|
|
|
#define ENSURE_BUFFER(NeededLength) \
|
|
do { \
|
|
if (!overrun && (BufferLength < usedLength + (NeededLength))) \
|
|
overrun = TRUE; \
|
|
} while (0)
|
|
|
|
#define OK_BUFFER (!overrun)
|
|
|
|
#define ADVANCE_BUFFER(Length) \
|
|
do { buffer += (Length) / sizeof(WCHAR); usedLength += (Length); } while (0)
|
|
|
|
#include "format_i.h"
|
|
|
|
// Write the null-terminator.
|
|
ENSURE_BUFFER(sizeof(WCHAR));
|
|
if (OK_BUFFER)
|
|
*buffer = 0;
|
|
else if (Buffer && BufferLength != 0) // try to null-terminate even if this function fails
|
|
*Buffer = 0;
|
|
ADVANCE_BUFFER(sizeof(WCHAR));
|
|
|
|
if (ReturnLength)
|
|
*ReturnLength = usedLength;
|
|
|
|
return OK_BUFFER;
|
|
}
|