WPFで実行ファイルとDLLを一つにまとめる(C#編)

.NETで実行ファイルとDLLをひとつにまとめる場合はILMergeを使えばいいのですが、WPFXAMLを使っているとILMergeが使えません。

 

そんな状態でもDLLをひとつにできる方法を紹介します。

その1 プロジェクトファイルにコマンドを追加

C#プロジェクトファイルの
<Import Project="$(MSBuildToolsPath)\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>

ここまで

 

その2 EXEからアセンブリをロードするコードを追加

以下のコードをソースファイルにしてプロジェクトに追加してください。

スタートアップコードの変更が入っているので各プロジェクトに合わせて改変してください。

 

[STAThread]
public static void Main()
{
    AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
 
    App.Main(); // Run WPF startup code.
}
 
private static Assembly OnResolveAssembly(object sender, ResolveEventArgs e)
{
    var thisAssembly = Assembly.GetExecutingAssembly();
 
    // Get the Name of the AssemblyFile
    var assemblyName = new AssemblyName(e.Name);
    var dllName = assemblyName.Name + ".dll";
 
    // Load from Embedded Resources - This function is not called if the Assembly is already
    // in the same folder as the app.
    var resources = thisAssembly.GetManifestResourceNames().Where(s => s.EndsWith(dllName));
    if (resources.Any())
    {
 
        // 99% of cases will only have one matching item, but if you don't,
        // you will have to change the logic to handle those cases.
        var resourceName = resources.First();
        using (var stream = thisAssembly.GetManifestResourceStream(resourceName))
        {
            if (stream == null) return null;
            var block = new byte[stream.Length];
 
            // Safely try to load the assembly.
            try
            {
                stream.Read(block, 0, block.Length);
                return Assembly.Load(block);
            }
            catch (IOException)
            {
                return null;
            }
            catch(BadImageFormatException)
            {
                return null;
            }
        }
    }
 
    // in the case the resource doesn't exist, return null.
    return null;
}

その3 プロジェクト設定を変更

プロジェクト設定を変更して、スタートアップオブジェクトをその2で追加したソースに変更する。

参考画像

f:id:reniris:20141006205124p:plain

すべて終了したらビルドすればEXE単独で起動できるようになります。

ただし、WPFToolkitのグラフのようにDLLからDLLの関数を呼んでいたりすると動かない場合があるので100%とはいきませんが99%くらいはこれでいけます。

WPFプロジェクトじゃなくても使えるので意外に便利です。

 

今回の記事は次のサイトの日本語訳だったりする

参考URL

http://blogs.interknowlogy.com/2011/07/13/merging-a-wpf-application-into-a-single-exe/

 

VB.NETユーザーはしばし待て