Replace CString + unsafe sprintf with safe string formatting — std or Boost
Classification¶
Abandoned by operator decision on 2026-05-21. This historical record preserves the old Boost/POCO analysis only as provenance; do not promote it without a new active item.
Summary¶
CString is used for essentially all string operations, coupling core logic to
MFC. sprintf/_stprintf with fixed-size TCHAR buf[N] buffers are present
throughout and carry classic buffer-overflow risk when format output exceeds the
buffer size.
Note: REF-023 covers the immediate safety fix (replace sprintf with
_stprintf_s / CString::Format / StringCchPrintf). This issue covers the
longer-term architectural question: moving away from CString toward standard
string types. Two paths are documented.
Affected Files¶
| File | Current usage |
|---|---|
srchybrid/AsyncSocketEx.h:133,179,186 |
const CString &sSocketAddress in API signatures |
srchybrid/OtherFunctions.h:113-115 |
GetNextString(CString, LPCTSTR, int&) tokeniser |
srchybrid/OtherFunctions.h:120-145 |
Multiple CastItoXBytes(uint16/32/64) overloads + SecToTimeLength |
| 50+ additional files | CString used for all string manipulation |
Current Problematic Pattern¶
// Fixed-size buffer — overflows if output exceeds 64 chars
TCHAR buf[64];
_stprintf(buf, _T("%u KB/s upload, %u KB/s download"), upKB, downKB);
Option A — CString::Format + StringCchPrintf (no new dependencies)¶
Least disruptive. Stay within MFC ecosystem. No new dependencies.
Replace unsafe sprintf/_stprintf with:
// Option 1: CString::Format — no buffer, no overflow
CString msg;
msg.Format(_T("%u KB/s upload, %u KB/s download"), upKB, downKB);
// Option 2: StringCchPrintf — safe fixed-buffer version (shlwapi.h)
TCHAR buf[256];
StringCchPrintf(buf, _countof(buf), _T("%u KB/s upload, %u KB/s download"), upKB, downKB);
// Truncates to fit — never overflows, always null-terminates
// Replace CString tokeniser with MFC AfxExtractSubString or manual find:
CString token;
int pos = 0;
while (AfxExtractSubString(token, input, pos++, _T(','))) { ... }
Pros: Minimal code churn; stays with CString which is used everywhere;
CString::Format is already exception-safe and unbounded.
Cons: Still MFC-coupled; doesn't help with non-UI code portability.
Option B — std::wstring / std::string + std::format (C++20)¶
No Boost required. MSVC supports std::format since VS 2022 17.1.
#include <format>
#include <string>
// Replace fixed-size sprintf — type-safe, no buffer
std::wstring msg = std::format(L"{} KB/s upload, {} KB/s download", upKB, downKB);
// Tokeniser — std::ranges::views::split (C++20)
auto tokens = input | std::views::split(L',');
// Replace CastItoXBytes overloads — one template
template<typename T>
std::wstring CastItoXBytes(T count, bool isK = false) {
return isK ? std::format(L"{} KB", count / 1024)
: std::format(L"{} B", count);
}
CString interop during migration:
// Convert std::wstring ↔ CString
std::wstring toStd(const CString& s) { return std::wstring((LPCTSTR)s); }
CString toCStr(const std::wstring& s) { return CString(s.c_str()); }
Option C — boost::format + boost::algorithm (requires Boost)¶
Use only if Boost is already adopted for REF-008.
#include <boost/format.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
// Replace fixed-size sprintf
std::string msg = boost::str(
boost::format("%1% KB/s upload, %2% KB/s download") % upKB % downKB);
// Replace CString tokeniser
std::vector<std::string> tokens;
boost::algorithm::split(tokens, input, boost::is_any_of(",;"));
// Type-safe numeric conversion
int port = boost::lexical_cast<int>(portString);
Note: boost::format is significantly slower than std::format or
CString::Format due to runtime type-erasure. Prefer Option A or B for hot paths.
Recommendation¶
- Immediate safety (now): Apply REF-023 first — replace all
sprintf/_stprintfwith_stprintf_sorCString::Format. This is a prerequisite. - Short-term (Option A): Use
CString::Formatfor new code in MFC-coupled files; safe and zero-churn. - Long-term (Option B): Migrate utility functions (
OtherFunctions.hoverloads, tokenisers) tostd::wstring+std::formatas files are refactored for other reasons. - Avoid Option C unless Boost is already a dependency —
boost::formatis slower andstd::formatcovers all the same ground.
Migration Strategy¶
- Do not do a bulk CString→wstring replace; it will break too much at once.
- Replace in refactored files first (those already touched by other issues).
- Target
OtherFunctions.h/cppoverloads early — high ROI for template collapse. - Add conversion helpers
toStd()/toCStr()for interop during transition.
Files¶
srchybrid/OtherFunctions.h/OtherFunctions.cpp— primary targetsrchybrid/AsyncSocketEx.h— API signatures (after REF-008)- 50+ consumer files — migrate gradually
Acceptance Criteria¶
- [ ] REF-023 (unsafe sprintf) completed first
- [ ]
OtherFunctions.hCastItoXBytesoverloads collapsed to a single template - [ ]
GetNextStringtokeniser uses safe string API - [ ] No new
sprintf/_stprintfcalls introduced in migrated files - [ ]
CString::Formatorstd::formatused for all new formatted string construction