Windows のコマンド ライン引数処理

# Windows と Unix(macOS,Linuxなど) の違い
Unix ではプロセスを起動するとき、コマンドラインとして「文字列の一覧」を渡すので、それが main 関数の引数になります。

Windows ではプロセスを起動するとき、コマンドラインは「文字列が１つ」なので、呼び出された側が、この文字列を解析して main 関数の引数にします。よって、 **main 関数の引数はアプリケーション側の解析方法次第** になります。

# 標準ライブラリの動作

GetCommandLine 関数で取得できるコマンドライン文字列が解析されます。処理内容は

- 空白文字で分割される
    - 空白文字を引数に含めるには「"」で括る必要がある
- 「"」から「"」までの括りを１つの引数とする
    - 途中で２つ並んだ「"」は、１文字の「"」になる
    - 最初と最後の「"」は削除される
- 「 \\ 」の直後に「"」がないとき
    - 「 \\ 」は文字として扱われる
- 「 \\ 」の直後に「"」があるとき
    - 引数では、連続した「 \\ 」の数が半分になる
    - 連続した「 \\ 」の数が偶数のときは「"」は括り文字になる
    - 連続した「 \\ 」の数が奇数のときは「"」を文字として扱う

となるようです。「 %env% 」形式の環境変数展開処理は行われません。

**パス名の前後を「"」で括って、パス区切りを「 \\ 」で渡すとき要注意です。**

---

以下、テスト プログラムで動作確認します。

# 動作確認

VisualStudio 2019 : C++ コンソール アプリのプロジェクトを使います。

```C++:print_argv.cpp
#include <stdio.h>

extern "C" __declspec(dllimport) char * __stdcall GetCommandLineA(void);
#define GetCommandLine GetCommandLineA

int main(int argc, char **argv)
{
    printf("GetCommandLine: 「%s」\n", GetCommandLine());
    for (int n = 0; n < argc; n++)
        printf("argv[%d]: 「%s」\n", n, argv[n]);
    return 0;
}
```

文字列を「…」で括るようにして出力します。

## コマンド ライン文字列の分割

文字列の途中にある空白で分割されます。

```text
> print_argv 1 2 3
GetCommandLine: 「print_argv  1 2 3」
argv[0]: 「print_argv」
argv[1]: 「1」
argv[2]: 「2」
argv[3]: 「3」
```

連続空白でも同じ結果になります。

```text
> print_argv 1  2 3
GetCommandLine: 「print_argv  1  2 3」
argv[0]: 「print_argv」
argv[1]: 「1」
argv[2]: 「2」
argv[3]: 「3」
```

## 引用符としてのダブルクォート「"」

ダブルクォート「"」で始まり、ダブルクォート「"」で終わる範囲を一連の文字列とし、先頭と最後のダブルクォート「"」は削除されます。途中にある空白文字は引数分割文字ではなくなります。

```text
> print_argv "1 2 3"
GetCommandLine: 「print_argv  "1 2 3"」
argv[0]: 「print_argv」
argv[1]: 「1 2 3」
```

途中にダブルクォート「"」が２つ並んでいると、ひとつのダブルクォート「"」文字とみなされます。

```text
> print_argv "1""2 3"
GetCommandLine: 「print_argv  "1""2 3"」
argv[0]: 「print_argv」
argv[1]: 「1"2 3」
```

## バックスラッシュ「\\」

バックスラッシュ「\\」は、パスの区切り文字なので、そのまま使いたい場合と、エスケープ文字の役割をさせたい場合があるので厄介です。

### 連続したバックスラッシュ「\\」の直後がダブルクォート「"」でない場合

エスケープ文字として機能せず、文字として扱われます。

```text:
> print_argv a\b
GetCommandLine: 「print_argv  a\b」
argv[0]: 「print_argv」
argv[1]: 「a\b」

> print_argv a\\b
GetCommandLine: 「print_argv  a\\b」
argv[0]: 「print_argv」
argv[1]: 「a\\b」

> print_argv a\ b
GetCommandLine: 「print_argv  a\ b」
argv[0]: 「print_argv」
argv[1]: 「a\」
argv[2]: 「b」
```

### バックスラッシュ「\\」の直後がダブルクォート「"」の場合

まず、引数に渡るバックスラッシュ「\\」の数が元の半分にされます。

- 連続するバックスラッシュ「\\」の数が奇数のときは、ダブルクォート「"」が追加されます。

```text
> print_argv a\"b
GetCommandLine: 「print_argv  a\"b」
argv[0]: 「print_argv」
argv[1]: 「a"b」

> print_argv a\\"b
GetCommandLine: 「print_argv  a\\"b」
argv[0]: 「print_argv」
argv[1]: 「a\b」

> print_argv a\\\"b
GetCommandLine: 「print_argv  a\\\"b」
argv[0]: 「print_argv」
argv[1]: 「a\"b」

> print_argv a\\\\"b
GetCommandLine: 「print_argv  a\\\\"b」
argv[0]: 「print_argv」
argv[1]: 「a\\b」
```

- 連続するバックスラッシュ「\\」の数が偶数のときの、ダブルクォート「"」は引用符になります。

```text
> print_argv a\"b c"
GetCommandLine: 「print_argv  a\"b c"」
argv[0]: 「print_argv」
argv[1]: 「a"b」
argv[2]: 「c」

> print_argv a\\"b c
GetCommandLine: 「print_argv  a\\"b c」
argv[0]: 「print_argv」
argv[1]: 「a\b c」

> print_argv a\\\"b c"
GetCommandLine: 「print_argv  a\\\"b c"」
argv[0]: 「print_argv」
argv[1]: 「a\"b」
argv[2]: 「c」

> print_argv a\\\\"b c"
GetCommandLine: 「print_argv  a\\\\"b c"」
argv[0]: 「print_argv」
argv[1]: 「a\\b c」
```

## コマンド プロンプトによる環境変数の展開

コマンド プロンプトは「%env%」形式の文字列が展開されます。

```text
> echo %windir%
C:\WINDOWS

> print_argv %windir%
GetCommandLine: 「print_argv  C:\WINDOWS」
argv[0]: 「print_argv」
argv[1]: 「C:\WINDOWS」

> print_argv %^windir%
GetCommandLine: 「print_argv  %windir%」
argv[0]: 「print_argv」
argv[1]: 「%windir%」
```

プロセスに渡された時点で、既に展開されています。呼び出された側では展開していません。

system 関数ではシェル(cmd.exe)を経由するので、環境変数展開が実行されますが、CreateProcess 関数では cmd.exe を使わないので動作が異なります。