Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save aont/a5acb2020cda9126ef2e3a54f82132e3 to your computer and use it in GitHub Desktop.

Select an option

Save aont/a5acb2020cda9126ef2e3a54f82132e3 to your computer and use it in GitHub Desktop.

A Tiny ArgvToCommandLine for Windows Without the CRT

This snippet shows how to build a Python-style list2cmdline on Windows—without relying on the C/C++ runtime (CRT). It constructs a single command-line string from an argv array, following the same quoting and escaping rules used by Python’s subprocess.list2cmdline and the Windows command-line parser.


What the code does

  1. Stays CRT-free. The code compiles with -nostdlib style flags because it only uses Win32 APIs (LocalAlloc, LocalFree, WriteFile) and tiny helper functions:

    • StrLenA and StrChrA: minimal strlen/strchr equivalents.
    • AppendChar: grows a heap buffer with LocalAlloc, doubling capacity as needed.
    • PrintA: writes bytes directly to STDOUT via WriteFile.
  2. Converts argv[] → command line. ArgvToCommandLine(int argc, LPSTR* argv) iterates over arguments and produces a single command line string with spaces between items, quoting and escaping as necessary.

  3. Implements Windows quoting rules. The logic mirrors Python’s and the Windows CreateProcess parsing rules:

    • Quote an argument if it is empty or contains space or tab.
    • Backslashes before a quote must be doubled. Example: x\"y in the input becomes x\\\"y in the command line.
    • Trailing backslashes inside quoted args must also be doubled so the closing quote is preserved.
    • Otherwise, characters are appended as-is.
  4. Provides a tiny demo. WinMain builds argv manually:

    {"hello", "a b c", "x\"y"}

    And prints:

    cmdline=hello "a b c" x\\\"y
    

    (The exact escapes reflect the doubling rules.)


Why these rules matter

Windows does not pass an argv[] array to a new process. Instead, it passes a single command-line string; the C runtime of the child usually splits that string into argv[]. If you’re CRT-free—or you’re launching other programs that are using the CRT—you must construct a string that will be parsed the way you intend. The backslash-and-quote dance ensures that:

  • Spaces/tabs are preserved inside quoted arguments.
  • Literal quotes survive parsing.
  • Trailing backslashes don’t accidentally escape the closing quote.

Structure at a glance

  • Helpers (no CRT):

    • StrLenA, StrChrA: minimal string ops.
    • AppendChar: auto-growing LocalAlloc buffer with NUL termination.
  • Core function:

    • ArgvToCommandLine: loops arguments, decides quoting, counts pending backslashes, doubles them when before a " or at the end of a quoted arg, appends characters, and closes quotes if opened.
  • I/O:

    • PrintA writes bytes directly to the console handle.

Practical tips & edge cases

  • When to quote: empty string, contains ' ' or '\t'. No need to quote for other punctuation (e.g., commas).
  • Backslashes before quotes: \\\" in the final string results in \" in the parsed argument.
  • Trailing backslashes in quoted args: must be doubled; otherwise they’d escape the closing ".
  • Memory ownership: the returned buffer is allocated with LocalAlloc; the caller frees it with LocalFree.

Where this is useful

  • Tiny launchers and wrappers that avoid the CRT.
  • Custom process creation where you must compose the command-line string yourself.
  • Reproducing Python behavior on Windows for consistent interop with subprocess.

