【お知らせ】プログラミング記事の投稿はQiitaに移行しました。

MSYSのPOSIX互換レイヤと引数

MSYSのコマンド群はPOSIX互換レイヤで動いています。Cygwinから派生したGPLのライブラリで、実体は以下にあります。

  • /bin/msys-1.0.dll

MSYSと一緒に配布されているgcc(MinGW)の出力は互換レイヤを使用しません。通常のWin32バイナリです。この挙動はCygwinでの-mno-cygwinに相当します。逆に-mcygwinに相当する互換レイヤを使用した出力はサポートされていません。CRTが通常と異なるため、msvcrt.dllの代わりにmsys-1.0.dllをリンクするだけでは正常動作しません。

MinGWのデフォルトでは互換レイヤの利用に必要なファイルはインストールされませんが、簡単に追加できます。

$ mingw-get install msys-core-dev

パッケージは自動でダウンロードされ、以下にキャッシュされます。

  • /mingw/var/cache/mingw-get/packages/

/includeにnewlibのヘッダ、/libにCRTやインポートライブラリなどがインストールされます。キャッシュに保存されたアーカイブを見れば、どんなファイルがインストールされたか確認できます。

$ tar tf /mingw/var/cache/mingw-get/packages/msysCORE-1.0.17-1-msys-1.0.17-dev.tar.lzma

【追記】自分でビルドする方法は以下を参照してください。

互換レイヤをリンク

Cygwinのようにgccのオプションは提供されていないため、互換レイヤを使用するには明示的に指定する必要があります。

$ gcc xxx.c -I/include -nostdlib /lib/crt0.o -L/lib -lgcc -lmsys-1.0.dll -lkernel32 -luser32 -lshell32 -ladvapi32

コマンドライン引数

互換レイヤは関数の挙動だけでなく、シェルから呼び出されるときの挙動も変化します。

コマンドライン引数を表示するだけのプログラムで実験してみます。

Win32

パスが変換されるのに注目してください。

$ gcc -o args-win32 args.c
$ ./args-win32 c /c /c/d /cc /cc:/dd
0: C:\MinGW\msys\1.0\home\7shi\args-win32.exe
1: c
2: c:/
3: c:/d
4: C:/MinGW/msys/1.0/cc
5: C:\MinGW\msys\1.0\cc;C:\MinGW\msys\1.0\dd
互換レイヤ

パスが変換されずにそのまま渡されます。

$ gcc -o args-msys args.c -I/include -nostdlib /lib/crt0.o -L/lib -lgcc -lmsys-1.0.dll -lkernel32
$ ./args-msys c /c /c/d /cc /cc:/dd
0: ./args-msys
1: c
2: /c
3: /c/d
4: /cc
5: /cc:/dd

仕組み

パスの変換はシェルで変換しているのではなく、互換レイヤ内のexec系関数で変換されます。実行対象のEXEにmsys-1.0.dllがリンクされているかどうかで挙動を変えているようです。

互換レイヤから他のプロセスを呼び出せば、自動的に引数が変換されるのが確認できます。

$ gcc -std=c99 -o call call.c -I/include -nostdlib /lib/crt0.o -L/lib -lgcc -lmsys-1.0.dll -lkernel32
$ ./call args-win32.exe c /c /c/d /cc /cc:/dd
1: args-win32.exe
2: c
3: /c
4: /c/d
5: /cc
6: /cc:/dd
----
0: C:\MinGW\msys\1.0\home\7shi\args-win32.exe
1: c
2: c:/
3: c:/d
4: C:/MinGW/msys/1.0/cc
5: C:\MinGW\msys\1.0\cc;C:\MinGW\msys\1.0\dd
$ ./call args-msys.exe c /c /c/d /cc /cc:/dd
1: args-msys.exe
2: c
3: /c
4: /c/d
5: /cc
6: /cc:/dd
----
0: args-msys.exe
1: c
2: /c
3: /c/d
4: /cc
5: /cc:/dd

変換の回避

引数を変換せずにWin32プロセスを呼び出したいときは、互換レイヤの関数ではなくWin32APIを使用する必要があります。

$ gcc -std=c99 -o call-win32 call-win32.c -I/include -nostdlib /lib/crt0.o -L/lib -lgcc -lmsys-1.0.dll -lkernel32
$ ./call-win32 args-win32.exe c /c /c/d /cc /cc:/dd
1: args-win32.exe
2: c
3: /c
4: /c/d
5: /cc
6: /cc:/dd
----
0: args-win32.exe
1: c
2: /c
3: /c/d
4: /cc
5: /cc:/dd

失敗談

互換レイヤを調べる前に、強引に引数を戻すラッパーを作りました。

しかし変換の規則が思ったより複雑で、完全には元に戻せませんでした。

$ ./revert-args.exe c /c /c/d /cc /cc:/dd
1: c -> c
2: c:/ -> /c:/
3: c:/d -> /c:/d
4: C:/MinGW/msys/1.0/cc -> /cc
5: C:\MinGW\msys\1.0\cc;C:\MinGW\msys\1.0\dd -> /cc:C:\MinGW\msys\1.0\dd

結果は散々です。この路線を続けるのは不毛なので、互換レイヤの調査に踏み切りました。

アーカイブ

実験に使用したソースをMakefile付きでアーカイブしました。