現在、多くの依存関係があるプロジェクトに取り組んでいます。埋め込みリソースの場合と同じように、参照されているすべての dll を .exe にコンパイルしたいと思います。ILMergeを試しましたが、.xaml リソースを処理できません。
私の質問は次のとおりです。複数の依存関係を持つ WPF プロジェクトを単一の .exe にマージする方法はありますか?
これは私にとって魅力のように機能しました:)そして完全に無料です。
ブログが消えた場合に備えてコードを追加します。
.csproj
1) これをファイルに追加します。<Target Name="AfterResolveReferences">
<ItemGroup>
<EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
<LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
</EmbeddedResource>
</ItemGroup>
</Target>
Program.cs
を次のようにします。[STAThreadAttribute]
public static void Main()
{
AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
App.Main();
}
OnResolveAssembly
3)メソッドを追加します。private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
{
Assembly executingAssembly = Assembly.GetExecutingAssembly();
AssemblyName assemblyName = new AssemblyName(args.Name);
var 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;
var assemblyRawBytes = new byte[stream.Length];
stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
return Assembly.Load(assemblyRawBytes);
}
}
ILMerge の Web サイトに投稿されているように、これらの dll をリソースとして扱います。こちらの Jeffrey Richterから:
多くのアプリケーションは、多くの DLL ファイルに依存する EXE ファイルで構成されています。このアプリケーションをデプロイするときは、すべてのファイルをデプロイする必要があります。ただし、単一の EXE ファイルのみを展開するために使用できる手法があります。まず、EXE ファイルが依存し、Microsoft .NET Framework 自体の一部として出荷されていないすべての DLL ファイルを特定します。次に、これらの DLL を Visual Studio プロジェクトに追加します。追加する DLL ファイルごとに、そのプロパティを表示し、「ビルド アクション」を「埋め込みリソース」に変更します。これにより、C# コンパイラによって DLL ファイルが EXE ファイルに埋め込まれ、この 1 つの EXE ファイルを展開できます。実行時に、CLR は依存する DLL アセンブリを見つけることができません。これが問題です。これを修正するには、アプリケーションの初期化時にコールバック メソッドを AppDomain の ResolveAssembly イベントに登録します。
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => {
String resourceName = "AssemblyLoadingAndReflection." +
new AssemblyName(args.Name).Name + ".dll";
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName)) {
Byte[] assemblyData = new Byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
return Assembly.Load(assemblyData);
}
};
ここで、依存する DLL ファイル内の型を参照するメソッドをスレッドが初めて呼び出すと、AssemblyResolve イベントが発生し、上記のコールバック コードが目的の埋め込み DLL リソースを見つけて、アセンブリの Load メソッドのオーバーロードを呼び出してそれをロードします。これは Byte[] を引数として取ります。
.NETリアクターには、アセンブリをマージする機能があり、それほど高価ではありません。
これは、コードを抽出するために名前空間を知る必要がない、Matthieu から引用されたコードの微調整バージョンです。WPF の場合、これをアプリケーションの起動イベント コードに入れます。
AppDomain.CurrentDomain.AssemblyResolve += (s, args) =>
{
// Note: Requires a using statement for System.Reflection and System.Diagnostics.
Assembly assembly = Assembly.GetExecutingAssembly();
List<string> embeddedResources = new List<string>(assembly.GetManifestResourceNames());
string assemblyName = new AssemblyName(args.Name).Name;
string fileName = string.Format("{0}.dll", assemblyName);
string resourceName = embeddedResources.Where(ern => ern.EndsWith(fileName)).FirstOrDefault();
if (!string.IsNullOrWhiteSpace(resourceName))
{
using (var stream = assembly.GetManifestResourceStream(resourceName))
{
Byte[] assemblyData = new Byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
var test = Assembly.Load(assemblyData);
string namespace_ = test.GetTypes().Where(t => t.Name == assemblyName).Select(t => t.Namespace).FirstOrDefault();
#if DEBUG
Debug.WriteLine(string.Format("\tNamespace for '{0}' is '{1}'", fileName, namespace_));
#endif
return Assembly.Load(assemblyData);
}
}
return null;
};
コンパイル時にそれらを使用できるようにするために、ExternalDLLs という名前のフォルダーを作成し、そこに dll をコピーして、上記のように EmbeddedResource に設定します。コードでそれらを使用するには、それらへの参照を設定する必要がありますが、Copy local を False に設定します。コードをエラーなしでクリーンにコンパイルするには、コード内の using ステートメントを dll の名前空間に設定する必要もあります。
これは、埋め込まれたリソース名を調べて、それらの名前空間を出力ウィンドウに表示する小さなユーティリティです。
private void getEmbeddedResourceNamespaces()
{
// Note: Requires a using statement for System.Reflection and System.Diagnostics.
Assembly assembly = Assembly.GetExecutingAssembly();
List<string> embeddedResourceNames = new List<string>(assembly.GetManifestResourceNames());
foreach (string resourceName in embeddedResourceNames)
{
using (var stream = assembly.GetManifestResourceStream(resourceName))
{
Byte[] assemblyData = new Byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
try
{
var test = Assembly.Load(assemblyData);
foreach (Type type in test.GetTypes())
{
Debug.WriteLine(string.Format("\tNamespace for '{0}' is '{1}'", type.Name, type.Namespace));
}
}
catch
{
}
}
}
}
.Netz ( http://madebits.com/netz/ ) を試してみてください。これは (ビールのように) 無料で、ターゲットが exe である場合に便利です。