This article walks through a minimal Windows launcher executable—useful as a wrapper that forwards your process’s command line to a target program after adjusting environment variables (e.g., MSYS2/MSYS paths), while avoiding any dependency on the C/C++ runtime (CRT). The code uses only Win32 APIs and a hand-rolled zero-fill to keep the binary lean and linkable with -nostdlib.
-
Skips
argv[0]: Parses the raw process command line (GetCommandLineA) and returns a pointer to the first real argument. This means you can invoke the wrapper aswrapper.exe git statusand have the child process receive justgit status. -
MSYS-centric PATH: Prepends a curated MSYS2/MSYS path segment to the existing
PATHso tools likebash,perl, orgitresolve as expected. -
Environment hint: Sets
MSYSTEM=MSYSto guide MSYS’s runtime behavior. -
Ctrl-C handling: Installs a console control handler that ignores
CTRL_C_EVENTandCTRL_BREAK_EVENTso the wrapper doesn’t die; the child process handles them. -
No CRT required: Replaces typical helpers (
memset, formatted I/O) with tiny custom routines:ZeroBufferwrites zeros without pulling in the CRT.PrintErrorMessagebuilds a compact ASCII error line and writes toSTD_ERROR_HANDLEdirectly.
-
Spawns and mirrors exit code: Launches the child with
CreateProcessA, waits withWaitForSingleObject, then returns the child’s exit code viaExitProcess.
Windows provides the full command line as a single string. To avoid CRT parsing, ShiftCmdline walks it character-by-character, respecting quotes and backslashes:
- Tracks whether it’s inside a double-quoted segment.
- Treats spaces/tabs outside quotes as delimiters.
- When it finds the boundary after
argv[0], it returns the pointer to the first “real” argument. - If there are no arguments, the program exits cleanly.
This behavior makes the wrapper transparent: whatever you pass after the wrapper’s name is forwarded unchanged (including proper quote semantics).
The launcher constructs a new PATH:
- A fixed prefix of MSYS locations (e.g.,
C:\msys64\ucrt64\bin,C:\msys64\usr\bin, etc.). - The current
PATH(if present).
It allocates buffers with LocalAlloc, concatenates with lstrcpyA/lstrcatA, then updates the process environment via SetEnvironmentVariableA("PATH", ...).
STARTUPINFOAandPROCESS_INFORMATIONare zero-initialized usingZeroBufferto avoid dragging inmemset.- The child process is created by
CreateProcessA(NULL, shiftedCmd, ...)so the child’s image is resolved from the newPATH. - Standard handles are inherited (
bInheritHandles=TRUE) to keep I/O flowing naturally. - After the child exits, the launcher fetches and returns the child’s exit code.
To keep the binary small, error messages are built by hand:
- Appends
" (Error NNN)"to a static buffer. - Writes directly with
WriteFiletoSTD_ERROR_HANDLE. - Avoids
printf,fprintf, orFormatMessage.
Because there’s no CRT, you must provide your own entry point and link only against kernel32:
x86_64-w64-mingw32-g++ msys_launcher.cpp -nostdlib -fno-exceptions -fno-rtti \
-Wl,-e,WinMainCRTStartup -lkernel32-nostdlibcuts out CRT initialization.-Wl,-e,WinMainCRTStartupsets the raw entry point to our function.-fno-exceptions -fno-rttikeeps C++ overhead out (the code uses only C-style constructs).-lkernel32is enough for the Win32 APIs used here.
- You need a tiny wrapper that adjusts environment or arguments and then launches another tool.
- You’re operating in portable or constrained environments where pulling in the CRT is undesirable.
- You want predictable quoting/escaping behavior that mirrors Windows’ native command line semantics.
- Adjust the
prefixlist for different MSYS2 subsystems (e.g.,mingw64\bin) or other toolchains. - If you want the wrapper to launch a specific program rather than rely on
PATHlookup, pass that program as thelpApplicationNametoCreateProcessAand keepshiftedCmdas the command line. - To forward signals differently, invert or customize
IgnoreCtrlHandler.
That’s it—a compact, CRT-free launcher that makes MSYS-based workflows (and other toolchains) easier to bootstrap on Windows.