既存のDLLをコンパイル済みのC#実行可能ファイルに埋め込むことは可能ですか(配布するファイルが1つだけになるように)?可能であれば、どうすればそれを実行できますか?
通常、私はDLLを外に置いてセットアッププログラムですべてを処理するだけでかっこいいですが、これについて質問してくれた人が何人かいて、正直にわかりません。
既存のDLLをコンパイル済みのC#実行可能ファイルに埋め込むことは可能ですか(配布するファイルが1つだけになるように)?可能であれば、どうすればそれを実行できますか?
通常、私はDLLを外に置いてセットアッププログラムですべてを処理するだけでかっこいいですが、これについて質問してくれた人が何人かいて、正直にわかりません。
回答:
Costura.Fodyを使用することを強くお勧めします-アセンブリにリソースを埋め込むための最善かつ最も簡単な方法です。NuGetパッケージとして入手できます。
Install-Package Costura.Fody
プロジェクトに追加すると、出力ディレクトリにコピーされたすべての参照がメインアセンブリに自動的に埋め込まれます。プロジェクトにターゲットを追加して、埋め込みファイルをクリーンアップすることができます。
Install-CleanReferencesTarget
また、pdbを含めるか、特定のアセンブリを除外するか、その場でアセンブリを抽出するかを指定することもできます。私の知る限り、アンマネージアセンブリもサポートされています。
更新
現在、一部の人々はDNXのサポートを追加しようとしています。
アップデート2
最新のFodyバージョンには、MSBuild 16(Visual Studio 2019)が必要です。Fodyバージョン4.2.1はMSBuild 15を実行します(参照:FodyはMSBuild 16以降でのみサポートされます。現在のバージョン:15)
Visual Studioでプロジェクトを右クリックし、[プロジェクトのプロパティ]-> [リソース]-> [リソースの追加]-> [既存のファイルの追加]を選択します。そして、以下のコードをApp.xaml.csまたは同等のものに含めます。
public App()
{
AppDomain.CurrentDomain.AssemblyResolve +=new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
string dllName = args.Name.Contains(',') ? args.Name.Substring(0, args.Name.IndexOf(',')) : args.Name.Replace(".dll","");
dllName = dllName.Replace(".", "_");
if (dllName.EndsWith("_resources")) return null;
System.Resources.ResourceManager rm = new System.Resources.ResourceManager(GetType().Namespace + ".Properties.Resources", System.Reflection.Assembly.GetExecutingAssembly());
byte[] bytes = (byte[])rm.GetObject(dllName);
return System.Reflection.Assembly.Load(bytes);
}
これが私の元のブログ投稿です:http : //codeblog.larsholm.net/2011/06/embed-dlls-easily-in-a-net-assembly/
bytes
は、null かどうかを確認し、nullの場合はnullを返すことです。結局のところ、dllがリソースにない可能性があります。2:これは、そのクラス自体がそのアセンブリからの「使用」を持たない場合にのみ機能します。コマンドラインツールの場合、実際のプログラムコードを新しいファイルに移動し、これを実行して古いクラスの元のメインを呼び出す小さな新しいメインプログラムを作成する必要がありました。
.dll
名前にハイフン(つまりtwenty-two.dll
)が含まれている場合、それらもアンダースコア(つまりtwenty_two.dll
)に置き換えられます。あなたはこれにコード行を変更することができます:dllName = dllName.Replace(".", "_").Replace("-", "_");
それらが実際にマネージアセンブリである場合は、ILMergeを使用できます。ネイティブDLLの場合、もう少し作業が必要になります。
C++
は、リンクでそれを恐れていません。ILMergeは、VB NETでも非常に簡単に機能します。こちらhttps://github.com/dotnet/ILMergeを参照してください。ありがとう@ Shog9
はい、.NET実行可能ファイルをライブラリとマージすることは可能です。仕事を成し遂げるために利用可能な複数のツールがあります:
さらに、これをMono Linkerと組み合わせることができます。MonoLinkerは未使用のコードを削除するため、結果として得られるアセンブリが小さくなります。
もう1つの可能性は、.NETZを使用することです。これは、アセンブリの圧縮を可能にするだけでなく、DLLを直接exeにパックすることもできます。上記のソリューションとの違いは、.NETZはそれらをマージせず、別々のアセンブリのままですが、1つのパッケージにパックされることです。
.NETZは、Microsoft .NET Framework実行可能(EXE、DLL)ファイルを圧縮およびパックして、ファイルを小さくするオープンソースツールです。
ILMergeは、アセンブリにマネージコードのみがある場合、アセンブリを1つのアセンブリに結合できます。コマンドラインアプリを使用するか、exeへの参照を追加してプログラムでマージできます。GUIバージョンにはEazfuscatorがあり、.Netzも無料です。有料アプリには、BoxedAppとSmartAssemblyが含まれます。
アセンブリをアンマネージコードとマージする必要がある場合は、SmartAssemblyをお勧めします。私はSmartAssemblyでしゃっくりをしたことはありませんでしたが、他のすべてとはしゃっくりでした。ここでは、必要な依存関係をリソースとしてメインexeに埋め込むことができます。
リソースにdllを埋め込んでからAppDomainのAssemblyに依存することで、アセンブリが管理されているか、混合モードであるかを心配する必要なく、これらすべてを手動で実行できますResolveHandler
。これは、最悪のケース、つまりアンマネージコードを含むアセンブリを採用することによるワンストップソリューションです。
static void Main()
{
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{
string assemblyName = new AssemblyName(args.Name).Name;
if (assemblyName.EndsWith(".resources"))
return null;
string dllName = assemblyName + ".dll";
string dllFullPath = Path.Combine(GetMyApplicationSpecificPath(), dllName);
using (Stream s = Assembly.GetEntryAssembly().GetManifestResourceStream(typeof(Program).Namespace + ".Resources." + dllName))
{
byte[] data = new byte[stream.Length];
s.Read(data, 0, data.Length);
//or just byte[] data = new BinaryReader(s).ReadBytes((int)s.Length);
File.WriteAllBytes(dllFullPath, data);
}
return Assembly.LoadFrom(dllFullPath);
};
}
ここで重要なのは、バイトをファイルに書き込み、その場所からロードすることです。鶏肉と卵の問題を回避するには、アセンブリにアクセスする前にハンドラーを宣言し、読み込み(アセンブリ解決)パーツ内でアセンブリメンバーにアクセスしない(またはアセンブリを処理する必要のあるものをインスタンス化しない)必要があります。またGetMyApplicationSpecificPath()
、一時ファイルが他のプログラムまたは自分で消去しようとする可能性があるため、一時ディレクトリが存在しないことにも注意してください(プログラムがdllにアクセスしているときに削除されるわけではありませんが、少なくともそれは厄介です。AppDataは良好です。ロケーション)。また、毎回バイトを書き込む必要があることに注意してください。DLLが既に存在する場所からロードすることはできません。
マネージドDLLの場合、バイトを書き込む必要はありませんが、DLLの場所から直接ロードするか、バイトを読み取ってメモリからアセンブリをロードするだけです。このように:
using (Stream s = Assembly.GetEntryAssembly().GetManifestResourceStream(typeof(Program).Namespace + ".Resources." + dllName))
{
byte[] data = new byte[stream.Length];
s.Read(data, 0, data.Length);
return Assembly.Load(data);
}
//or just
return Assembly.LoadFrom(dllFullPath); //if location is known.
ジェフリー・リヒターの抜粋はとても良いです。つまり、ライブラリを組み込みリソースとして追加し、何よりも先にコールバックを追加します。これは、コンソールアプリのMainメソッドの先頭に配置したコードのバージョンです(彼のページのコメントにあります)(ライブラリを使用するすべての呼び出しがMainとは異なるメソッドにあることを確認してください)。
AppDomain.CurrentDomain.AssemblyResolve += (sender, bargs) =>
{
String dllName = new AssemblyName(bargs.Name).Name + ".dll";
var assem = Assembly.GetExecutingAssembly();
String resourceName = assem.GetManifestResourceNames().FirstOrDefault(rn => rn.EndsWith(dllName));
if (resourceName == null) return null; // Not found, maybe another handler will find it
using (var stream = assem.GetManifestResourceStream(resourceName))
{
Byte[] assemblyData = new Byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
return Assembly.Load(assemblyData);
}
};
上記の@Bobbyのasnwerを拡張します。.csprojを編集して、IL- Repackを使用して、ビルド時にすべてのファイルを単一のアセンブリに自動的にパッケージ化できます。
Install-Package ILRepack.MSBuild.Task
以下は、ExampleAssemblyToMerge.dllをプロジェクト出力にマージする簡単なサンプルです。
<!-- ILRepack -->
<Target Name="AfterBuild" Condition="'$(Configuration)' == 'Release'">
<ItemGroup>
<InputAssemblies Include="$(OutputPath)\$(AssemblyName).exe" />
<InputAssemblies Include="$(OutputPath)\ExampleAssemblyToMerge.dll" />
</ItemGroup>
<ILRepack
Parallel="true"
Internalize="true"
InputAssemblies="@(InputAssemblies)"
TargetKind="Exe"
OutputFile="$(OutputPath)\$(AssemblyName).exe"
/>
</Target>
DLLを埋め込みリソースとして追加し、起動時に(それらが既に存在するかどうかを確認した後)プログラムでアプリケーションディレクトリに展開することができます。
セットアップファイルは非常に簡単に作成できますが、それだけの価値はないと思います。
編集:この手法は.NETアセンブリで簡単です。.NET以外のDLLを使用すると、作業が大幅に増加します(ファイルを解凍して登録する場所などを把握する必要があります)。
これをエレガントに処理できる別の製品は、SmartAssembly.comにあるSmartAssemblyです。この製品は、すべての依存関係を1つのDLLにマージすることに加えて、(オプションで)コードを難読化し、余分なメタデータを削除して結果のファイルサイズを削減し、ILを実際に最適化してランタイムパフォーマンスを向上させることもできます。
また、(必要に応じて)ソフトウェアに追加できる、有用なグローバル例外処理/レポート機能のようなものもあります。コマンドラインAPIもあるので、ビルドプロセスの一部にすることができます。
ILMergeアプローチもLars Holm JensenのAssemblyResolveイベントの処理も、プラグインホストでは機能しません。実行可能HがアセンブリPを動的にロードし、別のアセンブリで定義されたインターフェイスIPを介してそれにアクセスするとします。IPをHに埋め込むには、Larsのコードを少し変更する必要があります。
Dictionary<string, Assembly> loaded = new Dictionary<string,Assembly>();
AppDomain.CurrentDomain.AssemblyResolve += (sender, args) =>
{ Assembly resAssembly;
string dllName = args.Name.Contains(",") ? args.Name.Substring(0, args.Name.IndexOf(',')) : args.Name.Replace(".dll","");
dllName = dllName.Replace(".", "_");
if ( !loaded.ContainsKey( dllName ) )
{ if (dllName.EndsWith("_resources")) return null;
System.Resources.ResourceManager rm = new System.Resources.ResourceManager(GetType().Namespace + ".Properties.Resources", System.Reflection.Assembly.GetExecutingAssembly());
byte[] bytes = (byte[])rm.GetObject(dllName);
resAssembly = System.Reflection.Assembly.Load(bytes);
loaded.Add(dllName, resAssembly);
}
else
{ resAssembly = loaded[dllName]; }
return resAssembly;
};
新しいインスタンスを作成する代わりに、同じアセンブリを解決し、既存のアセンブリを返すための繰り返しの試行を処理するトリック。
編集: それが.NETのシリアライゼーションを台無しにしないように、あなたの中に埋め込まれていないすべてのアセンブリに対してnullを返すようにしてください、それによってデフォルトで標準の動作になります。これらのライブラリのリストは、次の方法で取得できます。
static HashSet<string> IncludedAssemblies = new HashSet<string>();
string[] resources = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceNames();
for(int i = 0; i < resources.Length; i++)
{ IncludedAssemblies.Add(resources[i]); }
渡されたアセンブリがに属していない場合は、nullを返しますIncludedAssemblies
。
簡単に聞こえるかもしれませんが、WinRarには、一連のファイルを自己解凍型実行可能ファイルに圧縮するオプションがあります。
等の抽出、全くポップアップウィンドウ、使用許諾契約書のテキスト、中に示すポップアップのための最終的なアイコン、抽出ファイル指定されたパス、ファイルへの抽出後に実行する、カスタムロゴ/テキスト:それは、設定可能なオプションの多くを持っている
いくつかのケースで有用である可能性があります。
.vbsスクリプトから呼び出されるcsc.exeコンパイラを使用しています。
xyz.csスクリプトで、ディレクティブの後に次の行を追加します(私の例はRenci SSHの場合です)。
using System;
using Renci;//FOR THE SSH
using System.Net;//FOR THE ADDRESS TRANSLATION
using System.Reflection;//FOR THE Assembly
//+ref>"C:\Program Files (x86)\Microsoft\ILMerge\Renci.SshNet.dll"
//+res>"C:\Program Files (x86)\Microsoft\ILMerge\Renci.SshNet.dll"
//+ico>"C:\Program Files (x86)\Microsoft CAPICOM 2.1.0.2 SDK\Samples\c_sharp\xmldsig\resources\Traffic.ico"
ref、res、icoタグは、以下の.vbsスクリプトによって取得され、cscコマンドを形成します。
次に、Mainにアセンブリリゾルバーの呼び出し元を追加します。
public static void Main(string[] args)
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
.
...そしてリゾルバ自体をクラスのどこかに追加します:
静的アセンブリCurrentDomain_AssemblyResolve(オブジェクト送信者、ResolveEventArgs args) { 文字列resourceName = new AssemblyName(args.Name).Name + ".dll"; 使用(var stream = Assembly.GetExecutingAssembly()。GetManifestResourceStream(resourceName)) { Byte [] assemblyData = new Byte [stream.Length]; stream.Read(assemblyData、0、assemblyData.Length); Assembly.Load(assemblyData);を返します。 } }
.csファイル名に一致するようにvbsスクリプトに名前を付けます(たとえば、ssh.vbsはssh.csを探します)。これにより、スクリプトを何度も簡単に実行できますが、私のような馬鹿者でない場合は、汎用スクリプトがドラッグアンドドロップで対象の.csファイルを取得できます。
薄暗い名前_、oShell、fso Set oShell = CreateObject( "Shell.Application") セットfso = CreateObject( "Scripting.fileSystemObject") 'VBSスクリプト名をターゲットファイル名にしてください '################################################ name_ = Split(wscript.ScriptName、 "。")(0) '.CSファイルから外部DLLとアイコン名を取得する '################################################# ###### 定数OPEN_FILE_FOR_READING = 1 セットobjInputFile = fso.OpenTextFile(name_& ".cs"、1) 'すべてをアレイに読み込む '############################# inputData = Split(objInputFile.ReadAll、vbNewline) 各strData In inputData もしleft(strData、7)= "// + ref>"なら csc_references = csc_references& "/ reference:"&trim(replace(strData、 "// + ref>"、 ""))& "" 終われば もしleft(strData、7)= "// + res>"なら csc_resources = csc_resources& "/ resource:"&trim(replace(strData、 "// + res>"、 ""))& "" 終われば もしleft(strData、7)= "// + ico>"なら csc_icon = "/ win32icon:"&trim(replace(strData、 "// + ico>"、 ""))& "" 終われば 次 objInputFile.Close 'ファイルをコンパイルする '################ oShell.ShellExecute "c:\ windows \ microsoft.net \ framework \ v3.5 \ csc.exe"、 "/ warn:1 / target:exe"&csc_references&csc_resources&csc_icon& ""&name_& ".cs" 、「」、「runas」、2 WScript.Quit(0)
C#でハイブリッドネイティブ/マネージアセンブリを作成することは可能ですが、それほど簡単ではありません。Visual C ++コンパイラはハイブリッドアセンブリを他の何よりも簡単に作成できるため、代わりにC ++を使用した方がはるかに簡単です。
ハイブリッドアセンブリを作成するための厳密な要件がない限り、MusiGenesisに同意します。これは、C#を使用してトラブルを起こすだけの価値はありません。必要な場合は、代わりにC ++ / CLIへの移行を検討してください。
一般的に、あなたが説明しているように、アセンブリのマージを実行するには、何らかのビルド後のツールが必要になります。Eazfuscator(eazfuscator.blogspot.com/)と呼ばれる無料のツールがあり、アセンブリのマージも処理するバイトコードのマングリング用に設計されています。これをVisual Studioのビルド後のコマンドラインに追加してアセンブリをマージできますが、トライバルアセンブリ以外のマージシナリオで発生する問題のため、距離は異なります。
また、ビルドmake untility NANTにビルド後にアセンブリをマージする機能があるかどうかを確認することもできますが、機能が組み込まれているかどうかについては、私自身がNANTに詳しくありません。
また、アプリケーションのビルドの一部としてアセンブリのマージを実行する多くのVisual Studioプラグインもあります。
または、これを自動的に実行する必要がない場合は、ILMergeなど、.netアセンブリを1つのファイルにマージするツールがいくつかあります。
アセンブリのマージで私が抱えていた最大の問題は、同様の名前空間を使用しているかどうかです。または、さらに悪いことに、同じdllの異なるバージョンを参照してください(私の問題は通常、NUnit dllファイルにありました)。