アプリケーション作成に必要なパスを取得する

アプリケーションを作成するときに必要なパスの取得方法をまとめます。

アプリケーションの起動パスを取得する ( Windows Forms )

アプリケーションの起動パスや、アプリケーションが配置されているパスは、実行時になにかと必要になるものです。たとえば初期設定ファイルをアプリケーションと同じディレクトリに配置している場合はそれが必要になります。

private void menuDebugExec000_Click( object sender, EventArgs e )
{

	String filepath_exe = "";
	String dirpath_exe = "";

	// 実行ファイルのパスを取得する.
	filepath_exe = Application.ExecutablePath;

	// 実行ファイルが配置されているディレクトリのパスを取得する.
	dirpath_exe = Application.StartupPath;

	StringBuilder sb = new StringBuilder();
	sb.AppendLine( filepath_exe );
	sb.AppendLine( dirpath_exe );

	String str = sb.ToString().Trim();

	// メッセージボックスで表示する.
	MessageBox.Show( str );

}

アプリケーションの起動パスを取得する ( WPF )

Windows Forms では Application.ExecutablePath や Application.StartupPath で取得できましたが、WPF では、それにあたるものがありません。アセンブリからその情報を取得します。

private void menuDebugExec000_Click( object sender, RoutedEventArgs e )
{

	String filepath_exe = "";
	String dirpath_exe = "";

	// 実行ファイルのパスを取得する.
	filepath_exe = System.Reflection.Assembly.GetExecutingAssembly().Location;

	// 実行ファイルが配置されているディレクトリのパスを取得する.
	dirpath_exe = System.IO.Path.GetDirectoryName( filepath_exe );

	StringBuilder sb = new StringBuilder();
	sb.AppendLine( filepath_exe );
	sb.AppendLine( dirpath_exe );

	String str = sb.ToString().Trim();

	// メッセージボックスで表示する.
	MessageBox.Show( str );

}

アプリケーションが配置されている上の階層のディレクトリを取得する

ここからは、Windows Forms や WPF の違いはなく、共通に取得できる手法を紹介します。

あるディレクトリの、ひとつ上や、ふたつ上のディレクトリを参照したい場合、コマンドプロンプトのシェルでは ../ とか ../../ のように打ち込めば参照できます。これと同じようなことをプログラムで実現するには System.IO の中で定義されている DirectoryInfo クラスを使います。そのプロパティの Parent を参照しましょう。

ルートディレクトリよりも上の階層を Parent で取得しようとすると、例外が発生するので try…catch で囲んで実行するとよいでしょう。

private void menuDebugExec000_Click( object sender, RoutedEventArgs e )
{

	// EXEのパスを取得する.
	String fp_exe = System.Reflection.Assembly.GetExecutingAssembly().Location;

	// この例ではEXEが配置されているディレクトリを基準にする.
	String dir_exe = System.IO.Path.GetDirectoryName( fp_exe );

	System.IO.DirectoryInfo di0;
	System.IO.DirectoryInfo di1;
	System.IO.DirectoryInfo di2;
	System.IO.DirectoryInfo di3;

	try
	{
		// ルートディレクトリよりも上の階層を取得しようとすると例外が発生する.
		di0 = new System.IO.DirectoryInfo( dir_exe );
		di1 = di0.Parent;
		di2 = di1.Parent;
		di3 = di2.Parent;
	}
	catch( Exception excp )
	{
		MessageBox.Show( excp.Message );
		return; // warning.
	}

	StringBuilder sb = new StringBuilder();
	sb.AppendLine( di0.FullName ); // 基準のディレクトリ.
	sb.AppendLine( di1.FullName ); // ひとつ上の階層.
	sb.AppendLine( di2.FullName ); // ふたつ上の階層.
	sb.AppendLine( di3.FullName ); // みっつ上の階層.

	String str = sb.ToString().Trim();

	// メッセージボックスで表示する.
	MessageBox.Show( str );

}

デスクトップのディレクトリを取得する

あまりパソコンが得意でない人達のためのアプリケーションでは、できあがったファイルをデスクトップに作ってあげると親切かもしれません。この場合は SpecialFolder.DesktopDirectory を引数にして GetFolderPath() します。

private void button1_Click( object sender, EventArgs e )
{

	// 特殊ディレクトリの種類を決定する( デスクトップ ).
	Environment.SpecialFolder spf = Environment.SpecialFolder.DesktopDirectory;

	// 特殊ディレクトリを取得する.
	String dir = Environment.GetFolderPath( spf );

	// メッセージボックスで表示する.
	MessageBox.Show( dir );

}

マイドキュメント関係のディレクトリを取得する

Windows では、ファイルの種類に応じて保存する専用のディレクトリが用意されています。この指針にまじめに従っているアプリケーションは少ないと思われますが、いちおう取得の方法を示しておきます。

・アプリケーション固有のファイルを保存しておく (ドキュメント)
・写真を保存しておく (ピクチャ)
・動画を保存しておく (ビデオ)
・音楽を保存しておく (ミュージック)

上記のソースコードと同様に、SpecialFolder.Personal, MyPictures, MyVideos, MyMusic を引数にして GetFolderPath() します。

