OITA: Oika's Information Technological Activities

@oika 情報技術的活動日誌。

アプリケーションのStartupPathをちゃんと取得する

アプリの実行ファイルがあるディレクトリのパスの取得の仕方について。

例えば実行ファイルと同じディレクトリにテキストを出力したいとき、

File.WriteAllText("hoge.txt", "テキストファイルの本文");

とでも書けば普通はちゃんと実行ファイルのディレクトリにhoge.txtが出力されるので、
横着してこう書いている人も少なくない気がするけれど、
これはたまたまカレントディレクトリと実行ディレクトリが同じだからうまく動いているだけだ。

Environment.CurrentDirectory = @"C:\";
File.WriteAllText("hoge.txt", "テキストファイルの本文");

とやればCドライブ直下に出力されてしまうし、
もっといえば、コマンドラインからexeファイルを叩いて実行した場合などは、
そのコマンドを打ったときのディレクトリがカレントになってしまう。

実行パスを取得する方法として、WindowsフォームだとApplication.StartupPathという
便利なプロパティがあるのだけど、WPFにはそのものずばりなものがない(と思う)。

そこで、WPFでApplication.StartupPathを実装する方法を調べると、
以下のように書いているサイトがいくつか見つかった。

            string exePath = Environment.GetCommandLineArgs()[0];
            string startupPath = System.IO.Path.GetDirectoryName(exePath);

アプリ実行時のコマンドライン引数の最初は必ず実行ファイルのパスになるから
そのディレクトリ部分をとればそれがStartupPathになるだろうという方法だ。

ところが、実はこれも厳密にいうと正しくない。
「C:\AppDir\Hoge.exe」を実行するときに、コマンドラインから

>cd C:\AppDir  
>Hoge.exe  

というように、ディレクトリ移動してから相対パスでexeを指定した場合は
1行目のexePathに"Hoge.exe"しか入らないので、
GetDirectoryName(exePath)とやっても空の文字列が返ってくる。

これをふまえると、以下のように書いてやる必要がある。

            string exePath = Environment.GetCommandLineArgs()[0];
            string exeFullPath = System.IO.Path.GetFullPath(exePath);
            string startupPath = System.IO.Path.GetDirectoryName(exeFullPath);

ただしPath.GetFullPathも、対象が相対パスの場合には
カレントディレクトリのパスで補完することになるので、
アプリ起動後にカレントディレクトリが変更された場合は正しいパスが取得できなくなる。
起動直後にStartupPathを取得して保持しておくような使い方にするのが良いと思う。