This compact, dependency-free approach gives you precise control over how arguments are serialized on Windows—exactly what you need when you can’t or don’t want to pull in the CRT.

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
// ============================================================
// Minimal helpers (no CRT)
// ============================================================
static SIZE_T StrLenA_NoCRT(LPCSTR s) {
SIZE_T n = 0;
if (!s) return 0;
while (s[n]) n++;
return n;
}
static SIZE_T StrLenW_NoCRT(LPCWSTR s) {
SIZE_T n = 0;
if (!s) return 0;
while (s[n]) n++;
return n;
}
static LPSTR StrChrA_NoCRT(LPCSTR s, CHAR c) {
if (!s) return NULL;
while (*s) {
if (*s == c) return (LPSTR)s;
s++;
}
return NULL;
}
static LPWSTR StrChrW_NoCRT(LPCWSTR s, WCHAR c) {
if (!s) return NULL;
while (*s) {
if (*s == c) return (LPWSTR)s;
s++;
}
return NULL;
}
static BOOL AppendCharA_NoCRT(LPSTR* buf, SIZE_T* cap, SIZE_T* len, CHAR c) {
if (*len + 1 >= *cap) {
SIZE_T newcap = (*cap == 0 ? 64 : *cap * 2);
LPSTR newbuf = (LPSTR)LocalAlloc(LMEM_FIXED, newcap * sizeof(CHAR));
if (!newbuf) return FALSE;
if (*buf) {
CopyMemory(newbuf, *buf, *len * sizeof(CHAR));
LocalFree(*buf);
}
*buf = newbuf;
*cap = newcap;
}
(*buf)[(*len)++] = c;
(*buf)[*len] = '\0';
return TRUE;
}
static BOOL AppendCharW_NoCRT(LPWSTR* buf, SIZE_T* cap, SIZE_T* len, WCHAR c) {
if (*len + 1 >= *cap) {
SIZE_T newcap = (*cap == 0 ? 64 : *cap * 2);
LPWSTR newbuf = (LPWSTR)LocalAlloc(LMEM_FIXED, newcap * sizeof(WCHAR));
if (!newbuf) return FALSE;
if (*buf) {
CopyMemory(newbuf, *buf, *len * sizeof(WCHAR));
LocalFree(*buf);
}
*buf = newbuf;
*cap = newcap;
}
(*buf)[(*len)++] = c;
(*buf)[*len] = L'\0';
return TRUE;
}
// ============================================================
// ArgvToCommandLineA/W
// Emulate Python subprocess.list2cmdline-style quoting
// ============================================================
LPSTR ArgvToCommandLineA(int argc, LPSTR* argv) {
LPSTR result = NULL;
SIZE_T len = 0;
SIZE_T cap = 0;
#define APPEND_A(ch) \
do { \
if (!AppendCharA_NoCRT(&result, &cap, &len, ch)) { \
if (result) LocalFree(result); \
return NULL; \
} \
} while (0)
for (int i = 0; i < argc; i++) {
if (i > 0) APPEND_A(' ');
LPCSTR arg = argv[i];
SIZE_T arglen = StrLenA_NoCRT(arg);
BOOL needquote =
(arglen == 0) ||
StrChrA_NoCRT(arg, ' ') ||
StrChrA_NoCRT(arg, '\t');
if (needquote) APPEND_A('"');
SIZE_T bs_count = 0;
for (SIZE_T j = 0; j < arglen; j++) {
CHAR c = arg[j];
if (c == '\\') {
bs_count++;
} else if (c == '"') {
for (SIZE_T k = 0; k < bs_count * 2 + 1; k++) {
APPEND_A('\\');
}
bs_count = 0;
APPEND_A('"');
} else {
for (SIZE_T k = 0; k < bs_count; k++) {
APPEND_A('\\');
}
bs_count = 0;
APPEND_A(c);
}
}
if (bs_count > 0) {
SIZE_T count = needquote ? bs_count * 2 : bs_count;
for (SIZE_T k = 0; k < count; k++) {
APPEND_A('\\');
}
}
if (needquote) APPEND_A('"');
}
#undef APPEND_A
return result;
}
LPWSTR ArgvToCommandLineW(int argc, LPWSTR* argv) {
LPWSTR result = NULL;
SIZE_T len = 0;
SIZE_T cap = 0;
#define APPEND_W(ch) \
do { \
if (!AppendCharW_NoCRT(&result, &cap, &len, ch)) { \
if (result) LocalFree(result); \
return NULL; \
} \
} while (0)
for (int i = 0; i < argc; i++) {
if (i > 0) APPEND_W(L' ');
LPCWSTR arg = argv[i];
SIZE_T arglen = StrLenW_NoCRT(arg);
BOOL needquote =
(arglen == 0) ||
StrChrW_NoCRT(arg, L' ') ||
StrChrW_NoCRT(arg, L'\t');
if (needquote) APPEND_W(L'"');
SIZE_T bs_count = 0;
for (SIZE_T j = 0; j < arglen; j++) {
WCHAR c = arg[j];
if (c == L'\\') {
bs_count++;
} else if (c == L'"') {
for (SIZE_T k = 0; k < bs_count * 2 + 1; k++) {
APPEND_W(L'\\');
}
bs_count = 0;
APPEND_W(L'"');
} else {
for (SIZE_T k = 0; k < bs_count; k++) {
APPEND_W(L'\\');
}
bs_count = 0;
APPEND_W(c);
}
}
if (bs_count > 0) {
SIZE_T count = needquote ? bs_count * 2 : bs_count;
for (SIZE_T k = 0; k < count; k++) {
APPEND_W(L'\\');
}
}
if (needquote) APPEND_W(L'"');
}
#undef APPEND_W
return result;
}
// ============================================================
// Minimal output helpers (no CRT)
// ============================================================
void PrintA(LPCSTR s) {
DWORD written = 0;
WriteFile(
GetStdHandle(STD_OUTPUT_HANDLE),
s,
(DWORD)StrLenA_NoCRT(s),
&written,
NULL
);
}
void PrintW(LPCWSTR s) {
DWORD written = 0;
WriteFile(
GetStdHandle(STD_OUTPUT_HANDLE),
s,
(DWORD)(StrLenW_NoCRT(s) * sizeof(WCHAR)),
&written,
NULL
);
}
// ============================================================
// Minimal entry point (no CRT)
// ============================================================
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
UNREFERENCED_PARAMETER(hInstance);
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
UNREFERENCED_PARAMETER(nCmdShow);
{
CHAR* argsA[] = {
(CHAR*)"hello",
(CHAR*)"a b c",
(CHAR*)"x\"y",
(CHAR*)"C:\\path\\",
NULL
};
LPSTR cmdlineA = ArgvToCommandLineA(4, argsA);
PrintA("cmdlineA=");
if (cmdlineA) {
PrintA(cmdlineA);
LocalFree(cmdlineA);
} else {
PrintA("(allocation failed)");
}
PrintA("\r\n");
}
{
WCHAR* argsW[] = {
(WCHAR*)L"hello",
(WCHAR*)L"a b c",
(WCHAR*)L"x\"y",
(WCHAR*)L"C:\\path\\",
NULL
};
LPWSTR cmdlineW = ArgvToCommandLineW(4, argsW);
PrintW(L"cmdlineW=");
if (cmdlineW) {
PrintW(cmdlineW);
LocalFree(cmdlineW);
} else {
PrintW(L"(allocation failed)");
}
PrintW(L"\r\n");
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment