web-dev-qa-db-ja.com

コンパイル済み実行ファイルにDLLを埋め込む

あなたが知っている、私はどこでもこれのための良い答えを見ていない。コンパイル済みのC#実行可能ファイルに既存のDLLを埋め込むことは可能ですか(配布するファイルは1つだけになります)。可能であれば、どうすればよいでしょうか。

通常、私はDLLを外に置いてセットアッププログラムにすべてを処理させるだけでいいのですが、仕事をしている人が何人かいましたが、正直なところわからないのです。

541
Merus

Costura.Fody - あなたのアセンブリにリソースを埋め込むためのはるかに最良で最も簡単な方法を使うことを強く勧めます。 NuGetパッケージとして入手可能です。

Install-Package Costura.Fody

プロジェクトに追加すると、出力ディレクトリにコピーされたすべての参照が自動的にmainアセンブリに埋め込まれます。プロジェクトにターゲットを追加して埋め込みファイルをきれいにすることができます。

Install-CleanReferencesTarget

Pdbを含めるか、特定のアセンブリを除外するか、またはその場でアセンブリを抽出するかを指定することもできます。私の知る限りでは、管理されていないアセンブリもサポートされています。

更新

現在、何人かの人々が DNXのサポートを追加しようとしています

676
Matthias

それらが実際に管理されているアセンブリの場合は、 ILMerge を使用できます。ネイティブDLLの場合は、もう少し作業が必要です。

下記も参照してください。 どのようにC++ windows dllをC#application exeに統合することができますか?

81
Shog9

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/

77

はい、.NET実行ファイルをライブラリとマージすることは可能です。仕事を終わらせるために利用可能な複数のツールがあります。

  • ILMerge は、複数の.NETアセンブリを単一のアセンブリにマージするために使用できるユーティリティです。
  • Mono mkbundle 、libmonoを含むexeとすべてのアセンブリを単一のバイナリパッケージにパッケージ化します。
  • IL-Repack はILMergeに代わるFLOSSであり、いくつかの追加機能があります。

さらに、これを Mono Linker と組み合わせることで、未使用のコードが削除されるため、生成されるアセンブリが小さくなります。

もう一つの可能​​性は .NETZ を使うことです。これはアセンブリの圧縮を可能にするだけでなく、DLLを直接exeにパックすることもできます。上記のソリューションとの違いは、.NETZではそれらがマージされず、別々のアセンブリに留まりますが、1つのパッケージにまとめられることです。

.NETZは、Microsoft .NET Frameworkの実行可能ファイル(EXE、DLL)を圧縮して小さくするためのオープンソースツールです。

25
Bobby

アセンブリにマネージコードしかない場合、 ILMerge はアセンブリを1つのアセンブリにまとめることができます。あなたは、コマンドラインアプリを使用するか、exeファイルへの参照を追加してプログラム的にマージすることができます。 GUI版の場合は Eazfuscator 、そして .Netz もあり、どちらも無料です。有料アプリには BoxedAppSmartAssembly があります。

アセンブリをアンマネージコードとマージする必要がある場合は、 SmartAssembly をお勧めします。私は SmartAssembly を使ってしゃっくりをしたことは一度もありませんでした。ここでは、必要な依存関係をメインのexeファイルへのリソースとして埋め込むことができます。

あなたのリソースにdllを埋め込んでAppDomainのAssembly ResolveHandlerに頼ることでAssemblyが管理されているか混在モードであるかを心配する必要はありません。これは最悪のケース、すなわちアンマネージドコードを持つアセンブリを採用することによるワンストップソリューションです。

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にアクセスしている間は削除されません)。良い場所)。また、毎回バイトを書き込まなければならないことにも注意してください、あなたは単にその場所からロードすることはできません。

マネージド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.

アセンブリが完全に管理されていない場合は、そのようなDLLをロードする方法について、この link または this が表示されます。

18
nawfal

Jeffrey Richterによる抜粋 はとても良いです。つまり、ライブラリを埋め込みリソースとして追加し、他のものの前にコールバックを追加します。これは私がコンソールアプリケーションの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);
            }
        };
11
Steve

@ Bobbyのasnwer を上に展開する。 .csprojを編集して IL-Repack を使用すると、ビルド時にすべてのファイルを単一のアセンブリに自動的にパッケージ化できます。

  1. Install-Package ILRepack.MSBuild.Taskを付けてnuget ILRepack.MSBuild.Taskパッケージをインストールします。
  2. .csprojのAfterBuildセクションを編集します。

これは、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>
11
Josh

チェック boxedapp

それはあらゆるアプリにdllを埋め込むことができます。もちろんC#でも書かれています:)

それが役に立てば幸い。

8
Art

DLLを埋め込みリソースとして追加してから、起動時にそれらがアプリケーションディレクトリにアンパックされるようにすることができます(すでに存在するかどうかを確認した後)。

セットアップファイルはとても簡単に作成できるので、これだけの価値はないと思います。

編集:このテクニックは.NETアセンブリを使えば簡単でしょう。 .NET以外のDLLでは、作業がずっと多くなります(ファイルを解凍して登録する場所などを特定する必要があります)。

7
MusiGenesis

.NETZユーティリティをチェックすることをお勧めします。このユーティリティも、選択したスキームでアセンブリを圧縮します。

http://madebits.com/netz/help.php#single

7
Nathan Baulch

