はじめに
Delphi でコマンドラインパラメータを独自に処理してみます。
コマンドラインパラメータの処理
Delphi でアプリケーションに渡されたコマンドラインパラメータを処理するには次のルーチンを使います。
| メンバ | 説明 |
|---|---|
| CmdLine | アプリケーションの呼び出し時に指定されたコマンドライン引数を示します。 |
| ParamCount | コマンドラインで渡されたパラメータの数を返します。 |
| ParamStr | 指定されたパラメータをコマンドラインから取得して返します。 |
| FindCmdLineSwitch | 文字列がアプリケーションのコマンドライン引数として渡されたかどうかを判定します。 |
ParamStr(0) には実行された EXE のフルパス名が入ります。
See also:
Delphi におけるコマンドラインパラメータ処理の問題点
しかしながら、コマンドラインパラメータ処理でたまに問題が起きる事があります。
program test;
{$APPTYPE CONSOLE}
uses
System.SysUtils, WinApi.Windows;
begin
var RawCmd := GetCommandLine;
Writeln('Raw: ', RawCmd);
Writeln('ParamCount: ', ParamCount);
for var i:=1 to ParamCount do
Writeln('ParamStr(', i, '): ', ParamStr(i));
end.
実際に渡されたパラメータ文字列と、Delphi が処理したパラメータを表示するコンソールアプリケーションで確認してみます。
問題点 1: ダブルクォーテーションの処理
文字としてのダブルクォーテーションを渡す方法がありません。test "abc"def" を実行するとこうなります。
Raw: test "abc"def"
ParamCount: 1
ParamStr(1): abcdef
「ダブルクォーテーションは 2 つ重ねなきゃ!」と思われたでしょう?
Raw: test "abc""def"
ParamCount: 1
ParamStr(1): abcdef
そういう問題ではないのです。もちろん \ でエスケープしてもダメなのです。
Raw: test "abc\"def"
ParamCount: 1
ParamStr(1): abc\def
問題点 2: 空文字列
空文字列パラメータが無視されてしまいます。test "" を実行するとこうなります。
Raw: test ""
ParamCount: 0
コマンドラインパラメータを独自に処理する
コマンドラインパラメータを独自に処理する TCmdLine レコードを作ってみました。
unit uCmdLine;
interface
uses
System.SysUtils, System.Generics.Collections, Winapi.Windows;
type
TCmdLine =
record
RawCmd: string;
Data: array of string;
public
procedure Init;
function ParamStr(Idx: Integer): string;
function ParamCount: Integer;
end;
implementation
{ TCmdLine }
procedure TCmdLine.Init;
function SplitCommandLine(const S: string): TArray<string>;
begin
var L := TList<string>.Create;
try
var i := 1;
var SLen := S.Length;
while i <= SLen do
begin
while (i <= SLen) and (S[i] = ' ') do
Inc(i);
if i > SLen then
Break;
var Cur := '';
var InQuote := False;
while i <= SLen do
begin
if (S[i] = '"') then
begin
if InQuote and (i < SLen) and (S[i + 1] = '"') then
begin
Cur := Cur + '"';
Inc(i);
end
else
InQuote := not InQuote;
end
else if (S[i] = ' ') and not InQuote then
Break
else
Cur := Cur + S[i];
Inc(i);
end;
L.Add(Cur);
end;
Result := L.ToArray;
finally
L.Free;
end;
end;
begin
RawCmd := Trim(GetCommandLine);
Data := SplitCommandLine(RawCmd);
Data[0] := System.ParamStr(0);
end;
function TCmdLine.ParamCount: Integer;
begin
Result := Length(Data) - 1;
end;
function TCmdLine.ParamStr(Idx: Integer): string;
begin
result := Data[Idx];
end;
end.
次の記事に拡張版のコードがあります。
使い方は以下のようになります。コマンドラインサポートメンバを使った時とほぼ変わりませんね。
program Test;
{$APPTYPE CONSOLE}
uses
System.SysUtils, uCmdLine;
begin
var CmdLine: TCmdLine;
CmdLine.Init;
Writeln('Raw: ', CmdLine.RawCmd);
Writeln('ParamCount: ', CmdLine.ParamCount);
for var i:=1 to CmdLine.ParamCount do
Writeln('ParamStr(', i, '): ', CmdLine.ParamStr(i));
end.
ダブルクォーテーションの処理も、
Raw: test "abc""def"
ParamCount: 1
ParamStr(1): abc"def
空文字列パラメータも大丈夫です。
Raw: test ""
ParamCount: 1
ParamStr(1):
気になったので System.ParamCount() と System.ParamStr() の実装を眺めてみたのですが、TCmdLine の実装とは結構違っていました。
おわりに
多くの場合、標準のルーチンで事足りるのですが、たまにこうやって独自に処理を行わなければならない事があります。
See also: