The Wiert Corner – irregular stream of stuff

Jeroen W. Pluimers on .NET, C#, Delphi, databases, and personal interests

  • My badges

  • Twitter Updates

  • My Flickr Stream

  • Pages

  • All categories

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 2,513 other followers

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

13 Responses to “Delphi commandline oddity with double-quoted arguments”

  1. 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

  2. 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.

  3. 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’.

  4. 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

  5. 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.

  6. 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 ?

  7. Uwe Raabe said

    Instead of TStrings CreateCommandLineStrings may return a TArray.

  8. 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”.

  9. 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.

  10. 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…

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

 
%d bloggers like this: