Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

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

Select an option

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

CommandLineToArgvAEx: an ANSI-Compatible Command-Line Parser in Windows

When writing Windows applications in C, it’s common to rely on the CommandLineToArgvW function to split the command line into individual arguments. However, this function only works with wide-character (UTF-16) strings. If your application handles ANSI or UTF-8 command lines, you need a compatible bridge. This article demonstrates how to implement CommandLineToArgvAEx, an ANSI-aware version that supports arbitrary code pages.


Overview

CommandLineToArgvAEx converts an ANSI (or UTF-8) command line into a list of arguments (argv[]) by:

  1. Converting the original ANSI string into a UTF-16 string.
  2. Calling CommandLineToArgvW to perform the actual parsing.
  3. Converting each wide-character argument back to the specified code page.

This approach leverages Windows’s built-in parsing logic while preserving compatibility with various encodings.


Implementation Highlights

The function works in three main stages:

  1. Conversion to UTF-16

    int wlen = MultiByteToWideChar(codepage, 0, lpCmdLine, -1, NULL, 0);
    LPWSTR lpWideCmdLine = (LPWSTR)LocalAlloc(LMEM_FIXED, wlen * sizeof(WCHAR));
    MultiByteToWideChar(codepage, 0, lpCmdLine, -1, lpWideCmdLine, wlen);
  2. Parsing with CommandLineToArgvW

    int argcW = 0;
    LPWSTR* argvW = CommandLineToArgvW(lpWideCmdLine, &argcW);
  3. Conversion back to ANSI

    int alen = WideCharToMultiByte(codepage, 0, argvW[i], -1, NULL, 0, NULL, NULL);
    argvA[i] = (LPSTR)LocalAlloc(LMEM_FIXED, alen);
    WideCharToMultiByte(codepage, 0, argvW[i], -1, argvA[i], alen, NULL, NULL);

Each argvA[i] is a separately allocated string that the caller must free using LocalFree.


Demonstration

The main() function retrieves the current console code page using GetConsoleOutputCP() and the full command line using GetCommandLineA(). It then calls CommandLineToArgvAEx to parse it:

UINT cp = GetConsoleOutputCP();
LPCSTR cmd = GetCommandLineA();
int argc = 0;
LPSTR* argv = CommandLineToArgvAEx(cmd, &argc, cp);

Finally, it prints each argument and cleans up the allocated memory.

Sample output:

Current code page: 65001
Raw CommandLineA: "example.exe" arg1 "multi word" arg3

Parsed arguments:
  [0] example.exe
  [1] arg1
  [2] multi word
  [3] arg3

Key Advantages

  • ✅ Supports UTF-8, ACP, or any specified code page
  • ✅ Uses standard Windows API for correctness
  • ✅ Requires no C runtime dependencies beyond <windows.h>

Conclusion

CommandLineToArgvAEx is a practical wrapper for developers who need consistent argument parsing across encodings in minimal or CRT-free Windows applications. It preserves the behavior of CommandLineToArgvW while adding full ANSI and UTF-8 support — ideal for lightweight tools and custom launchers.

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <shellapi.h>
#include <stdio.h>
// ----------------------------------------------
// CommandLineToArgvAEx
// - Parses ANSI/ASCII command line into argv[]
// - Internally uses CommandLineToArgvW
// - Supports arbitrary code page (CP_UTF8, CP_ACP, etc.)
// ----------------------------------------------
LPSTR* CommandLineToArgvAEx(LPCSTR lpCmdLine, int* pNumArgs, UINT codepage)
{
if (!lpCmdLine || !pNumArgs)
return NULL;
// Convert to UTF-16
int wlen = MultiByteToWideChar(codepage, 0, lpCmdLine, -1, NULL, 0);
if (wlen <= 0)
return NULL;
LPWSTR lpWideCmdLine = (LPWSTR)LocalAlloc(LMEM_FIXED, wlen * sizeof(WCHAR));
if (!lpWideCmdLine)
return NULL;
MultiByteToWideChar(codepage, 0, lpCmdLine, -1, lpWideCmdLine, wlen);
// Use CommandLineToArgvW
int argcW = 0;
LPWSTR* argvW = CommandLineToArgvW(lpWideCmdLine, &argcW);
LocalFree(lpWideCmdLine);
if (!argvW)
return NULL;
// Allocate array of char*
LPSTR* argvA = (LPSTR*)LocalAlloc(LMEM_FIXED, argcW * sizeof(LPSTR));
if (!argvA) {
LocalFree(argvW);
return NULL;
}
for (int i = 0; i < argcW; i++) {
int alen = WideCharToMultiByte(codepage, 0, argvW[i], -1, NULL, 0, NULL, NULL);
argvA[i] = (LPSTR)LocalAlloc(LMEM_FIXED, alen);
if (!argvA[i]) {
for (int j = 0; j < i; j++) LocalFree(argvA[j]);
LocalFree(argvA);
LocalFree(argvW);
return NULL;
}
WideCharToMultiByte(codepage, 0, argvW[i], -1, argvA[i], alen, NULL, NULL);
}
*pNumArgs = argcW;
LocalFree(argvW);
return argvA;
}
// ----------------------------------------------
// main
// Demonstrates parsing the current command line
// ----------------------------------------------
int main(void)
{
UINT cp = GetConsoleOutputCP();
LPCSTR cmd = GetCommandLineA();
printf("Current code page: %u\n", cp);
printf("Raw CommandLineA: %s\n\n", cmd);
int argc = 0;
LPSTR* argv = CommandLineToArgvAEx(cmd, &argc, cp);
if (!argv) {
fprintf(stderr, "CommandLineToArgvAEx failed.\n");
return 1;
}
printf("Parsed arguments:\n");
for (int i = 0; i < argc; i++)
printf(" [%d] %s\n", i, argv[i]);
// Clean up
for (int i = 0; i < argc; i++)
LocalFree(argv[i]);
LocalFree(argv);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment