/* * 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 . */ /* * 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 #include 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; }