secretbase.log

.NET/C#/Pythonなど

WPFアプリケーションをEXEひとつにまとめる

はじめに

WPFアプリケーションをインストーラーなどで配置する場合、Prismなどライブラリを使うとDLLを複数配置する必要があります。 WiXを用いる場合は、heat でまとめて wxsソースを自動生成して…といったアプローチが常套手段となりますが、EXEにDLLをマージすることで、EXEひとつを配布する方法を調べました。

ILMergeを使うとDLLをマージできるのですが、XAMLのリソースに対応していないためWPFでは使えないようです。

そこで、さらに調べたところ、下記ブログに記載されている方法がズバリな方法だったので、ご紹介します。 http://www.digitallycreated.net/Blog/61/combining-multiple-assemblies-into-a-single-exe-for-a-wpf-application

参照しているDLLをリソースファイルとして埋め込むことで、まとめるというアプローチです。

手順

csproj に追記

csprojをエディタで開きます。 最後の方に追加できるような形でコメントが記載されています。

  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
       Other similar extension points exist, see Microsoft.Common.targets.
  <Target Name="BeforeBuild">
  </Target>
  <Target Name="AfterBuild">
  </Target>
  -->
</Project>

このコメントの後に下記を追記します。

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

ビルド結果の確認

一度ビルドしてみましょう。 ビルドされた実行ファイルを 逆コンパイラのILSpy でみると、リソースファイルとして埋め込まれていることがわかります。

http://ilspy.net/

DLLの読み込み処理の追加

実行時にDLLを読み込むための処理を追加します。 まず Program.cs を追加します。 下記処理を実装します。

    public class Program
    {
        /// <summary>
        /// Main
        /// </summary>
        [STAThread]
        public static void Main()
        {
            AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
            App.Main();
        }

        private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
        {
            Assembly executingAssembly = Assembly.GetExecutingAssembly();
            AssemblyName assemblyName = new AssemblyName(args.Name);

            string path = assemblyName.Name + ".dll";
            if (assemblyName.CultureInfo.Equals(CultureInfo.InvariantCulture) == false)
            {
                path = String.Format(@"{0}\{1}", assemblyName.CultureInfo, path);
            }

            using (Stream stream = executingAssembly.GetManifestResourceStream(path))
            {
                if (stream == null)
                    return null;

                byte[] assemblyRawBytes = new byte[stream.Length];
                stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
                return Assembly.Load(assemblyRawBytes);
            }
        }
    }

次に、アプリケーションのスタートアップオブジェクトを .Program に設定してください。

これでDLLを埋め込んだ状態で起動したりデバッグ実行したりすることができます。

参考

WPF 4 Unleashed

WPF 4 Unleashed