private void button1_Click( object sender, EventArgs e )
{

	// 特殊ディレクトリの種類を決定する( マイドキュメント ).
	Environment.SpecialFolder spf0 = Environment.SpecialFolder.Personal; // ドキュメント.
	Environment.SpecialFolder spf1 = Environment.SpecialFolder.MyPictures; // ピクチャ.
	Environment.SpecialFolder spf2 = Environment.SpecialFolder.MyVideos; // ビデオ.
	Environment.SpecialFolder spf3 = Environment.SpecialFolder.MyMusic; // 音楽.

	// 特殊ディレクトリを取得する.
	String dir0 = Environment.GetFolderPath( spf0 );
	String dir1 = Environment.GetFolderPath( spf1 );
	String dir2 = Environment.GetFolderPath( spf2 );
	String dir3 = Environment.GetFolderPath( spf3 );

	StringBuilder sb = new StringBuilder();
	sb.AppendLine( dir0 );
	sb.AppendLine( dir1 );
	sb.AppendLine( dir2 );
	sb.AppendLine( dir3 );

	// メッセージボックスで表示する.
	MessageBox.Show( sb.ToString().Trim() );

}

ダウンロードのディレクトリを取得する

マイドキュメントのディレクトリには「ダウンロード」というディレクトリがあります。Chrome でも Edge でも、ファイルをダウンロードしたらここのディレクトリにファイルが入っています。この場所を動的に取得したいと思います。

勘のいいひとならば、上記のソースコード SpecialFolder 定数と GetFolderPath() のメソッドで取得できると予想すると思いますが、ここは、落とし穴です。その方法ではできません。

「ダウンロード」だけは SpecialFolder の中で定数 Enum 定義されていないのです。

Windows 3.1 とか Windows95 などの時代の古いソフトウェアでもリビルドすることなく動かすことができる互換性を保っているのが Windows の長所です。なにか SpecialFolder に定数定義を追加できない事情があるのでしょう。

ではどのように取得するか、shell32.dll の中にある SHGetKnownFolderPath() という API を C# 使ってコールします。

KNOWNFOLDERID (Knownfolders.h) - Win32 apps | Microsoft Learn

The KNOWNFOLDERID constants represent GUIDs that identify standard folders registered with the system as Known Folders.

SHGetKnownFolderPath() に与える引数は上記の MSDN のページを参照してください。GUID なので今後の変更はないと思います。互換性を保つために尋常でない努力をする Microsoft を信じましょう。

FOLDERID_Downloads が "374DE290-123F-4565-9164-39C4925E467B" です。
この場合 "c:\Users\ログイン名\Downloads" が取得できます。

FOLDERID_PublicDownloads が "3D644C9B-1FB8-4F30-9B45-F670235F79C0" です。
この場合 "c:\Users\Public\Downloads" が取得できます。

SHGetKnownFolderPath() 第1引数に欲しいディレクトリの GUID を指定してください。そうすれば SHGetKnownFolderPath 実行後に、第4引数にそのディレクトリのパスを示す文字列が格納されています。

残りは謎の第2引数と第3引数ですが、「多くの場合は第2引数は 0 でよい、第3引数は NULL でよい」とMSDNに書いてあります。

引数がいっぱいあるけれど実際は何も指定しなくてもいいというのは WindowsAPI ではよくあります。問題がおこったときにはじめて考え始めるのでかまいません。

C++ のオーバーロード機能を使ってあとから引数を追加するとリビルドが必要になるので、最初から予想される引数は準備しておいて、とりあえず 0 とか NULL にして運用させるのでしょう。ここにも互換性を大事にする Microsoft の姿勢が表れています。

下記の名前空間の追加をお忘れなく.
using System.Runtime.InteropServices;

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

// この using 追加を忘れずに. Don't forget to add this sentence.
using System.Runtime.InteropServices;

namespace aaa
{

	public partial class Form1 : Form
	{

		// MSDN の https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid を参照した定数.
		static Guid FOLDERID_Downloads       = Guid.Parse( "374DE290-123F-4565-9164-39C4925E467B" );
		static Guid FOLDERID_PublicDownloads = Guid.Parse( "3D644C9B-1FB8-4F30-9B45-F670235F79C0" );

		// shell32.dll に定義されているAPIをC#でつかえるように定義する.
		[DllImport("shell32.dll")]
		extern static int SHGetKnownFolderPath( ref Guid folder_id, uint flags, IntPtr token, [MarshalAs(UnmanagedType.LPWStr)] out string want_path );

		public Form1()
		{
			InitializeComponent();
		}

		private void button1_Click( object sender, EventArgs e )
		{

			Guid the_guid = FOLDERID_Downloads;
			uint flags = 0;
			IntPtr token = IntPtr.Zero;
			String want = "";

			// 特殊フォルダのディレクトリを取得する.
			int ret = SHGetKnownFolderPath( ref the_guid, flags, token, out want );
			if ( ret == 0 )
			{
				MessageBox.Show( want );
			}
			else
			{
				MessageBox.Show( "Error: SHGetKnownFolderPath();" );
			}

		}

		private void button2_Click( object sender, EventArgs e )
		{

			Guid the_guid = FOLDERID_PublicDownloads;
			uint flags = 0;
			IntPtr token = IntPtr.Zero;
			String want = "";

			// 特殊フォルダのディレクトリを取得する.
			int ret = SHGetKnownFolderPath( ref the_guid, flags, token, out want );
			if ( ret == 0 )
			{
				MessageBox.Show( want );
			}
			else
			{
				MessageBox.Show( "Error: SHGetKnownFolderPath();" );
			}

		}

	}

}