コンパイルされた実行可能ファイルにDLLを埋め込む


619

既存のDLLをコンパイル済みのC#実行可能ファイルに埋め込むことは可能ですか(配布するファイルが1つだけになるように)?可能であれば、どうすればそれを実行できますか?

通常、私はDLLを外に置いてセットアッププログラムですべてを処理するだけでかっこいいですが、これについて質問してくれた人が何人かいて、正直にわかりません。


:私はあなたにもお好みのスキームで組み立てを圧縮.NETZユーティリティ、チェックアウトをお勧めしhttp://madebits.com/netz/help.php#single
ネイサンBaulch

2
可能ですが、実行ファイルが大きくなります(dllのエンコードにはBase64が使用されます)。
パヴェルDyda

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

2
@PawełDyda:生のバイナリデータをPEイメージに埋め込むことができます(RCDATAを参照)。変換は不要です(または推奨されません)。
IInspectable 2017

回答:


761

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


79
この素晴らしい提案をありがとう。パッケージをインストールすれば完了です。デフォルトでアセンブリも圧縮します。
ダニエル

9
「私も」というのは嫌いですが、私もそうです。これにより、頭痛が大幅に軽減されました。推薦ありがとうございます!これにより、1つのexeに再配布するために必要なすべてのものをパッケージ化でき、元のexeとdllを組み合わせたものよりも小さくなりました...これを数日間使用しているだけなので、私はそうは言えません。ペースを上げていきましたが、ポップアップの不良がなければ、これがツールボックスの通常のツールになっているのがわかります。うまくいきます!
mattezell 14年

20
かっこいいね。ただし、デメリットがあります。Windowsで生成されたアセンブリは、mono Linuxとバイナリ互換ではなくなりました。つまり、アセンブリをLinux monoに直接デプロイすることはできません。
タイラーロング

7
これは素敵です!vs2018を使用している場合は、FodyWeavers.xmlファイルをプロジェクトのルートに配置することを忘れないでください。
アランディープ

4
最後のコメントの補足として、次のコンテンツを含むFodyWeavers.xmlをプロジェクトに追加します:<?xml version = "1.0" encoding = "utf-8"?> <Weavers VerifyAssembly = "true"> <Costura /> </ Weavers>
HHenn

89

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/


6
この動作はそのまま使用できます。私の回答をチェックしてくださいstackoverflow.com/a/20306095/568266
Matthias

4
AshRoweからのブログに関する非常に役立つコメントに注意することも重要です。カスタムテーマがインストールされている場合、クラッシュして書き込みが発生するPresentationFramework.Themeアセンブリを解決しようとします!AshRoweの提案に従って、次のようにdllNameにPresentationFrameworkが含まれているかどうかを簡単に確認できます。if(dllName.ToLower()。Contains( "presentationframework"))return null;
YasharBahman、2014年

4
これに関する2つのコメント。1つbytesは、null かどうかを確認し、nullの場合はnullを返すことです。結局のところ、dllがリソースにない可能性があります。2:これは、そのクラス自体がそのアセンブリからの「使用」を持たない場合にのみ機能します。コマンドラインツールの場合、実際のプログラムコードを新しいファイルに移動し、これを実行して古いクラスの元のメインを呼び出す小さな新しいメインプログラムを作成する必要がありました。
Nyerguds、2014年

2
このアプローチの利点は、目的の機能を実現するために外部ライブラリをインストールすることに依存しないことです。このアプローチの欠点は、マネージdllに関してのみ有用であることです。相互運用DLL(少なくとも私のテストでは)は、assemblyresolveイベントを起動せず、Assembly.Load(<interopのバイト数.dll>)は、将来的に望ましい効果を達成しません。stackoverflow.com/questions/13113131/…問題に関する私の2c
XDS

3
誰かが私の問題に遭遇した場合に備えて:.dll名前にハイフン(つまりtwenty-two.dll)が含まれている場合、それらもアンダースコア(つまりtwenty_two.dll)に置き換えられます。あなたはこれにコード行を変更することができます:dllName = dllName.Replace(".", "_").Replace("-", "_");
ミカVertal

87

それらが実際にマネージアセンブリである場合は、ILMergeを使用できます。ネイティブDLLの場合、もう少し作業が必要になります。

参照: C ++ windows dllをC#アプリケーションexeにマージするにはどうすればよいですか?


ネイティブDLLのマージに興味があります。資料はありますか?
Baiyan Huang


で@BaiyanHuang見github.com/boxedapp/bxilmerge、アイデアは、ネイティブDLLの「ILMerge」を作ることです。
Artem Razin

私のようなVB NET開発者C++は、リンクでそれを恐れていません。ILMergeは、VB NETでも非常に簡単に機能します。こちらhttps://github.com/dotnet/ILMergeを参照してください。ありがとう@ Shog9
Ivan Ferrer Villa

26

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

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

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

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

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



うわー、ようやく見つけたと思って、このコメントを読んだ。まるでなくなっているようです。フォークはありますか?
2016年

まあ、それはGitHubに移動しただけで、ウェブサイトにリンクされなくなりました。おそらくそれはもうサポートされていませんが、まだそこにあります。リンクを更新しました。
ボビー

20

ILMergeは、アセンブリにマネージコードのみがある場合、アセンブリを1つのアセンブリに結合できます。コマンドラインアプリを使用するか、exeへの参照を追加してプログラムでマージできます。GUIバージョンにはEazfuscatorがあり、.Netzも無料です。有料アプリには、BoxedAppSmartAssemblyが含まれます。

アセンブリをアンマネージコードとマージする必要がある場合は、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.

アセンブリが完全に管理されていない場合は、このリンクまたはこのようなDLLをロードする方法についてこれを確認できます。


リソースの「ビルドアクション」を「埋め込みリソース」に設定する必要があることに注意してください。
Mavamaarten 14年

@Mavamaarten必ずしもそうとは限りません。事前にプロジェクトのResources.resxに追加されている場合は、追加する必要はありません。
Nyerguds 2014年

2
EAZfuscatorが商用化されました。
Telemat、2014年

16

ジェフリー・リヒター抜粋はとても良いです。つまり、ライブラリを組み込みリソースとして追加し、何よりも先にコールバックを追加します。これは、コンソールアプリの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);
            }
        };

1
それを少し変えて、仕事をしました、tnx buddy!
Sean Ed-Man

プロジェクトlibz.codeplex.comはこのプロセスを使用しますが、イベントハンドラーの管理や特別なコードなど、 " Managed Extensibility Framework Catalogs " を壊さないようにする(このプロセス自体がこのプロセスを壊す)
スコットチェンバレン

それは素晴らしいことです!!ありがとう@Steve
Ahmer Afzal

14

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

  1. nuget ILRepack.MSBuild.Taskパッケージをインストールします Install-Package 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>

1
IL-Repackの構文が変更されました。リンクされたgithubリポジトリ(github.com/peters/ILRepack.MSBuild.Task)にあるREADME.mdを確認してください。この方法が私にとって有効な唯一の方法であり、ワイルドカードを使用して、含めたいすべてのdllに一致させることができました。
Seabass77

8

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

セットアップファイルは非常に簡単に作成できますが、それだけの価値はないと思います。

編集:この手法は.NETアセンブリで簡単です。.NET以外のDLLを使用すると、作業が大幅に増加します(ファイルを解凍して登録する場所などを把握する必要があります)。


これを行う方法を説明する素晴らしい記事があります。codeproject.com
青みがかった

8

これをエレガントに処理できる別の製品は、SmartAssembly.comにあるSmartAssemblyです。この製品は、すべての依存関係を1つのDLLにマージすることに加えて、(オプションで)コードを難読化し、余分なメタデータを削除して結果のファイルサイズを削減し、ILを実際に最適化してランタイムパフォーマンスを向上させることもできます。

また、(必要に応じて)ソフトウェアに追加できる、有用なグローバル例外処理/レポート機能のようなものもあります。コマンドラインAPIもあるので、ビルドプロセスの一部にすることができます。


7

ILMergeアプローチもLars Holm JensenのAssemblyResolveイベントの処理も、プラグインホストでは機能しません。実行可能HがアセンブリPを動的にロードし、別のアセンブリで定義されたインターフェイスIPを介してそれにアクセスするとします。IPHに埋め込むには、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


コメントではなく回答として投稿してください。私は他の人の答えにコメントする権利がありません。
Ant_222 2013年

5

.NET Core 3.0は、単一の.exeへのコンパイルをネイティブでサポートします

この機能は、プロジェクトファイル(.csproj)で次のプロパティを使用することで有効になります。

    <PropertyGroup>
        <PublishSingleFile>true</PublishSingleFile>
    </PropertyGroup>

これは、外部ツールなしで行われます。

詳細については、この質問に対する私の回答参照してください。


3

簡単に聞こえるかもしれませんが、WinRarには、一連のファイルを自己解凍型実行可能ファイルに圧縮するオプションがあります。
等の抽出、全くポップアップウィンドウ、使用許諾契約書のテキスト、中に示すポップアップのための最終的なアイコン、抽出ファイル指定されたパス、ファイルへの抽出後に実行する、カスタムロゴ/テキスト:それは、設定可能なオプションの多くを持っている
いくつかのケースで有用である可能性があります。


Windows自体にも、iexpressと呼ばれる同様のツールがあります。ここではチュートリアルだ
イワン・フェレールヴィラ

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タグは、以下の.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)

0

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

ハイブリッドアセンブリを作成するための厳密な要件がない限り、MusiGenesisに同意します。これは、C#を使用してトラブルを起こすだけの価値はありません。必要な場合は、代わりにC ++ / CLIへの移行を検討してください。


0

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

また、ビルドmake untility NANTにビルド後にアセンブリをマージする機能があるかどうかを確認することもできますが、機能が組み込まれているかどうかについては、私自身がNANTに詳しくありません。

また、アプリケーションのビルドの一部としてアセンブリのマージを実行する多くのVisual Studioプラグインもあります。

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

アセンブリのマージで私が抱えていた最大の問題は、同様の名前空間を使用しているかどうかです。または、さらに悪いことに、同じdllの異なるバージョンを参照してください(私の問題は通常、NUnit dllファイルにありました)。


1
Eazfuscatorは、IlMerge、AFAIKを呼び出すだけです。
ボビー

+1ボビー。覚えておくべきだった。Eazfucatorが行うすべてのことについて、より一般的な構成ファイルを使用して、ILMergeへの実際の呼び出しを抽象化します。
wllmsaccnt
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.