1 つの exe だけにしたい
◆ ILMerge
◆ targetplatform:v4
◆ VisualStudio の機能じゃないのでインストール必要
◆ WPF アプリは ILMerge で出力された exe が起動できない
◆ csproj の設定で依存 dll をリソースに埋め込む
◆ 手動でアセンブリの解決時にリソースを読みこませる
◆ targetplatform:v4
◆ VisualStudio の機能じゃないのでインストール必要
◆ WPF アプリは ILMerge で出力された exe が起動できない
◆ csproj の設定で依存 dll をリソースに埋め込む
◆ 手動でアセンブリの解決時にリソースを読みこませる
nuget のライブラリや 自分の dll ライブラリなど使うと ビルドした時に フォルダの中が dll まみれになります
1 つの exe にまとまってたほうがフォルダがすっきりするし 扱いやすいです
また PC 詳しくない人にアプリケーションを使ってもらうときに exe だけ移動させてしまってエラーになったなんて問題もなくなります
サンプルに dll を作る lib プロジェクトとコンソールアプリの ConsoleApplication1 を作ります
ConsoleApplication の中身はこれだけ
lib を参照に加えて lib のほうには getPrivate 拡張メソッドで Reflection を使って private の値を取り出すようなコードを書いておきます
bin/Release フォルダには ConsoleApplication1.exe と lib.dll ができます
この exe を単体で デスクトップなどに持っていて実行すると lib.dll がみつからないエラーが起きます
ILMerge で結合すると
この output.exe をデスクトップに単体で持っていっても動きます
さっきのコードが MainWindow に移動したくらいです
同じオプションだと PresentaionFramework がないとエラーが起きるので lib で dll の場所を指定しました
これでエラー無く exe ファイルが作成されるのですが……
起動できません
ダブルクリックしても何も起きません
イベントビューアをみると
IOException になってます
ILMerge の問題かとググってみると XAML がロードできないので WPF では使えないとか
http://www.telerik.com/blogs/how-to-merge-assemblies-into-wpf-application
WPF アプリの csproj の最後 </Project> の直前に これを追加します
これでビルドされる exe に dll ファイルが埋め込みリソースとして含まれるようになります
ただ それらのアセンブリは手動でロードしないといけなくなります
起動時にリソースから .dll ファイルを読み込んでおいて AssemblyResolve イベント時に要求された dll が埋め込みリソースにあればそれを返すようにしておきます
リンクのページのもの ほぼままです
この書き方だと別に Main 関数をもう一つ作って 前処理が終わったら 本来の App.Main を呼び出しています
埋め込みが不要になったらクラスごと消すだけで楽ですが Main が複数になるので作ったときに スタートアップオブジェクトを選択する必要があります
プロジェクトのプロパティのアプリケーションタブで選べます
エントリポイントなので この Program.Main の方をえらべばおっけいです
できた exe ファイルをデスクトップなどに持っていっても動きます
簡単にいうと eval するみたいなもの
正式な機能じゃないので Microsoft が出してるけど 外部ライブラリとして nuget でダウンロードすることになります
これをいれるだけで 30 弱のファイルが Release フォルダに追加されました
動かしてみると 単体 exe だとアセンブリをメソッドを呼び出すタイミングでエラーになりました
出力されたフォルダにある状態なら動いたので何が必要なのか調べてみたところ
一緒に出力される config ファイルがあれば動きました
App.config にはライブラリインストール時に自動で色々設定が追加されてるので必要そうとも言えます
逆に dll についてきた xml ファイルはなくても問題ありませんでした
中身を見ると doc と書いててメッセージ系ですが 数 MB はありますしビルドで出力される以上実行時に必要になりそうだけど使ってないのかな?
一見めんどくさそうな WPF の exe にまとめる方法ですが ILMerge も外部ツールなのでビルド後に ILMerge を実行しないといけません
一応 ビルド後イベントでコマンド実行するように設定すれば外部ツールの ILMerge を呼び出せますが ILMerge は VisualStudio についてるものでもないので 別にインストール必要です
となれば ILMerge よりも WPF の方法のほうが楽なのかもしれません
本当に VisualStudio だけの機能 でできればいいのですけど
dll 以外のファイルを exe ファイルにリソースとして含める方法はこっち
ファイルを exe に含める
1 つの exe にまとまってたほうがフォルダがすっきりするし 扱いやすいです
また PC 詳しくない人にアプリケーションを使ってもらうときに exe だけ移動させてしまってエラーになったなんて問題もなくなります
ILMerge
dll をまとめるといえば ILMergeサンプルに dll を作る lib プロジェクトとコンソールアプリの ConsoleApplication1 を作ります
using System;
using lib;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(new A().getPrivate("val"));
}
}
public class A
{
private int val { get; } = 100;
}
}
using lib;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(new A().getPrivate("val"));
}
}
public class A
{
private int val { get; } = 100;
}
}
ConsoleApplication の中身はこれだけ
lib を参照に加えて lib のほうには getPrivate 拡張メソッドで Reflection を使って private の値を取り出すようなコードを書いておきます
bin/Release フォルダには ConsoleApplication1.exe と lib.dll ができます
C:\tmpcodes\ConsoleApplication1\bin\Release>ConsoleApplication1.exe
100
そのまま実行すれば普通に結果が表示されます100
この exe を単体で デスクトップなどに持っていて実行すると lib.dll がみつからないエラーが起きます
ILMerge で結合すると
C:\tmpcodes\ConsoleApplication1\bin\Release>"C:\Program Files (x86)\Microsoft\ILMerge\ILMerge.exe" ^
ConsoleApplication1.exe *.dll ^
/out:output.exe /wildcards /targetplatform:v4
C:\tmpcodes\ConsoleApplication1\bin\Release>output.exe
100
ConsoleApplication1.exe *.dll ^
/out:output.exe /wildcards /targetplatform:v4
C:\tmpcodes\ConsoleApplication1\bin\Release>output.exe
100
この output.exe をデスクトップに単体で持っていっても動きます
WPF
WPF でもやってみますさっきのコードが MainWindow に移動したくらいです
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MessageBox.Show(new A().getPrivate("val").ToString());
}
}
{
public MainWindow()
{
InitializeComponent();
MessageBox.Show(new A().getPrivate("val").ToString());
}
}
同じオプションだと PresentaionFramework がないとエラーが起きるので lib で dll の場所を指定しました
C:\tmpcodes\WpfApp1\bin\Release>"C:\Program Files (x86)\Microsoft\ILMerge\ILMerge.exe" ^
WpfApp1.exe *.dll /out:output.exe /wildcards /targetplatform:v4 ^
/lib:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.2"
WpfApp1.exe *.dll /out:output.exe /wildcards /targetplatform:v4 ^
/lib:"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.2"
これでエラー無く exe ファイルが作成されるのですが……
起動できません
ダブルクリックしても何も起きません
イベントビューアをみると
アプリケーション:output.exe
フレームワークのバージョン:v4.0.30319
説明: ハンドルされない例外のため、プロセスが中止されました。
例外情報:System.IO.IOException
場所 MS.Internal.AppModel.ResourcePart.GetStreamCore(System.IO.FileMode, System.IO.FileAccess)
場所 System.IO.Packaging.PackagePart.GetStream(System.IO.FileMode, System.IO.FileAccess)
場所 System.IO.Packaging.PackagePart.GetStream()
場所 System.Windows.Application.LoadComponent(System.Uri, Boolean)
場所 System.Windows.Application.DoStartup()
場所 System.Windows.Application.<.ctor>b__1_0(System.Object)
場所 System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object, Int32)
場所 System.Windows.Threading.ExceptionWrapper.TryCatchWhen(System.Object, System.Delegate, System.Object, Int32, System.Delegate)
場所 System.Windows.Threading.DispatcherOperation.InvokeImpl()
場所 System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(System.Object)
場所 MS.Internal.CulturePreservingExecutionContext.CallbackWrapper(System.Object)
場所 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
場所 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
場所 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
場所 MS.Internal.CulturePreservingExecutionContext.Run(MS.Internal.CulturePreservingExecutionContext, System.Threading.ContextCallback, System.Object)
場所 System.Windows.Threading.DispatcherOperation.Invoke()
場所 System.Windows.Threading.Dispatcher.ProcessQueue()
場所 System.Windows.Threading.Dispatcher.WndProcHook(IntPtr, Int32, IntPtr, IntPtr, Boolean ByRef)
場所 MS.Win32.HwndWrapper.WndProc(IntPtr, Int32, IntPtr, IntPtr, Boolean ByRef)
場所 MS.Win32.HwndSubclass.DispatcherCallbackOperation(System.Object)
場所 System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object, Int32)
場所 System.Windows.Threading.ExceptionWrapper.TryCatchWhen(System.Object, System.Delegate, System.Object, Int32, System.Delegate)
場所 System.Windows.Threading.Dispatcher.LegacyInvokeImpl(System.Windows.Threading.DispatcherPriority, System.TimeSpan, System.Delegate, System.Object, Int32)
場所 MS.Win32.HwndSubclass.SubclassWndProc(IntPtr, Int32, IntPtr, IntPtr)
場所 MS.Win32.UnsafeNativeMethods.DispatchMessage(System.Windows.Interop.MSG ByRef)
場所 System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame)
場所 System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame)
場所 System.Windows.Application.RunDispatcher(System.Object)
場所 System.Windows.Application.RunInternal(System.Windows.Window)
場所 System.Windows.Application.Run(System.Windows.Window)
場所 WpfApp1.App.Main()
フレームワークのバージョン:v4.0.30319
説明: ハンドルされない例外のため、プロセスが中止されました。
例外情報:System.IO.IOException
場所 MS.Internal.AppModel.ResourcePart.GetStreamCore(System.IO.FileMode, System.IO.FileAccess)
場所 System.IO.Packaging.PackagePart.GetStream(System.IO.FileMode, System.IO.FileAccess)
場所 System.IO.Packaging.PackagePart.GetStream()
場所 System.Windows.Application.LoadComponent(System.Uri, Boolean)
場所 System.Windows.Application.DoStartup()
場所 System.Windows.Application.<.ctor>b__1_0(System.Object)
場所 System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object, Int32)
場所 System.Windows.Threading.ExceptionWrapper.TryCatchWhen(System.Object, System.Delegate, System.Object, Int32, System.Delegate)
場所 System.Windows.Threading.DispatcherOperation.InvokeImpl()
場所 System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(System.Object)
場所 MS.Internal.CulturePreservingExecutionContext.CallbackWrapper(System.Object)
場所 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
場所 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
場所 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)
場所 MS.Internal.CulturePreservingExecutionContext.Run(MS.Internal.CulturePreservingExecutionContext, System.Threading.ContextCallback, System.Object)
場所 System.Windows.Threading.DispatcherOperation.Invoke()
場所 System.Windows.Threading.Dispatcher.ProcessQueue()
場所 System.Windows.Threading.Dispatcher.WndProcHook(IntPtr, Int32, IntPtr, IntPtr, Boolean ByRef)
場所 MS.Win32.HwndWrapper.WndProc(IntPtr, Int32, IntPtr, IntPtr, Boolean ByRef)
場所 MS.Win32.HwndSubclass.DispatcherCallbackOperation(System.Object)
場所 System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object, Int32)
場所 System.Windows.Threading.ExceptionWrapper.TryCatchWhen(System.Object, System.Delegate, System.Object, Int32, System.Delegate)
場所 System.Windows.Threading.Dispatcher.LegacyInvokeImpl(System.Windows.Threading.DispatcherPriority, System.TimeSpan, System.Delegate, System.Object, Int32)
場所 MS.Win32.HwndSubclass.SubclassWndProc(IntPtr, Int32, IntPtr, IntPtr)
場所 MS.Win32.UnsafeNativeMethods.DispatchMessage(System.Windows.Interop.MSG ByRef)
場所 System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame)
場所 System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame)
場所 System.Windows.Application.RunDispatcher(System.Object)
場所 System.Windows.Application.RunInternal(System.Windows.Window)
場所 System.Windows.Application.Run(System.Windows.Window)
場所 WpfApp1.App.Main()
IOException になってます
ILMerge の問題かとググってみると XAML がロードできないので WPF では使えないとか
対策
ILMerge が使えないとどうしようもないかと思ったのですが 方法があるようですhttp://www.telerik.com/blogs/how-to-merge-assemblies-into-wpf-application
WPF アプリの csproj の最後 </Project> の直前に これを追加します
<Target Name="AfterResolveReferences">
<ItemGroup>
<EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
<LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
</EmbeddedResource>
</ItemGroup>
</Target>
<ItemGroup>
<EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
<LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
</EmbeddedResource>
</ItemGroup>
</Target>
これでビルドされる exe に dll ファイルが埋め込みリソースとして含まれるようになります
ただ それらのアセンブリは手動でロードしないといけなくなります
起動時にリソースから .dll ファイルを読み込んでおいて AssemblyResolve イベント時に要求された dll が埋め込みリソースにあればそれを返すようにしておきます
public class Program
{
[STAThreadAttribute]
public static void Main()
{
var assemblies = new Dictionary<string, Assembly>();
var executingAssembly = Assembly.GetExecutingAssembly();
var resources = executingAssembly.GetManifestResourceNames().Where(n => n.EndsWith(".dll"));
foreach (var resource in resources)
{
using (var stream = executingAssembly.GetManifestResourceStream(resource))
{
if (stream == null)
continue;
var bytes = new byte[stream.Length];
stream.Read(bytes, 0, bytes.Length);
try
{
assemblies.Add(resource, Assembly.Load(bytes));
}
catch (Exception ex)
{
System.Diagnostics.Debug.Print($"Failed to load: {resource}, Exception: {ex.Message}");
}
}
}
AppDomain.CurrentDomain.AssemblyResolve += (s, e) =>
{
var assemblyName = new AssemblyName(e.Name);
var path = $"{assemblyName.Name}.dll";
if (assemblies.ContainsKey(path))
{
return assemblies[path];
}
return null;
};
App.Main();
}
}
{
[STAThreadAttribute]
public static void Main()
{
var assemblies = new Dictionary<string, Assembly>();
var executingAssembly = Assembly.GetExecutingAssembly();
var resources = executingAssembly.GetManifestResourceNames().Where(n => n.EndsWith(".dll"));
foreach (var resource in resources)
{
using (var stream = executingAssembly.GetManifestResourceStream(resource))
{
if (stream == null)
continue;
var bytes = new byte[stream.Length];
stream.Read(bytes, 0, bytes.Length);
try
{
assemblies.Add(resource, Assembly.Load(bytes));
}
catch (Exception ex)
{
System.Diagnostics.Debug.Print($"Failed to load: {resource}, Exception: {ex.Message}");
}
}
}
AppDomain.CurrentDomain.AssemblyResolve += (s, e) =>
{
var assemblyName = new AssemblyName(e.Name);
var path = $"{assemblyName.Name}.dll";
if (assemblies.ContainsKey(path))
{
return assemblies[path];
}
return null;
};
App.Main();
}
}
リンクのページのもの ほぼままです
この書き方だと別に Main 関数をもう一つ作って 前処理が終わったら 本来の App.Main を呼び出しています
埋め込みが不要になったらクラスごと消すだけで楽ですが Main が複数になるので作ったときに スタートアップオブジェクトを選択する必要があります
プロジェクトのプロパティのアプリケーションタブで選べます
エントリポイントなので この Program.Main の方をえらべばおっけいです
できた exe ファイルをデスクトップなどに持っていっても動きます
Scripting で試した
dll が多かったり複雑そうなのだと大丈夫なのかな と思って Scripting で機能を試してみました簡単にいうと eval するみたいなもの
正式な機能じゃないので Microsoft が出してるけど 外部ライブラリとして nuget でダウンロードすることになります
これをいれるだけで 30 弱のファイルが Release フォルダに追加されました
動かしてみると 単体 exe だとアセンブリをメソッドを呼び出すタイミングでエラーになりました
出力されたフォルダにある状態なら動いたので何が必要なのか調べてみたところ
一緒に出力される config ファイルがあれば動きました
App.config にはライブラリインストール時に自動で色々設定が追加されてるので必要そうとも言えます
逆に dll についてきた xml ファイルはなくても問題ありませんでした
中身を見ると doc と書いててメッセージ系ですが 数 MB はありますしビルドで出力される以上実行時に必要になりそうだけど使ってないのかな?
一見めんどくさそうな WPF の exe にまとめる方法ですが ILMerge も外部ツールなのでビルド後に ILMerge を実行しないといけません
一応 ビルド後イベントでコマンド実行するように設定すれば外部ツールの ILMerge を呼び出せますが ILMerge は VisualStudio についてるものでもないので 別にインストール必要です
となれば ILMerge よりも WPF の方法のほうが楽なのかもしれません
本当に VisualStudio だけの機能 でできればいいのですけど
dll 以外のファイルを exe ファイルにリソースとして含める方法はこっち
ファイルを exe に含める