2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C#/.NETのEXEとDLLはどこに作られる?|bin・obj・ProjectReferenceをやさしく整理【外伝G34】

2
Last updated at Posted at 2026-08-03

連載Index(読む順・公開済みリンクが最新)
C#達人への道 連載Index|読む順・公開記事一覧【S00】

Visual StudioでF5キーを押すと、作ったアプリケーションが起動します。

普段はそれだけで困りません。
ただ、ソリューション内にプロジェクトが増えてくると、急に分かりにくくなります。

  • 今起動しているEXEは、どこにあるのか
  • EXEの横にあるDLLは、どこから来たのか
  • binobjは何が違うのか
  • 別プロジェクトで作ったDLLを、アプリケーションはどうやって見つけるのか
  • 開発中は読めた設定ファイルが、インストール後に読めなくなるのはなぜか

このあたりは別々の話に見えますが、実際にはすべてつながっています。

この記事では、F5で起動しているEXEの場所から、複数プロジェクトのDLLが集まる仕組みまでを順番に見ていきます。

先に結論

最初に全体像をまとめます。

知りたいこと 結論
EXEやDLLはどこに作られるか 基本的に各プロジェクトのbin以下
objは何か ビルド途中で使う中間ファイルの置き場
DLLプロジェクトで作ったDLLはどこに出るか そのDLLプロジェクト自身のbin以下
アプリ側へDLLが来る理由 ProjectReferenceによって参照関係が設定されているため
ファイル名だけで読める基準 EXEの場所ではなく、カレントディレクトリ
EXEと同じ場所を基準にしたい AppContext.BaseDirectoryを使う
書き換える設定やログの保存先 AppDataなど、書き込み可能な場所

ここから、順番に見ていきます。

F5で起動しているEXEはどこにあるのか

.NET FrameworkのデスクトップアプリケーションをDebugビルドした場合、通常は次の場所にEXEが作られます。

MyApp
├─ bin
│  └─ Debug
│     ├─ MyApp.exe
│     ├─ MyApp.exe.config
│     ├─ MyLibrary.dll
│     └─ OtherLibrary.dll
├─ obj
└─ MyApp.csproj

Visual StudioからF5で起動した場合、基本的にはbin\DebugにあるMyApp.exeが実行されます。

Releaseビルドなら、出力先はbin\Releaseです。

bin
├─ Debug
└─ Release

.NET 8など、現在の.NETではターゲットフレームワーク名のフォルダーが追加されます。

MyApp
└─ bin
   └─ Debug
      └─ net8.0-windows
         ├─ MyApp.exe
         ├─ MyApp.dll
         ├─ MyApp.deps.json
         ├─ MyApp.runtimeconfig.json
         └─ MyLibrary.dll

.NET Frameworkと現在の.NETでは出力されるファイルが少し違います。

ただし、最初は次の理解で十分です。

実行に必要なファイルは、基本的にアプリケーションプロジェクトのbin以下へ集まる。

binとobjは何が違うのか

ビルドすると、プロジェクト内にbinobjが作られます。

フォルダー 役割
bin EXEやDLLなど、ビルドして作られたファイル
obj コンパイル途中で使う中間ファイル

実行ファイルやDLLを確認したいときは、まずbinを見ます。

objは、コンパイラーやMSBuildがビルド途中で使う場所です。通常、obj内のファイルを直接実行したり、配布したりすることはありません。

ビルド結果がおかしいときに、binobjを削除して再ビルドすることがあります。

これは、以前のビルドで残ったファイルをいったん消し、最初から作り直すためです。

binobjに入っているのは、ビルド時に作られたファイルです。

通常は削除しても、次回のビルドで作り直されます。ただし、手作業で置いたファイルがある場合は一緒に消えるため、その点だけ確認しておくと安心です。

ソリューションとプロジェクトの違い

プロジェクトが1つだけの間は、ソリューションとプロジェクトの違いを意識しなくても困りません。

複数プロジェクトになると、この違いが大切になります。

MySolution.sln
├─ MyApp
│  └─ MyApp.csproj
├─ MyLibrary
│  └─ MyLibrary.csproj
└─ MyDataAccess
   └─ MyDataAccess.csproj

この例では、1つのソリューションに3つのプロジェクトがあります。

プロジェクト 役割
MyApp 画面や起動処理を持つアプリケーション
MyLibrary 共通処理を持つクラスライブラリ
MyDataAccess データアクセスを担当するクラスライブラリ

.slnは、複数のプロジェクトをまとめて管理するためのファイルです。

一方、.csprojには、そのプロジェクトの次のような情報が書かれています。

  • どの.NETを使うか
  • どのファイルをビルドするか
  • どのプロジェクトやパッケージを参照するか
  • どこへ出力するか
  • ビルド時に追加で何をするか

つまり、ビルドの単位は基本的にプロジェクトです。

binとobjはプロジェクトごとに作られる

複数プロジェクトのソリューションでも、各プロジェクトの仕組みは同じです。

MySolution
├─ MyApp
│  ├─ bin
│  ├─ obj
│  └─ MyApp.csproj
│
├─ MyLibrary
│  ├─ bin
│  ├─ obj
│  └─ MyLibrary.csproj
│
└─ MyDataAccess
   ├─ bin
   ├─ obj
   └─ MyDataAccess.csproj

MyLibraryをビルドすると、まずMyLibrary自身の出力先へDLLが作られます。

MyLibrary
└─ bin
   └─ Debug
      └─ MyLibrary.dll

ここで、最初に引っかかりやすい点があります。

MyAppからMyLibraryを使うなら、実行時にはMyApp側にもMyLibrary.dllが必要です。

MyApp
└─ bin
   └─ Debug
      ├─ MyApp.exe
      └─ MyLibrary.dll

では、別のプロジェクトで作られたDLLは、どうやってMyApp側へ来るのでしょうか。

そこで使うのがProjectReferenceです。

ProjectReferenceでプロジェクト同士をつなぐ

アプリケーションから別プロジェクトを使う場合、通常はプロジェクト参照を追加します。

.csprojには、次のように記録されます。

<ItemGroup>
  <ProjectReference Include="..\MyLibrary\MyLibrary.csproj" />
  <ProjectReference Include="..\MyDataAccess\MyDataAccess.csproj" />
</ItemGroup>

ProjectReferenceが設定されていると、MSBuildに次の関係が伝わります。

MyApp
 ├─ MyLibraryが必要
 └─ MyDataAccessが必要

ビルドすると、次の順番で処理が進みます。

  1. 参照先のプロジェクトを先にビルドする
  2. MyLibrary.dllなどを作成する
  3. アプリケーションのビルドに必要なDLLとして参照する
  4. 実行に必要なDLLをMyApp側の出力先へ配置する
  5. MyAppをビルドする

最終的には、アプリケーション側のbinへ必要なDLLが集まります。

MyApp
└─ bin
   └─ Debug
      ├─ MyApp.exe
      ├─ MyApp.dll
      ├─ MyLibrary.dll
      └─ MyDataAccess.dll

ここで大切なのは、ProjectReferenceが単なるコピー設定ではないことです。

ProjectReferenceには、次の役割があります。

  • プロジェクト同士の依存関係を伝える
  • ビルド順序を決める
  • 参照先をビルドして作られた最新のDLLを使う
  • 実行に必要なDLLをアプリ側へ配置する

DLLを手作業でコピーする必要がなくなり、古いDLLを使ってしまうミスも減らせます。

DLLを直接参照する場合との違い

すでに作られているDLLを、ファイルとして直接参照することもできます。

<ItemGroup>
  <Reference Include="MyLibrary">
    <HintPath>..\Libraries\MyLibrary.dll</HintPath>
  </Reference>
</ItemGroup>

使い分けは次のように考えると分かりやすくなります。

参照方法 主な用途
ProjectReference 同じソリューション内にソースコードがある
DLLの直接参照 外部からDLLだけ提供されている
PackageReference NuGetパッケージを使う

同じソリューション内にプロジェクトがあるのにDLLを直接参照すると、次の問題が起きやすくなります。

  • 参照先を修正してもDLLが更新されない
  • 古いDLLを使い続ける
  • Debug版とRelease版を取り違える
  • DLLのコピー作業が増える
  • どちらを先にビルドすべきか分かりにくくなる

同じソリューション内で管理しているプロジェクトなら、基本はProjectReferenceを使います。

EXEと同じ場所にあるファイルは、名前だけで読めるのか

次のように、EXEと設定ファイルが同じフォルダーにあるとします。

MyApp.exe
settings.json

次のコードで読み込めることがあります。

string json = File.ReadAllText("settings.json");

ただし、このコードは「EXEと同じフォルダーのsettings.jsonを読む」とは限りません。

settings.jsonのような相対パスは、カレントディレクトリを基準に探されます。

現在のカレントディレクトリは、次のコードで確認できます。

string currentDirectory = Directory.GetCurrentDirectory();

Console.WriteLine(currentDirectory);

Visual StudioからF5で実行している間は、カレントディレクトリとビルド出力先が同じになることが多いため、問題に気付きにくいところです。

しかし、次のような起動方法では、カレントディレクトリが変わることがあります。

  • ショートカットから起動する
  • PowerShellやコマンドプロンプトから起動する
  • 別アプリケーションから起動する
  • タスクスケジューラから起動する
  • Windowsサービスとして実行する

そのため、開発中は読み込めたのに、配布後はファイルが見つからないということがあります。

アプリケーションのフォルダーを基準にする

アプリケーションと一緒に置いたファイルを確実に読み込みたい場合は、AppContext.BaseDirectoryを使います。

string settingsPath = Path.Combine(
    AppContext.BaseDirectory,
    "settings.json");

string json = File.ReadAllText(settingsPath);

サブフォルダーに置く場合も、Path.Combineで組み立てます。

MyApp.exe
Settings
└─ settings.json
string settingsPath = Path.Combine(
    AppContext.BaseDirectory,
    "Settings",
    "settings.json");

string json = File.ReadAllText(settingsPath);

文字列を直接連結するより、Path.Combineを使った方が、パスの区切りを安全に扱えます。

// 文字列を直接連結する例
string settingsPath =
    AppContext.BaseDirectory + "\\Settings\\settings.json";
// Path.Combineを使う例
string settingsPath = Path.Combine(
    AppContext.BaseDirectory,
    "Settings",
    "settings.json");

設定ファイルは自動でbinへ入るとは限らない

参照しているDLLは、通常、ビルド時にアプリケーションの出力先へ配置されます。

一方で、JSONやCSV、画像などは、プロジェクトへ追加しただけではbinへコピーされないことがあります。

例えば、settings.jsonを出力先へコピーしたい場合は、.csprojへ次のように設定します。

<ItemGroup>
  <None Update="settings.json">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </None>
</ItemGroup>

主な設定値は次のとおりです。

設定値 動作
Always ビルドするたびにコピーする
PreserveNewest コピー元の方が新しい場合だけコピーする
Never コピーしない

通常は、必要なときだけコピーされるPreserveNewestが使いやすい設定です。

Visual Studioのプロパティ画面では、「出力ディレクトリにコピー」を「新しい場合はコピーする」に設定すると、同じ内容が.csprojへ反映されます。

Program Filesにインストールした後は保存先を分ける

開発中は、EXEと同じ場所へ設定ファイルやログを書き込んでも動くことがあります。

しかし、アプリケーションをProgram Filesへインストールすると、同じコードが失敗する場合があります。

C:\Program Files\MyCompany\MyApp

Program Filesは、通常のユーザー権限で自由に書き換える場所ではないからです。

保存先は、ファイルの用途で分けます。

ファイル 保存先の例
EXE、DLL Program Filesなどのインストール先
書き換えない初期設定 インストール先
ユーザーごとの設定 AppData
ログ、キャッシュ LocalAppData
全ユーザー共通データ ProgramDataなど

ユーザーごとの設定保存先は、次のように取得できます。

string appDataDirectory = Environment.GetFolderPath(
    Environment.SpecialFolder.ApplicationData);

string settingsDirectory = Path.Combine(
    appDataDirectory,
    "MyCompany",
    "MyApp");

Directory.CreateDirectory(settingsDirectory);

string settingsPath = Path.Combine(
    settingsDirectory,
    "settings.json");

ログやキャッシュなど、端末内だけで使うデータにはLocalApplicationDataが向いています。

string localAppDataDirectory = Environment.GetFolderPath(
    Environment.SpecialFolder.LocalApplicationData);

string logDirectory = Path.Combine(
    localAppDataDirectory,
    "MyCompany",
    "MyApp",
    "Logs");

Directory.CreateDirectory(logDirectory);

保存先は「EXEと同じ場所に置けるか」ではなく、実行中に書き換えるかどうかで考えると迷いにくくなります。

出力先が思っていた場所と違うときに確認する順番

ビルド結果が思っていた場所に出ていない場合、最初からビルド後イベントだけを探す必要はありません。

次の順番で確認すると、原因を見つけやすくなります。

1. プロジェクトのプロパティ

まず、DebugとReleaseのどちらでビルドしているかを確認します。

同じプロジェクトでも、構成によって出力先が変わります。

2. csprojの出力設定

.csprojOutputPathなどが設定されていないか確認します。

<PropertyGroup>
  <OutputPath>..\Output\</OutputPath>
</PropertyGroup>

3. Directory.Build.propsとDirectory.Build.targets

上位フォルダーに次のファイルがあると、複数プロジェクトへ共通設定が適用されます。

Directory.Build.props
Directory.Build.targets

.csprojだけ見ても設定が見つからない場合は、上位フォルダーも確認します。

4. ビルド前後の処理

ビルド後にファイルをコピーしている場合があります。

<Target Name="CopySettings" AfterTargets="Build">
  <Copy
    SourceFiles="settings.json"
    DestinationFolder="$(OutputPath)" />
</Target>

Visual Studioのビルドイベントだけでなく、MSBuildのTargetとして書かれている場合もあります。

5. ビルドを実行しているスクリプト

PowerShell、バッチファイル、GitHub Actionsなどから、出力先を上書きしている場合があります。

.csprojに原因がなければ、実際にビルドを開始しているコマンドまで確認します。

症状から確認場所を絞る

最後に、よくある症状と確認場所をまとめます。

症状 最初に確認する場所
修正したはずなのに動きが古い 起動しているEXEの場所、Debug/Release、binの更新日時
DLLが見つからない ProjectReference、参照先のビルド結果、アプリ側のbin
JSONや画像が見つからない CopyToOutputDirectoryAppContext.BaseDirectory
開発中は保存できたが、インストール後は失敗する Program Filesへ書き込んでいないか
出力先がプロジェクト設定と違う Directory.Build.propsDirectory.Build.targets、ビルドスクリプト
DLLを直してもアプリへ反映されない DLL直接参照ではなくProjectReferenceになっているか

症状に合わせて見る場所を絞れば、「何となくbinを消して試す」だけでなく、原因を考えながら確認できます。

まとめ

C#/.NETのビルドでは、プロジェクトごとにbinobjが作られます。

プロジェクト
├─ bin
│  └─ 実行や配布に使うビルド結果
└─ obj
   └─ ビルド途中で使う中間ファイル

複数プロジェクトの場合も、仕組みは同じです。

ソリューション
├─ アプリケーションプロジェクト
│  ├─ bin
│  └─ obj
└─ クラスライブラリプロジェクト
   ├─ bin
   └─ obj

アプリケーションから別プロジェクトを使う場合は、ProjectReferenceを設定します。

<ProjectReference Include="..\MyLibrary\MyLibrary.csproj" />

これにより、MSBuildがビルド順序と参照関係を管理し、必要なDLLをアプリケーション側の出力先へ配置します。

ファイルを読むときは、次の違いも重要です。

// カレントディレクトリを基準にする
string relativePath = "settings.json";

// アプリケーション側のフォルダーを基準にする
string applicationPath = Path.Combine(
    AppContext.BaseDirectory,
    "settings.json");

最後に、今回の内容を一言でまとめると次のようになります。

各プロジェクトは自分のbinにEXEやDLLを出力し、アプリケーションに必要なDLLはProjectReferenceによってアプリ側のbinへ集められる。

この流れが分かると、DLLが見つからない、設定ファイルを読めない、インストール後に書き込めないといった問題でも、どこを確認すればよいか判断しやすくなります。

連載Index(読む順・公開済みリンクが最新)
C#達人への道 連載Index|読む順・公開記事一覧【S00】

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?