前提
.NETでは何も付けずににpublishをすると、出力先のディレクトリに依存しているアセンブリをコピーします。
そのため、作成したアプリケーションを配布しようとしたときに、複数ファイルを配布する必要があります。
これがあんまり嬉しくない場合があるということで、複数出力されるファイルをまとめるビルドオプションが存在します。
また、単一ファイルといっても、普通の.NETのアセンブリは直接実行可能な機械語ではありません。
中間言語で記述されたバイナリであり、システムにインストールされた直接実行可能なランタイムにホストしてもらって実行されます。
単一ファイルといった場合、.NETのアセンブリだけを纏めるものと、ランタイムまでまとめるものの二種類があるわけです。
出力ファイルに関係するオプション
--runtime
これを指定すると、Nativeのランタイムが出力にコピーされます
System:
- Nativeのランタイム(システムにインストールされている必要がある)
- 標準ライブラリのアセンブリファイル
出力(配布するやつ)
- ランタイムを起動する実行ファイル
- エントリポイントを含むアセンブリファイル
- 標準ライブラリ以外のリンクしているアセンブリファイル
System
- 依存なし(ランタイムがインストールされている必要がない)
出力(配布するやつ)
- Nativeのランタイム
- ランタイムを起動する実行ファイル(NativeHost)
- エントリポイントを含むアセンブリファイル
- 標準ライブラリ以外のリンクしているアセンブリファイル
- 標準ライブラリのアセンブリファイル
PublishSingleFile
これをtrueにすると、標準ライブラリを除く、.NETのアセンブリが纏められます。
このオプションを有効にするためには、ターゲットのOSを指定(--runtime
)する必要があります。
ランタイムはまとまってくれないので、単一ファイルと言いつつ単一にはなりません。
System
- 依存なし(ランタイムがインストールされている必要がない)
出力(配布するやつ)
- Nativeのランタイム
- バンドルファイル
- ランタイムを起動する実行ファイル(NativeHost)
- エントリポイントを含むアセンブリ
- 標準ライブラリ以外のリンクしているアセンブリ
- 標準ライブラリのアセンブリ
--self-contained
これをfalseにすると、標準ライブラリやランタイムをシステムに依存させます。
配布すべき出力ファイルの総量が減る代わりに、システムにランタイムがインストールされていないと動きません
System
- ランタイム(Native)
- 標準ライブラリのアセンブリ
出力(配布するやつ)
- ランタイムを起動する実行ファイル(NativeHost)
- エントリポイントを含むアセンブリファイル
- 標準ライブラリ以外のリンクしているアセンブリファイル
System
- ランタイム(Native)
- 標準ライブラリのアセンブリ
出力(配布するやつ)
- バンドルファイル
- ランタイムを起動する実行ファイル(NativeHost)
- エントリポイントを含むアセンブリ
- 標準ライブラリ以外のリンクしているアセンブリ
IncludeNativeLibrariesForSelfExtract
これをtrueにすると、NativeのRuntimeをまとめるようになります
System
- 依存なし(ランタイムがインストールされている必要がない)
出力(配布するやつ)
- バンドルファイル
- ランタイム(Native)
- ランタイムを起動する実行ファイル(NativeHost)
- エントリポイントを含むアセンブリ
- 標準ライブラリ以外のリンクしているアセンブリ
- エントリポイントを含むアセンブリファイル
- 標準ライブラリ以外のリンクしているアセンブリファイル
- 標準ライブラリのアセンブリ
System
- 依存なし(ランタイムがインストールされている必要がない)
出力(配布するやつ)
- バンドルファイル
- ランタイム(Native)
- ランタイムを起動する実行ファイル(NativeHost)
- エントリポイントを含むアセンブリ
- 標準ライブラリ以外のリンクしているアセンブリ
- エントリポイントを含むアセンブリファイル
- 標準ライブラリ以外のリンクしているアセンブリファイル
- 標準ライブラリのアセンブリ
これで完全な単一ファイルが実現できた…かのように思えますが、実はこれ、見かけ上だけです。
IncludeNativeLibrariesForSelfExtract
は出力(配布時)こそ一つのファイルにまとまっていますが、実行時にはランタイム部分がバンドルから取り出されて、%TEMP%\.net
に普通のランタイムのファイルとして展開されます。(DOTNET_BUNDLE_EXTRACT_BASE_DIR
で場所の変更は可能)
ということで、単一「実行」ファイルではなく、実質的にはインストーラのようなものだというわけです。とはいえ実用上困るケースは、ぱっと思いつきませんが…
単一ファイルアプリケーションをホストする
.NETアプリケーションは、HostAPIを使ってネイティブアプリからライブラリとして呼び出すことができます。
上記で書いている、「ランタイムを起動する実行ファイル(NativeHost)」の部分を自前実装できるということです。
// https://github.com/dotnet/runtime/blob/v6.0.0-rc.2.21480.5/src/native/corehost/hostfxr.h#L40
#include "./hostfxr.h"
auto hostLib = LoadLibrary("hostfxr.dll");
hostfxr_main_bundle_startupinfo_fn hostfxr_main_bundle_startupinfo
= GetProcAddress(hostLib, "hostfxr_main_bundle_startupinfo");
hostfxr_main_bundle_startupinfo(...);
このようにすれば、単一アプリケーションとしてpublishしたバンドルファイルをホストできそうです。この方法を取るには、ホストする側のネイティブアプリ+hostfxr.dll
+バンドルファイルの3つは最低必要になるので全然単一じゃないですが。
hostfxr
を性的にリンクすれば、hostfxr.dll
は減らせるかな?
展開処理フロー
[hostfxr_main_bundle_startupinfo
]
(https://github.com/dotnet/runtime/blob/v6.0.0-rc.2.21480.5/src/native/corehost/fxr/hostfxr.cpp#L46)
-> [fx_muxer_t::execute
]
(https://github.com/dotnet/runtime/blob/v6.0.0-rc.2.21480.5/src/native/corehost/fxr/fx_muxer.cpp#L579)
-> [fx_muxer_t::handle_exec_host_command
]
(https://github.com/dotnet/runtime/blob/v6.0.0-rc.2.21480.5/src/native/corehost/fxr/fx_muxer.cpp#L1018)
-> [read_config_and_execute
]
(https://github.com/dotnet/runtime/blob/v6.0.0-rc.2.21480.5/src/native/corehost/fxr/fx_muxer.cpp#L533)
-> [execute_app
]
(https://github.com/dotnet/runtime/blob/v6.0.0-rc.2.21480.5/src/native/corehost/fxr/fx_muxer.cpp#L146)
-> [corehost_main
]
(https://github.com/dotnet/runtime/blob/v6.0.0-rc.2.21480.5/src/native/corehost/hostpolicy/hostpolicy.cpp#L416)
-> [corehost_main_init
]
(https://github.com/dotnet/runtime/blob/v6.0.0-rc.2.21480.5/src/native/corehost/hostpolicy/hostpolicy.cpp#L394)
-> [process_manifest_and_extract
]
(https://github.com/dotnet/runtime/blob/v6.0.0-rc.2.21480.5/src/native/corehost/bundle/runner.h#L42)
-> [runner_t::extract
]
(https://github.com/dotnet/runtime/blob/v6.0.0-rc.2.21480.5/src/native/corehost/bundle/runner.cpp#L38)
-> [extractor_t::extract
]
(https://github.com/dotnet/runtime/blob/v6.0.0-rc.2.21480.5/src/native/corehost/bundle/extractor.cpp#L116)
単一ファイルのビルド処理フロー
// WIP