これをエレガントに処理できるもう1つの製品はSmartAssemblyです。 SmartAssembly.com にあります。この製品は、すべての依存関係を1つのDLLにマージするだけでなく(オプションで)コードを難読化し、余分なメタデータを削除してファイルサイズを縮小し、さらにランタイムパフォーマンスを向上させるためにILを最適化します。私が理解するのに時間をかけなかったが有用であるかもしれないそれがあなたのソフトウェアに追加するある種の世界的な例外処理/報告機能(もし望むなら)もあります。コマンドラインAPIもあるので、ビルドプロセスの一部にすることができます。

6
Nathan

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]);  }

渡されたアセンブリがIncludedAssembliesに属していない場合はnullを返します。

6
Ant_222

ILMerge まさにあなたが望むことをする。

4
plinth

ILMerge 以外にも、コマンドラインスイッチに煩わされたくない場合は、 ILMerge-Gui をお勧めします。それはオープンソースプロジェクトです、本当に良いです!

3
tyron

それは単純に聞こえるかもしれませんが、WinRarはたくさんのファイルを自己解凍型実行ファイルに圧縮するオプションを与えます。
これにはたくさんの設定可能なオプションがあります:ファイナルアイコン、指定されたパスにファイルを抽出する、抽出後に実行するファイル、抽出時に表示されるポップアップのカスタムロゴ/テキスト、ポップアップウィンドウなし、ライセンス契約テキストなど.
場合によっては役に立つかもしれません。

2

私は.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タグは、cscコマンドを形成するために以下の.vbsスクリプトによって取得されます。

次に、メインにAssembly resolver呼び出し側を追加します。

public static void Main(string[] args)
{
    AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
    .

...そしてリゾルバ自体をクラスのどこかに追加する:

静的アセンブリCurrentDomain_AssemblyResolve(オブジェクトセンダ、ResolveEventArgs args)
 {
文字列resourceName = 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); 
 Assembly.Load(assemblyData); 
} 
 
} 
を返します。

Vbsスクリプトの名前を.csファイル名と一致するように指定します(例:ssh.vbsはssh.csを探します)。これにより、スクリプトの実行が非常に簡単になりますが、私のようなばか者ではない場合は、汎用のスクリプトでドラッグアンドドロップからターゲットの.csファイルを選択できます。

 Dim name_、oShell、fso 
 Set oShell = CreateObject( "Shell.Application")
 Set fso = CreateObject( "Scripting.fileSystemObject")
 
 ''ターゲットファイル名としてVBSスクリプト名を使用します
 '############################## ################## 
 name_ = Split(wscript.ScriptName、 "。")(0)
 
 'GET .CSファイルからの外部DLLとアイコンの名前
 '################################# ###################### 
 Const OPEN_FILE_FOR_READING = 1 
設定objInputFile = fso.OpenTextFile(name_& ".cs"、 1)
 
 '配列にすべてを読む
' ########################## ### 
 inputData = Split(objInputFile.ReadAll、vbNewline)
 
各strDataについてinputData 
 
が残っている場合(strData、7) = "// + ref>" then 
 csc_references = csc_references& "/ reference:"&amp;(replace(strData、 "// + ref>"、 ""))& "" 
 
の場合は終了
 left(strData、7)= "// + res>"の場合、
 csc_resources = csc_resources& "/ resource:"&(remove(strData、 "// + res>"、 ""))& "" 
 
 
の場合は終了(strData、7)= "// + ico>"の場合
 csc_icon = "/ win32icon : "&trim(replace(strData、" // + ico> "、" "))&" "
次[
 
 objInputFile.Close 
 
 
 'COMPILE THE FILE 
 ############### 
 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)
1
Mark Llewellyn

あなたが説明しているように一般的にあなたはアセンブリマージを実行するためにポストビルドツールの何らかの形を必要とするでしょう。 Eazfuscator(eazfuscator.blogspot.com/)という無料のツールがあります。これはアセンブリのマージも処理するバイトコードマングル用に設計されています。これをVisual Studioのポストビルドコマンドラインに追加してアセンブリをマージすることができますが、他のアセンブリ以外のアセンブリのマージシナリオで発生する問題によって、マイレージは異なります。

NANTがビルド後にアセンブリをマージする能力を持っているかどうかを確認することもできますが、機能が組み込まれているかどうかを言うのに私はNANT自身に十分な知識がありません。

アプリケーション構築の一環としてアセンブリのマージを実行するVisual Studioプラグインも数多くあります。

あるいは、これを自動的に行う必要がない場合は、.netアセンブリを単一のファイルにマージするILMergeなどのツールがいくつかあります。

アセンブリのマージに関して私が抱えていた最大の問題は、それらが類似の名前空間を使用している場合です。もっと悪いことに、同じDLLの異なるバージョンを参照してください(私の問題は一般的にNUnitのdllファイルに関するものです)。

0
wllmsaccnt

C#でハイブリッドネイティブ/マネージアセンブリを作成することは可能ですがそれほど簡単ではありません。 Visual C++コンパイラは他のものと同じくらい簡単にハイブリッドアセンブリを作成できるので、代わりにC++を使用した方がはるかに簡単です。

あなたがハイブリッドアセンブリを作成するという厳格な要件を持っていない限り、私はMusiGenesisに同意します。あなたがそれをする必要があるなら、おそらくC++/CLIへの移行を見てください。

0