Skip to content

Instantly share code, notes, and snippets.

@udaken
Last active June 12, 2023 04:20
Show Gist options
  • Save udaken/69c4c51d7e6c1a63c4b0aea431c10d16 to your computer and use it in GitHub Desktop.
Save udaken/69c4c51d7e6c1a63c4b0aea431c10d16 to your computer and use it in GitHub Desktop.
Tiny String class for Win32.(licensed under The Unlicense)
#pragma once
#include <windows.h>
#include <Shlwapi.h>
#include <Strsafe.h>
#pragma comment(lib, "Shlwapi.lib")
#include <utility>
#include <cassert>
#include <cwchar>
#include <cstdint>
#if _MSC_VER < 1900
#ifndef noexcept
#define noexcept throw()
#endif
#endif
#if __cpp_rvalue_references >= 200610
#include <memory>
#define TWSTRING_MOVE(...) std::move(__VA_ARGS__)
#else
#define TWSTRING_MOVE(...) (__VA_ARGS__)
#endif
#ifdef TWSTRING_NAMESPACE
namespace TWSTRING_NAMESPACE {
#endif
#define TWSTRING_CHECK ::DebugBreak()
#if !NDEBUG
#define TWSTRING_HEAP_FLAG HEAP_GENERATE_EXCEPTIONS
#else
#define TWSTRING_HEAP_FLAG 0
#endif
template <class TChar>
struct TinyWinStringAllocator
{
typedef TChar* Ptr;
static Ptr const NullValue;
TinyWinStringAllocator()
: hHeap(::GetProcessHeap())
{
}
explicit TinyWinStringAllocator(HANDLE h)
: hHeap(h)
{
}
inline Ptr Alloc(SIZE_T size) noexcept
{
if (size > INT_MAX)
{
TWSTRING_CHECK;
return NullValue;
}
Ptr newPtr = reinterpret_cast<TChar*>(::HeapAlloc(hHeap, HEAP_ZERO_MEMORY | TWSTRING_HEAP_FLAG, size * sizeof(TChar)));
return newPtr;
}
inline Ptr ReAlloc(Ptr p, SIZE_T size) noexcept
{
if (size > INT_MAX)
{
TWSTRING_CHECK;
return NullValue;
}
Ptr newPtr = reinterpret_cast<TChar*>(::HeapReAlloc(hHeap, HEAP_ZERO_MEMORY | TWSTRING_HEAP_FLAG, p, size * sizeof(TChar)));
if (!newPtr)
TWSTRING_CHECK;
return newPtr;
}
inline void Free(Ptr p) noexcept
{
if (!::HeapFree(hHeap, 0, p))
TWSTRING_CHECK;
}
TChar* CastTo(Ptr from) const noexcept { return reinterpret_cast<TChar*>(from); }
private:
HANDLE hHeap;
};
template <class TChar>
typename TinyWinStringAllocator<TChar>::Ptr const TinyWinStringAllocator<TChar>::NullValue = NULL;
template <class Allocator = TinyWinStringAllocator<TCHAR> >
class TinyWinString
{
enum { DefaultBufferLength = 16 };
static const size_t BufferGrows = 16;
//typedef typename Allocator::Ptr Ptr;
private:
// internal
explicit TinyWinString(Allocator& allocator, typename Allocator::Ptr ptr, SIZE_T capacity)
: m_allocator(allocator)
, m_buffer(ptr)
, m_capacity(capacity)
{
}
inline static TCHAR* strmove(LPTSTR dest, LPCTSTR src, SIZE_T size)
{
#if UNICODE
wmemmove(dest, src, size);
#else
memmove(dest, src, size);
#endif
return dest;
}
TinyWinString& append(LPCTSTR other, SIZE_T nLength)
{
SIZE_T newLength = GetLength() + nLength;
if (newLength + 1 > GetAllocLength())
{
Preallocate(newLength + 1);
}
StrNCat(GetBuffer(), other, nLength + 1);
return *this;
}
public:
explicit TinyWinString(SIZE_T allocLength = 0, Allocator allocator = Allocator())
: m_allocator(allocator)
, m_buffer(allocLength > 0 ? allocator.Alloc(allocLength) : allocator.NullValue)
, m_capacity(allocLength)
{
}
template <class A>
TinyWinString(const TinyWinString<A>& other)
: m_allocator(other.m_allocator)
, m_buffer(m_allocator.Alloc(other.m_capacity))
, m_capacity(other.m_capacity)
{
other.CopyTo(GetBuffer(), m_capacity);
}
explicit TinyWinString(LPCTSTR pchSrc, SIZE_T nLength, Allocator allocator = Allocator())
: TinyWinString(nLength + 1, allocator)
{
assert(nLength >= 0);
CopyChars(GetBuffer(), pchSrc, m_capacity);
}
template <SIZE_T nLength>
constexpr TinyWinString(const TCHAR(&other)[nLength], Allocator allocator = Allocator())
: m_allocator(allocator)
, m_buffer(m_allocator.Alloc(nLength))
, m_capacity(nLength)
{
strmove(m_buffer, other, m_capacity);
}
TinyWinString(LPCTSTR other, Allocator allocator = Allocator())
: TinyWinString(other, StringLength(other), allocator)
{
}
#ifdef UNICODE
explicit TinyWinString(LPCSTR other, int nLength = -1, DWORD codepage = CP_THREAD_ACP, DWORD dwFlags = 0, Allocator allocator = Allocator())
: TinyWinString(static_cast<SIZE_T>(::MultiByteToWideChar(codepage, dwFlags, other, nLength, NULL, 0)), allocator)
{
if (!::MultiByteToWideChar(codepage, dwFlags, other, nLength, GetBuffer(), static_cast<int>(m_capacity)))
TWSTRING_CHECK;
}
#endif
#if __cpp_rvalue_references >= 200610
TinyWinString(TinyWinString&& other) noexcept
: TinyWinString(other.m_allocator, other.m_buffer, other.m_capacity)
{
other.m_buffer = other.m_allocator.NullValue;
other.m_capacity = 0;
}
#endif
~TinyWinString()
{
Empty();
}
#pragma region SimpleString
template <class A>
TinyWinString& Append(const TinyWinString<A>& other)
{
return Append(other.m_buffer);
}
TinyWinString& Append(LPCTSTR other)
{
return append(other, StringLength(other));
}
template <SIZE_T nLength>
TinyWinString& Append(const TCHAR(&other)[nLength])
{
return Append(other, nLength);
}
TinyWinString& Append(LPCTSTR other, SIZE_T nLength)
{
if (nLength > StringLength(other))
TWSTRING_CHECK;
return append(other, nLength);
}
TinyWinString& AppendChar(TCHAR c)
{
return append(&c, 1);
}
static void CopyChars(
LPTSTR pchDest,
LPCTSTR pchSrc,
SIZE_T nChars) noexcept
{
assert(nChars <= MAXINT);
lstrcpyn(pchDest, pchSrc, static_cast<int>(nChars));
pchDest[nChars - 1] = TEXT('\0');
}
void Empty() noexcept
{
if (m_buffer != m_allocator.NullValue)
{
m_allocator.Free(m_buffer);
m_buffer = m_allocator.NullValue;
m_capacity = 0;
}
}
SIZE_T GetAllocLength() const { return m_capacity; }
const TCHAR& GetAt(SIZE_T iChar) const
{
if (iChar > GetLength())
TWSTRING_CHECK;
return GetBuffer()[iChar];
}
__forceinline LPTSTR GetBuffer() { return m_allocator.CastTo(m_buffer); }
__forceinline LPCTSTR GetBuffer() const { return m_allocator.CastTo(m_buffer); }
Allocator& GetAllocator() { return m_allocator; }
size_t GetLength() const
{
return m_buffer != m_allocator.NullValue ? StringLength(GetBuffer()) : 0;
}
LPCTSTR GetString() const noexcept
{
if (m_buffer != m_allocator.NullValue) return GetBuffer();
else return TEXT("");
}
bool IsEmpty() const noexcept
{
return m_buffer == m_allocator.NullValue;
}
void Preallocate(SIZE_T nLength)
{
typename Allocator::Ptr newPtr;
if (m_buffer != m_allocator.NullValue)
{
newPtr = m_allocator.ReAlloc(m_buffer, nLength);
}
else
{
newPtr = m_allocator.Alloc(nLength);
}
if (newPtr != m_allocator.NullValue)
{
m_buffer = newPtr;
m_capacity = nLength;
}
else
{
TWSTRING_CHECK;
}
}
void SetAt(SIZE_T iChar, TCHAR ch)
{
if (iChar > GetLength())
{
TWSTRING_CHECK;
return;
}
GetBuffer()[iChar] = ch;
}
void SetString(LPCTSTR pszSrc, SIZE_T nLength)
{
if (nLength + 1 > GetAllocLength())
{
Preallocate(nLength + 1);
}
CopyChars(GetBuffer(), pszSrc, nLength);
}
void SetString(LPCTSTR pszSrc)
{
SetString(pszSrc, StringLength(pszSrc));
}
static SIZE_T StringLength(LPCTSTR psz) noexcept
{
return lstrlen(psz);
}
void Truncate(int nNewLength)
{
SetAt(nNewLength, TEXT('\0'));
}
const TCHAR& operator[](SIZE_T iChar) const
{
return GetAt(iChar);
}
operator LPCTSTR() const noexcept
{
return GetString();
}
#pragma endregion
void __cdecl AppendFormat(LPCTSTR pszFormat, ...)
{
va_list ap;
va_start(ap, pszFormat);
TinyWinString temp;
temp.FormatV(pszFormat, ap);
Append(temp);
}
enum CompareFlag
{
CompareFlagNone = 0,
LinguisticIgnoreCase = LINGUISTIC_IGNORECASE,
LinguisticIgnoreDiacritic = LINGUISTIC_IGNOREDIACRITIC,
NormIgnoreCase = NORM_IGNORECASE,
NormIgnoreKanaType = NORM_IGNOREKANATYPE,
NormIgnoreNonSpace = NORM_IGNORENONSPACE,
NormIgnoreSymbols = NORM_IGNORESYMBOLS,
NormIgnoreWidth = NORM_IGNOREWIDTH,
NormLinguisticCasing = NORM_LINGUISTIC_CASING,
#if (WINVER >= 0x0601)
SortDigitsasNumbbers = SORT_DIGITSASNUMBERS,
#endif
SortStringSort = SORT_STRINGSORT,
};
int Collate(LPCTSTR other, CompareFlag dwCmpFlags = CompareFlagNone) const
{
return Collate(other, ::GetThreadLocale(), dwCmpFlags);
}
int Collate(LPCTSTR other, LCID locale, CompareFlag dwCmpFlags = CompareFlagNone) const
{
switch (::CompareString(locale, dwCmpFlags, GetBuffer(), -1, other, -1))
{
case CSTR_LESS_THAN: return -1;
case CSTR_EQUAL: return 0;
case CSTR_GREATER_THAN: return 1;
default: TWSTRING_CHECK;
}
}
int CollateNoCase(LPCTSTR other) const
{
return Collate(other, ::GetThreadLocale(), NormIgnoreCase);
}
int CollateNoCase(LPCTSTR other, LCID locale) const
{
return Collate(other, locale, NormIgnoreCase);
}
int Compare(LPCTSTR other) const
{
return lstrcmp(GetString(), other);
}
int CompareNoCase(LPCTSTR other) const
{
return lstrcmpi(GetString(), other);
}
template <size_t cch>
void CopyTo(TCHAR(&buffer)) const
{
CopyTo(buffer, cch);
}
void CopyTo(LPTSTR buffer, SIZE_T cch) const
{
CopyChars(buffer, GetString(), static_cast<int>(cch));
}
#ifdef UNICODE
template <size_t cch>
int CopyTo(CHAR(&buffer)[cch], UINT cp = CP_THREAD_ACP) const
{
return CopyTo(buffer, cch, cp);
}
int CopyTo(LPSTR buffer, SIZE_T cch, UINT cp = CP_THREAD_ACP) const
{
return ::WideCharToMultiByte(cp, 0, GetBuffer(), -1, buffer, cch, NULL, NULL);
}
#endif
ptrdiff_t Find(LPCTSTR pszSub, SIZE_T iStart = 0) const noexcept
{
LPCTSTR n = StrStr(GetString() + iStart, pszSub);
return n == NULL ? -1 : n - GetString();
}
ptrdiff_t Find(TCHAR ch, SIZE_T iStart = 0) const noexcept
{
LPCTSTR n = StrChr(GetString() + iStart, ch);
return n == NULL ? -1 : n - GetString();
}
void __cdecl Format(LPCTSTR pszFormat, ...)
{
va_list ap;
va_start(ap, pszFormat);
FormatV(pszFormat, ap);
}
__forceinline void __cdecl FormatV(LPCTSTR pszFormat, va_list args)
{
#if TWSTRING_USE_SAFESTR
HRESULT hr;
do
{
hr = ::StringCchVPrintf(GetBuffer(), m_capacity, pszFormat, args);
if (hr == STRSAFE_E_INSUFFICIENT_BUFFER && m_capacity < (STRSAFE_MAX_CCH - BufferGrows))
{
Preallocate(m_capacity + BufferGrows);
}
else
{
break;
}
} while (true);
#else
do {
SIZE_T n = wvnsprintf(GetBuffer(), m_capacity, pszFormat, args);
if (static_cast<int>(n) < 0)
{
Preallocate(m_capacity + DefaultBufferLength);
}
else if (n < m_capacity)
{
SetAt(n, TEXT('\0'));
return;
}
else
TWSTRING_CHECK;
} while (m_capacity < 1024);
TWSTRING_CHECK;
#endif
}
void __cdecl FormatMessage(LPCTSTR pszFormat, ...)
{
va_list ap;
va_start(ap, pszFormat);
FormatMessageV(pszFormat, &ap);
}
void FormatMessageV(LPCTSTR pszFormat, va_list* pArgList)
{
do
{
DWORD ret = ::FormatMessage(FORMAT_MESSAGE_FROM_STRING, pszFormat, 0, 0, GetBuffer(), m_capacity, pArgList);
if (ret)
{
return;
}
else if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
Preallocate(m_capacity + DefaultBufferLength);
}
} while (m_capacity < 1024);
TWSTRING_CHECK;
}
int Insert(SIZE_T iIndex, LPCTSTR psz)
{
SIZE_T currentLength = GetLength();
if (iIndex >= currentLength)
TWSTRING_CHECK;
SIZE_T optLength = lstrlen(psz);
if (m_capacity < currentLength + optLength + 1)
Preallocate(currentLength + optLength + 1);
strmove(GetBuffer() + iIndex + optLength, GetBuffer() + iIndex, (currentLength - iIndex) + 1);
strmove(GetBuffer() + iIndex, psz, optLength);
return currentLength + optLength;
}
TinyWinString Left(SIZE_T nCount) const
{
if (nCount > GetLength())
TWSTRING_CHECK;
return TWSTRING_MOVE(TinyWinString(GetBuffer(), min(nCount, m_capacity), m_allocator));
}
TinyWinString& MakeLower()
{
::CharLowerBuff(GetBuffer(), GetLength());
return *this;
}
// TODO CStringT& MakeReverse();
TinyWinString& MakeUpper()
{
::CharUpperBuff(GetBuffer(), GetLength());
return *this;
}
TinyWinString Mid(SIZE_T iFirst, SIZE_T nCount) const
{
if (iFirst + nCount > GetLength())
{
TWSTRING_CHECK;
return TinyWinString();
}
return TWSTRING_MOVE(TinyWinString(&GetAt(iFirst), nCount, m_allocator));
}
TinyWinString Mid(SIZE_T iFirst) const
{
if (iFirst > GetLength())
{
TWSTRING_CHECK;
return TinyWinString();
}
return TWSTRING_MOVE(TinyWinString(&GetAt(iFirst), m_allocator));
}
int Remove(TCHAR const chRemove)
{
int count = 0;
SIZE_T start = 0;
ptrdiff_t i;
TinyWinString result(GetAllocLength(), m_allocator);
while ((i = Find(chRemove)) > 0)
{
result.Append(&GetAt(start), i);
start = i + 1;
i++;
count++;
}
*this = TWSTRING_MOVE(result);
return count;
}
ptrdiff_t ReverseFind(TCHAR ch) const noexcept
{
LPCTSTR s = StrRChr(GetBuffer(), GetBuffer() + GetLength(), ch);
if (s)
return s - GetBuffer();
else
return -1;
}
TinyWinString Right(SIZE_T nCount) const
{
SIZE_T len = GetLength();
if (nCount > len)
{
TWSTRING_CHECK;
return TinyWinString();
}
return TWSTRING_MOVE(TinyWinString(GetBuffer() + len - nCount, nCount, m_allocator));
}
TinyWinString Tokenize(LPCTSTR pszTokens, INT_PTR& iStart) const
{
INT_PTR iStartOrg = iStart;
ptrdiff_t pos = Find(pszTokens, iStart);
if (pos >= 0)
{
iStart = pos + StringLength(pszTokens);
return Mid(iStartOrg, iStart - iStartOrg - 1);
}
else
{
iStart = -1;
return TWSTRING_MOVE(TinyWinString(TEXT("")));
}
}
#if __cpp_rvalue_references >= 200610
TinyWinString& operator =(TinyWinString&& other) noexcept
{
Empty();
swap(other);
return *this;
}
#endif
TinyWinString& operator =(const TinyWinString& other)
{
SetString(other.GetString());
return *this;
}
TinyWinString& operator =(LPCTSTR other)
{
SetString(other);
return *this;
}
bool operator ==(LPCTSTR other) const
{
return Compare(other) == 0;
}
template <class A>
bool operator ==(const TinyWinString<A>& other) const
{
return Compare(other.GetString()) == 0;
}
bool operator !=(LPCTSTR other) const
{
return Compare(other) != 0;
}
template <class A>
bool operator !=(const TinyWinString<A>& other) const
{
return Compare(other.GetString()) != 0;
}
template <class A>
friend TinyWinString operator+(
const TinyWinString& str1,
const TinyWinString<A>& str2)
{
TinyWinString temp(str1.GetAllocLength() + str2.GetAllocLength());
temp.SetString(str1.GetString());
temp.Append(str2);
return TWSTRING_MOVE(temp);
}
friend TinyWinString operator+(
const TinyWinString& str1,
LPCTSTR psz2)
{
TinyWinString temp = str1;
temp.Append(psz2);
return temp;
}
friend TinyWinString operator+(
LPCTSTR psz1,
const TinyWinString& str2)
{
TinyWinString temp(psz1);
temp.Append(str2);
return temp;
}
friend TinyWinString operator+(
const TinyWinString& str1,
TCHAR ch2)
{
TinyWinString temp = str1;
temp.AppendChar(ch2);
return temp;
}
friend TinyWinString operator+(
TCHAR ch1,
const TinyWinString& str2)
{
TinyWinString temp(&ch1, 1);
temp.Append(str2);
return temp;
}
void DebugPrint() const
{
::OutputDebugString(this->GetString());
}
static void DebugPrint(LPCTSTR pszFormat, ...)
{
va_list ap;
va_start(ap, pszFormat);
TinyWinString temp;
temp.FormatV(pszFormat, ap);
::OutputDebugString(temp);
}
void DebugPrintln() const
{
::OutputDebugString((*this + TEXT("\n")));
}
static void DebugPrintln(LPCTSTR pszFormat, ...)
{
va_list ap;
va_start(ap, pszFormat);
TinyWinString temp;
temp.FormatV(pszFormat, ap);
temp.AppendChar(TEXT('\n'));
::OutputDebugString(temp);
}
inline void swap(TinyWinString& other) noexcept
{
std::swap(m_buffer, other.m_buffer);
std::swap(m_capacity, other.m_capacity);
std::swap(m_allocator, other.m_allocator);
}
typename Allocator::Ptr begin()
{
return GetBuffer();
}
typename Allocator::Ptr end()
{
return GetBuffer() + GetLength() + 1;
}
const typename Allocator::Ptr begin() const
{
return const_cast<TinyWinString*>(this)->begin();
}
const typename Allocator::Ptr end() const
{
return const_cast<TinyWinString*>(this)->end();
}
private:
Allocator m_allocator;
typename Allocator::Ptr m_buffer;
SIZE_T m_capacity;
friend class TinyWinStringTest;
};
typedef TinyWinString<> TWString;
#ifdef TWSTRING_NAMESPACE
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment