Converting Command Line String to Args[] using CommandLineToArgvW() API

,

In testing a command line I recently wanted to verify that the string passed on the command line was converted to the args[] array that was passed to Main(string[] args).  For example, given the command line

Compress.exe /v:ReallyMakeItSmall myfile.txt “Read ME.txt”

What does the call to Main(string args[]) resolve args to?  There are two ways to test this.  Firstly, you can start a the proces with the corresponding commandline and then test the args array.  But this is cumbersome because data needs to be passed between the new process and the test process.

The alternate solution is to use the CommandLineToArgW() API.  This API converts a string into is args.  The problem, however, is that it is a rather cumbersome API to call.  The declaration is as follows:

[DllImport(“shell32.dll”, SetLastError = true)]
static extern IntPtr CommandLineToArgvW(
[MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, out int pNumArgs);

It is great that it claims to return the arguments but how the heck does one get at them?  pNumArgs is presumably no help and IntPtr isn’t exactly an array of strings either.  It turns out that you need to marshal the data back manually, as demonstrated in the following class:

using System;
using System.Runtime.InteropServices;

namespace Dnp
{
public abstract class CommandLineHandler
{
[DllImport(“shell32.dll”, SetLastError = true)]
static extern IntPtr CommandLineToArgvW(
[MarshalAs(UnmanagedType.LPWStr)] string lpCmdLine, out int pNumArgs);

public static string[] CommandLineToArgs(string commandLine)
{
string executableName;
return CommandLineToArgs(commandLine, out executableName);
}

public static string[] CommandLineToArgs(string commandLine, out string executableName)
{
int argCount;
IntPtr result;
string arg;
IntPtr pStr;
result = CommandLineToArgvW(commandLine, out argCount);

if (result == IntPtr.Zero)
{
throw new System.ComponentModel.Win32Exception();
}
else
{
try
{
// Jump to location 0*IntPtr.Size (in other words 0).
pStr = Marshal.ReadIntPtr(result, 0 * IntPtr.Size);
executableName = Marshal.PtrToStringUni(pStr);

// Ignore the first parameter because it is the application
// name which is not usually part of args in Managed code.
string[] args = new string[argCount-1];
for (int i = 0; i < args.Length; i++)
{
pStr = Marshal.ReadIntPtr(result, (i+1) * IntPtr.Size);
arg = Marshal.PtrToStringUni(pStr);
args[i] = arg;
}

return args;
}
finally
{
Marshal.FreeHGlobal(result);
}
}

}

// …
}
}

Not exactly obvious.

UPDATE 6/6/2005

Added try-finally block and a call to Marshal.FreeHGlobal() thanks to comment by Atif Aziz.  For reference on P/Invoke, SetLastError, and Win32Exception I appreciate Shawn Van Ness comments here.

4 responses to “Converting Command Line String to Args[] using CommandLineToArgvW() API

  1. Don’t forget to call Marshal.FreeHGlobal on the pointer returned by CommandLineToArgvW. Otherwise you’ll create a memory leak.

    Quote from SDK documentation: "It is the caller’s responsibility to free the memory used by the argument list when it is no longer needed. To free the memory, use a single call to the GlobalFree function."

Leave a Reply

Your email address will not be published. Required fields are marked *