Delphi commandline oddity with double-quoted arguments
Posted by jpluimers on 2011/07/14
Boy, I was wrong. Somewhere in the back of my head I knew it.
ParamStr goes from zero (the name of the EXE) through ParamCount (the last parameter). Duh :)
I’ll keep this so you can have a good laugh:
When you add double quoted arguments to the commandline of your Delphi app, strange things can happen: the first parameter seems to be gone, while it is there.
It appears that the ParamCount/ParamStr logic fails here, and cannot be repaired because of backward compatibility.
Lets look at this commandline:
“..\bin\DumpCommandLine.exe” “C:\Program Files\test.xml” “C:\Program Files\meMySelfAndI.xml”
The demo code below will output something like this:
ParamCount: 2 ParamStr(): 0 C:\develop\DumpCommandLine\bin\DumpCommandLine.exe 1 C:\Program Files\test.xml CommandLine: "..\bin\DumpCommandLine.exe" "C:\Program Files\test.xml" "C:\Program Files\meMySelfAndI.xml" CommandLineStrings.Count: 3 CommandLineStrings[]: 0 ..\bin\DumpCommandLine.exe 1 C:\Program Files\test.xml 2 C:\Program Files\meMySelfAndI.xml
You see that regular ParamCount/ParamStr calls will mis the “C:\Program Files\test.xml” parameter.
But getting it through CommandLineStrings will correctly get it.
This is the dump code:
unit CommandlineDemoUnit;
interface
procedure DumpCommandLineToConsole;
implementation
uses
Classes, CommandlineUnit;
procedure DumpCommandLineToConsole;
var
CommandLineStrings: TStrings;
I: Integer;
begin
Writeln('ParamCount:', #9, ParamCount);
Writeln('ParamStr():');
for I := 0 to ParamCount - 1 do
Writeln(I, #9, ParamStr(I));
Writeln('CommandLine:');
Writeln(CommandLine);
CommandLineStrings := CreateCommandLineStrings();
Writeln('CommandLineStrings.Count:', #9, CommandLineStrings.Count);
Writeln('CommandLineStrings[]:');
try
for I := 0 to CommandLineStrings.Count - 1 do
Writeln(I, #9, CommandLineStrings[I]);
finally
CommandLineStrings.Free;
end;
end;
end.
And this the code to get the CommandLine and CommandLineStrings, which will fill a TStrings result using the CommaText property (it uses a default QuoteChar of of double quote #34 and Delimiter of space #32, this will work nicely):
unit CommandlineUnit; interface uses Classes; function CommandLine: string; function CreateCommandLineStrings: TStrings; implementation uses Windows; function CommandLine: string; begin Result := GetCommandLine(); end; function CreateCommandLineStrings: TStrings; begin Result := TStringList.Create(); Result.CommaText := CommandLine; end; end.
Note that you need to manually free the TStrings object to avoid memory leaks.
–jeroen






WarrenP said
I’m glad I’m not the only one who experiences PEBCAK. (“Problem exists between chair and keyboard”, that is, in your brain.)
:-)
Warren
jpluimers said
I’m so glad to be human :)
Alex said
May be I’ve misread something, but…. hit F1 – http://docwiki.embarcadero.com/VCL/en/System.ParamStr
“ParamStr returns the parameter from the command line that corresponds to Index, or an empty string if Index is greater than ParamCount”
Which means Index for ParamStr ranges from 0 to ParamCount, not ParamCount – 1, as in your code.
jpluimers said
It also tells that ParamStr(0) is always the name of the executable, and that ParmStr(2) gives you the Parameter at index 2.
So ParamStr() goes from 1 through N, and 0 is a special case.
So I trapped into my own pit :(
Somewhere in the back of my head I knew that, but not on the conscious level. Thanks for clearing that up.
Tim said
With great respect, this is nonsense. First, the paramcount does not include the ‘0’ parameter, so your loop should be ‘for i:=1 to paramcount’. Then the last parameter (not the first parameter) would no longer be missing. Second, I do not believe that double-quotes have anything to do with the problem: your original demo program also fails when invoked with ‘demo.exe a b’.
Remy Lebeau said
Your demo is misusing ParamCount(), that is why it misses the last parameter. ParamCount() does not include ParamStr(0), which is always the calling .exe path and filename. ParamStr() is 1-indexed for accessing parameters, but the demo treats it as 0-indexed. By subtracting 1 from the count, it skips ParamStr(2), which is the last parameter, not ParamStr(1).
There are other legitimate quote-related errors in the ParamCount/ParamStr parser that you are not touching on at all. They have been reported as QC #3946, #27451, and #43340. See the following discussion for workarounds:
http://groups.google.com/group/borland.public.delphi.language.delphi.win32/browse_thread/thread/dc44b1f75f059361/c208b2097cefb852
tondrej said
My first comment disappeared, so I’ll try again:
The oddity is there because you exit the for loop too early and therefore the last parameter is missed.
In your example:
ParamCount = 2
ParamStr(0) = “…\DumpCommandLine.exe” (full path to this executable)
ParamStr(1) = ‘C:\Program Files\test.xml’
ParamStr(2) = ‘C:\Program Files\meMySelfAndI.xml’
The oddity is simply that ParamStr is 1-based and the special element 0 always points to “this executable”. Double-quoting is an unrelated issue.
Edwin van der Kraan said
Instead of You see that regular ParamCount/ParamStr calls will mis the “C:\Program Files\test.xml” parameter, didn’t you mean You see that regular ParamCount/ParamStr calls will mis the “C:\Program Files\meMySelfAndI.xml” parameter ?
Edwin van der Kraan said
And mis should of course be miss.
Uwe Raabe said
Instead of TStrings CreateCommandLineStrings may return a TArray.
tondrej said
The code using ParamCount and ParamStr should be:
for I := 1 to ParamCount do
Writeln(I, #9, ParamStr(I));
It’s an oddity because ParamStr is 1-based and the additional element 0 has a special meaning, “this executable”.
Marjan said
Uh, don’t you have to loop to ParamCount and not ParamCount – 1? The actual params start at index 1 while index 0 points to program name? And your code doesn’t miss the first parameter, it misses the last? The output it gives shows test.xml but not mymyselfandi.xml.
ruurd said
What happens if you use single quotes? And yes – technically speaking ‘Program Files’ is by far the STUPIDEST thing Microsoft ever came up with…