694

Is it possible to embed a pre-existing DLL into a compiled C# executable (so that you only have one file to distribute)? If it is possible, how would one go about doing it?

Normally, I'm cool with just leaving the DLLs outside and having the setup program handle everything, but there have been a couple of people at work who have asked me this and I honestly don't know.

4

16 に答える 16

816

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 )

于 2013-11-30T21:53:21.077 に答える
100

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/

于 2011-06-15T18:20:12.667 に答える
89

If they're actually managed assemblies, you can use ILMerge. For native DLLs, you'll have a bit more work to do.

See also: How can a C++ windows dll be merged into a C# application exe?

于 2008-10-09T23:17:15.273 に答える
26

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

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

さらに、これをモノリンカーと組み合わせることができます。これにより、未使用のコードが削除されるため、結果のアセンブリが小さくなります。

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

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

于 2010-10-28T14:06:14.660 に答える
21

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 をロードする方法について、このリンクまたはこれを確認できます。

于 2012-05-15T11:46:30.127 に答える
14

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

  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>
于 2015-03-07T20:40:24.917 に答える
8

You could add the DLLs as embedded resources, and then have your program unpack them into the application directory on startup (after checking to see if they're there already).

Setup files are so easy to make, though, that I don't think this would be worth it.

EDIT: This technique would be easy with .NET assemblies. With non-.NET DLLs it would be a lot more work (you'd have to figure out where to unpack the files and register them and so on).

于 2008-10-09T23:14:15.233 に答える
8

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

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

于 2008-10-09T23:40:23.200 に答える
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

于 2013-10-09T10:55:37.083 に答える
7

次の方法では、外部ツールを使用せず、必要なすべての DLLを自動的に含めます(手動操作は不要で、すべてコンパイル時に行われます)。

ここでILMergeILRepackまたはJeffrey Ritcher メソッドを使用するように言っている多くの回答を読みましたが、そのどれもがWPF アプリケーションで機能せず、使いやすいものでもありませんでした。

多くの DLL がある場合、必要なものを exe に手動で含めるのは難しい場合があります。私が見つけた最良の方法は、ここStackOverflowのWeggedによって説明されました

わかりやすくするために、ここに彼の回答をコピーして貼り付けました(すべてWeggedの功績です)


.csproj1) これをファイルに追加します。

<Target Name="AfterResolveReferences">
  <ItemGroup>
    <EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
      <LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
    </EmbeddedResource>
  </ItemGroup>
</Target>

2) MainProgram.csを次のようにします。

[STAThreadAttribute]
public static void Main()
{
    AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
    App.Main();
}

OnResolveAssembly3)メソッドを追加します。

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);
    }
}
于 2020-07-16T06:50:03.120 に答える
3

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

于 2015-01-20T09:03:26.697 に答える
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);
    .

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

    static Assembly 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) を返します。
        }

    }

.cs ファイル名に一致するように vbs スクリプトに名前を付けます (たとえば、ssh.vbs は ssh.cs を検索します)。これにより、スクリプトを何度も簡単に実行できますが、私のような馬鹿でなければ、一般的なスクリプトでドラッグ アンド ドロップからターゲットの .cs ファイルを取得できます。

    Dim name_,oShell,fso
    Set oShell = CreateObject("Shell.Application")
    fso = CreateObject("Scripting.fileSystemObject") を設定します。

    'VBS スクリプト名をターゲット ファイル名として使用
    '##################################################
    name_ = Split(wscript.ScriptName, ".")(0)

    '.CS ファイルから外部 DLL とアイコン名を取得します
    '################################################## ######
    Const OPEN_FILE_FOR_READING = 1
    objInputFile = fso.OpenTextFile(name_ & ".cs", 1) を設定します。

    'すべてを配列に読み込む
    '################################
    inputData = Split(objInputFile.ReadAll, vbNewline)

    各 strData In inputData について

        if left(strData,7)="//+ref>" なら
            csc_references = csc_references & " /reference:" & トリム (replace(strData,"//+ref>","")) & " "
        終了する場合

        if left(strData,7)="//+res>" なら
            csc_resources = csc_resources & " /resource:" & トリム (replace(strData,"//+res>","")) & " "
        終了する場合

        if left(strData,7)="//+ico>" なら
            csc_icon = " /win32icon:" & トリム (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" 、「」、「ルーナス」、2


    WScript.Quit(0)
于 2016-04-28T13:58:30.293 に答える
0

It's possible but not all that easy, to create a hybrid native/managed assembly in C#. Were you using C++ instead it'd be a lot easier, as the Visual C++ compiler can create hybrid assemblies as easily as anything else.

Unless you have a strict requirement to produce a hybrid assembly, I'd agree with MusiGenesis that this isn't really worth the trouble to do with C#. If you need to do it, perhaps look at moving to C++/CLI instead.

于 2008-10-09T23:19:44.943 に答える
0

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

build make untility NANT にビルド後にアセンブリをマージする機能があるかどうかを確認することもできますが、機能が組み込まれているかどうかを判断できるほど NANT に精通していません。

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

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

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

于 2010-10-28T14:00:53.190 に答える