WPFアプリケーションの展開:.NETアセンブリを単一の実行可能ファイルに結合する

複数のマネージアセンブリを1つのファイルに結合する場合、ILMergeユーティリティを使用できます。



ILMerge.exe /t:winexe /out:test.exe test1.exe test2.dll







この例では、2つのアセンブリから結合された実行可能ファイルが作成されます。 / t:winexe属性は、結果がウィンドウ(WinForms)アプリケーションになることを示します。



ただし、ILMergeはWPFアプリケーションを使用できません。 これは、ユーザーインターフェイスの構造、動作、およびアニメーションを宣言的に記述するためにWPFアーキテクチャで使用されるXAMLファイルをコンパイルする特性のためです。



このようなアセンブリを組み合わせる場合、ILMergeはBAMLリソースへのすべてのアクセスURIを修正する必要がありますが、これは起こりません。



幸い、別の方法があります:組み込みリソースとして統合された内部にアセンブリファイルを配置する。



配置が自動的に行われるようにするには、Microsoft.CSharp.targetsファイルをインポートした直後に次の目標を追加してプロジェクトファイルを変更するだけで十分です。



 <Target Name="AfterResolveReferences"> <ItemGroup> <EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'"> <LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName> </EmbeddedResource> </ItemGroup> </Target>
      
      





この目標の達成は、 ResolveAssemblyReferenceタスクの完了直後に発生します。 この目標を達成した結果、関連するアセンブリは埋め込みリソースとしてパッケージ化されます。 各リソースには、アセンブリの相対パスとファイル名に従って名前が付けられます。

埋め込みリソースに名前を付ける際にアセンブリへのパスを使用すると、より柔軟なアプローチが可能になります。たとえば、通常は同じ名前のローカリゼーションでアセンブリを埋め込むことができます。



アセンブリを1つのファイルにパッケージ化したら、カーネルとWPFソフトウェアインフラストラクチャを初期化する前に、すべてのアセンブリをメモリに読み込む必要があります。

WPFアプリケーションへの「マジック」エントリポイントは、App.xamlファイルです。 ただし、実際には、App.xamlはApp.g.csファイルにコンパイルされます。このファイルは、プロジェクト内のobjディレクトリにあります。 ファイルApp.g.csには、標準のエントリポイント-静的メソッドMainが含まれています。



したがって、WPFカーネルを初期化する前にネストされたアセンブリを読み込むには、標準のエントリポイントを再定義する必要があります。



たとえば、次のように:



 public class Program { [STAThread] public static void Main() { App.Main(); } }
      
      





画像



最後の手順は、AppDomain.AssemblyResolveイベントの独自のハンドラーです。これは、標準ローダーが次のアセンブリの物理的な場所を特定できないたびに起動します。



 public class Program { static Dictionary<string, Assembly> assembliesDictionary = new Dictionary<string, Assembly>(); [STAThread] public static void Main() { AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly; App.Main(); } private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args) { AssemblyName assemblyName = new AssemblyName(args.Name); Assembly executingAssembly = Assembly.GetExecutingAssembly(); string path = string.Format("{0}.dll", assemblyName.Name); if (assemblyName.CultureInfo.Equals(CultureInfo.InvariantCulture) == false) { path = String.Format(@"{0}\{1}", assemblyName.CultureInfo, path); } if (!assembliesDictionary.ContainsKey(path)) { using (Stream assemblyStream = executingAssembly.GetManifestResourceStream(path)) { if (assemblyStream != null) { var assemblyRawBytes = new byte[assemblyStream.Length]; assemblyStream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length); using (var pdbStream = executingAssembly.GetManifestResourceStream(Path.ChangeExtension(path, "pdb"))) { if (pdbStream != null) { var pdbData = new Byte[pdbStream.Length]; pdbStream.Read(pdbData, 0, pdbData.Length); var assembly = Assembly.Load(assemblyRawBytes, pdbData); assembliesDictionary.Add(path, assembly); return assembly; } } assembliesDictionary.Add(path, Assembly.Load(assemblyRawBytes)); } else { assembliesDictionary.Add(path, null); } } } return assembliesDictionary[path]; } }
      
      







まとめ



提示された方法は実装が簡単であり、1つの実行可能ファイルでの.NETアプリケーションのマネージモジュールの自動アセンブリを目的としています。 ただし、実用的な価値の観点から、このソリューションは小さなプロジェクトのアセンブリにのみ適していることは明らかです。 この場合、アンマネージコードは、明らかに、結合されたアプリケーションファイルの「オーバーボード」のままです。



参照:



1. WPFアプリケーションの構築| msdn.microsoft.com/en-us/library/aa970678.aspx

2. MSBuild | msdn.microsoft.com/en-us/library/ms171452.aspx

3. Vlad Chistyakov:MSBuild | www.rsdn.ru/article/devtools/msbuild-05.xml

4.タスクResolveAssemblyReference | msdn.microsoft.com/en-us/library/9ad3f294.aspx

5. Visual Studioのビルドプロセスの拡張| msdn.microsoft.com/en-us/library/ms366724.aspx

6.アセンブリをダウンロードする許可| msdn.microsoft.com/en-us/library/ff527268.aspx2

7. C#+ WPF +サードパーティアセンブリ-> one .exe-shnik | habrahabr.ru/blogs/personal/67836



あとがき



2012年6月、Visual Studio 2010のCosturaオープンソースプラグインがリリースされました。これにより、プロジェクト設定の管理までの上記のプロセスを削減できます。



All Articles