webentwicklung-frage-antwort-db.com.de

Einbetten von DLLs in eine kompilierte ausführbare Datei

Ist es möglich, ein bereits vorhandenes DLL in eine kompilierte ausführbare C # -Datei einzubetten (sodass Sie nur eine Datei zum Verteilen haben)? Wenn es möglich ist, wie würde man das machen?

Normalerweise finde ich es cool, wenn ich nur die DLLs draußen lasse und das Setup-Programm alles erledigt, aber es gab ein paar Leute bei der Arbeit, die mich das gefragt haben und ich weiß es ehrlich gesagt nicht.

563
Merus

Ich empfehle dringend die Verwendung von Costura.Fody - bei weitem der beste und einfachste Weg, Ressourcen in Ihre Assembly einzubetten. Es ist als NuGet-Paket erhältlich.

Install-Package Costura.Fody

Nach dem Hinzufügen zum Projekt werden alle Referenzen, die in das Ausgabeverzeichnis kopiert wurden, automatisch in Ihre Hauptassembly eingebettet. Möglicherweise möchten Sie die eingebetteten Dateien bereinigen, indem Sie Ihrem Projekt ein Ziel hinzufügen:

Install-CleanReferencesTarget

Sie können auch angeben, ob die PDBs eingeschlossen, bestimmte Assemblys ausgeschlossen oder die Assemblys sofort extrahiert werden sollen. Soweit ich weiß, werden auch nicht verwaltete Assemblys unterstützt.

Update

Derzeit versuchen einige Leute, nterstützung für DNX hinzuzufügen.

708
Matthias

Wenn es sich tatsächlich um verwaltete Assemblys handelt, können Sie ILMerge verwenden. Bei nativen DLLs müssen Sie noch ein wenig arbeiten.

Siehe auch: Wie kann eine C++ - Windows-DLL in eine C # -Anwendungs-Exe eingefügt werden?

84
Shog9

Klicken Sie in Visual Studio einfach mit der rechten Maustaste auf Ihr Projekt, wählen Sie Projekteigenschaften -> Ressourcen -> Ressource hinzufügen -> Vorhandene Datei hinzufügen ... und fügen Sie den folgenden Code in Ihre App.xaml.cs oder eine entsprechende Datei ein.

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

Hier ist mein ursprünglicher Blogbeitrag: http://codeblog.larsholm.net/2011/06/embed-dlls-easily-in-a-net-Assembly/

80

Ja, es ist möglich, ausführbare .NET-Dateien mit Bibliotheken zusammenzuführen. Es gibt mehrere Tools, mit denen Sie Ihre Arbeit erledigen können:

  • ILMerge ist ein Dienstprogramm, mit dem mehrere .NET-Assemblys zu einer einzigen Assembly zusammengeführt werden können.
  • Mono mkbundle , packt eine Exe und alle Assemblys mit libmono in ein einzelnes Binärpaket.
  • IL-Repack ist eine FLOSS-Alternative zu ILMerge mit einigen zusätzlichen Funktionen.

Zusätzlich kann dies mit dem Mono Linker kombiniert werden, der nicht verwendeten Code entfernt und daher die resultierende Assembly verkleinert.

Eine andere Möglichkeit ist die Verwendung von . NETZ , das nicht nur das Komprimieren einer Assembly ermöglicht, sondern auch die DLLs direkt in die EXE-Datei packen kann. Der Unterschied zu den oben genannten Lösungen besteht darin, dass sie in .NETZ nicht zusammengeführt werden, sondern separate Assemblys bleiben, die jedoch in einem Paket zusammengefasst sind.

.NETZ ist ein Open Source-Tool, das die ausführbaren Microsoft .NET Framework-Dateien (EXE, DLL) komprimiert und komprimiert, um sie zu verkleinern.

26
Bobby

ILMerge kann Assemblys zu einer einzigen Assembly zusammenfassen, sofern die Assembly nur Code verwaltet. Sie können die Befehlszeilen-App verwenden oder einen Verweis auf die exe hinzufügen und programmgesteuert zusammenführen. Für eine GUI-Version gibt es Eazfuscator und auch . Netz , die beide kostenlos sind. Bezahlte Apps beinhalten BoxedApp und SmartAssembly .

Wenn Sie Assemblys mit nicht verwaltetem Code zusammenführen müssen, empfehle ich SmartAssembly . Ich hatte nie Probleme mit SmartAssembly , sondern mit allen anderen. Hier können die erforderlichen Abhängigkeiten als Ressourcen in Ihre Haupt-Exe eingebettet werden.

Sie können dies alles manuell erledigen, ohne sich Sorgen machen zu müssen, wenn Assembly verwaltet wird oder sich im gemischten Modus befindet, indem Sie dll in Ihre Ressourcen einbetten und dann auf AppDomains Assembly ResolveHandler vertrauen. Dies ist eine One-Stop-Lösung, bei der der ungünstigste Fall übernommen wird, dh Assemblys mit nicht verwaltetem Code.

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

Der Schlüssel hier ist, die Bytes in eine Datei zu schreiben und von dort zu laden. Um Probleme mit Hühnern und Eiern zu vermeiden, müssen Sie sicherstellen, dass Sie den Handler deklarieren, bevor Sie auf die Assembly zugreifen, und dass Sie nicht auf die Assembly-Mitglieder innerhalb des Ladeteils (Assembly Resolution) zugreifen (oder alles instanziieren, was mit der Assembly zu tun hat). Stellen Sie außerdem sicher, dass GetMyApplicationSpecificPath() kein temporäres Verzeichnis ist, da temporäre Dateien möglicherweise von anderen Programmen oder von Ihnen selbst gelöscht werden (nicht, dass sie gelöscht werden, während Ihr Programm auf die DLL zugreift, aber zumindest von a Belästigung. AppData ist guter Ort). Beachten Sie außerdem, dass Sie die Bytes jedes Mal schreiben müssen, wenn Sie nicht vom Speicherort laden können, nur weil sich die DLL bereits dort befindet.

Für verwaltete DLLs müssen Sie keine Bytes schreiben, sondern direkt vom Speicherort der DLL laden oder einfach die Bytes lesen und die Assembly aus dem Speicher laden. So oder so:

    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.

Wenn die Assembly vollständig nicht verwaltet wird, können Sie hier sehen link oder this , wie solche DLLs geladen werden.

19
nawfal

Das Auszug von Jeffrey Richter ist sehr gut. Kurz gesagt, fügen Sie die Bibliothek als eingebettete Ressourcen hinzu und fügen Sie vor allen anderen einen Rückruf hinzu. Hier ist eine Version des Codes (zu finden in den Kommentaren seiner Seite), die ich zu Beginn der Main-Methode für eine Konsolen-App angegeben habe (stellen Sie nur sicher, dass alle Aufrufe, die die Bibliotheken verwenden, eine andere Methode als Main haben).

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);
            }
        };
14
Steve

Zum Erweitern auf @ Bobbys Antwort oben. Sie können Ihre .csproj-Datei so bearbeiten, dass sie IL-Repack verwendet, um alle Dateien beim Erstellen automatisch in eine einzelne Assembly zu packen.

  1. Installieren Sie das nuget ILRepack.MSBuild.Task-Paket mit Install-Package ILRepack.MSBuild.Task
  2. Bearbeiten Sie den AfterBuild-Abschnitt Ihres .csproj

Hier ist ein einfaches Beispiel, das ExampleAssemblyToMerge.dll in Ihre Projektausgabe einfügt.

<!-- 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>
14
Josh

Aktivieren Sie boxedapp

Es kann eine DLL in jede App einbetten. Natürlich auch in C # geschrieben :)

Ich hoffe es hilft.

8
Art

Sie können die DLLs als eingebettete Ressourcen hinzufügen und sie dann vom Programm beim Start in das Anwendungsverzeichnis entpacken lassen (nachdem Sie überprüft haben, ob sie bereits vorhanden sind).

Setup-Dateien sind jedoch so einfach zu erstellen, dass ich nicht denke, dass sich das lohnt.

BEARBEITEN: Diese Technik wäre mit .NET-Assemblys einfach. Mit Nicht-.NET-DLLs wäre es viel arbeitsintensiver (Sie müssten herausfinden, wo Sie die Dateien entpacken und registrieren müssen usw.).

8
MusiGenesis

Ich würde empfehlen, dass Sie sich das .NETZ-Dienstprogramm ansehen, das auch die Assembly mit einem Schema Ihrer Wahl komprimiert:

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

7
Nathan Baulch

Ein weiteres Produkt, das dies elegant bewältigen kann, ist SmartAssembly unter SmartAssembly.com . Dieses Produkt führt nicht nur alle Abhängigkeiten in einer einzigen DLL zusammen, sondern verschleiert (optional) Ihren Code, entfernt zusätzliche Metadaten, um die resultierende Dateigröße zu reduzieren, und kann die IL auch optimieren, um die Laufzeitleistung zu steigern. Es gibt auch eine Art von globaler Ausnahmebehandlungs-/Berichtsfunktion, die Ihrer Software hinzugefügt wird (falls gewünscht), die ich nicht verstanden habe, aber nützlich sein könnte. Ich glaube, es hat auch eine Befehlszeilen-API, so dass Sie es Teil Ihres Build-Prozesses machen können.

7
Nathan

Weder der ILMerge-Ansatz noch Lars Holm Jensens Bearbeitung des AssemblyResolve-Ereignisses funktionieren für einen Plug-in-Host. Angenommen, ausführbares H lädt Assembly P dynamisch und greift darauf zu über die Schnittstelle IP in einer separaten Assembly definiert. Um IP in H einzubetten, braucht man ein wenig Änderung von Lars 'Code:

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

Der Trick, um wiederholte Versuche zu verarbeiten, dieselbe Assembly aufzulösen und die vorhandene zurückzugeben, anstatt eine neue Instanz zu erstellen.

EDIT: Damit die .NET-Serialisierung nicht beeinträchtigt wird, stellen Sie sicher, dass für alle Assemblys, die nicht in Ihrer eingebettet sind, null zurückgegeben wird, und verwenden Sie dabei das Standardverhalten. Sie können eine Liste dieser Bibliotheken erhalten, indem Sie:

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

und geben Sie einfach null zurück, wenn die übergebene Assembly nicht zu IncludedAssemblies gehört.

7
Ant_222

ILMerge macht genau das, was Sie wollen.

4
plinth

Es mag simpel klingen, aber WinRar bietet die Möglichkeit, eine Reihe von Dateien in eine selbstextrahierende ausführbare Datei zu komprimieren.
Es stehen viele konfigurierbare Optionen zur Verfügung: endgültiges Symbol, Extrahieren von Dateien in den angegebenen Pfad, Datei, die nach dem Extrahieren ausgeführt werden soll, benutzerdefiniertes Logo/Texte für Popups, die während des Extrahierens angezeigt werden, kein Popup-Fenster, Lizenzvereinbarungstext usw.
Kann in einigen Fällen nützlich sein.

3

Neben ILMerge empfehle ich ILMerge-Gui , wenn Sie sich nicht um Befehlszeilenoptionen kümmern möchten. Es ist ein Open Source Projekt, wirklich gut!

3
tyron

Ich benutze den csc.exe-Compiler, der von einem .vbs-Skript aufgerufen wird.

Fügen Sie in Ihrem xyz.cs-Skript die folgenden Zeilen nach den Anweisungen ein (mein Beispiel gilt für 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"

Die Tags "ref", "res" und "ico" werden vom folgenden VBS-Skript zur Bildung des Befehls "csc" abgerufen.

Fügen Sie dann den Assembly Resolver-Aufrufer im Hauptfenster hinzu:

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

... und füge den Resolver selbst irgendwo in der Klasse hinzu:

 static Assembly CurrentDomain_AssemblyResolve (Objektabsender, ResolveEventArgs-Argumente) 
 {
 String resourceName = new AssemblyName (Argumente.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) zurückgeben; 
} 
 
} 

Ich benenne das VBS-Skript so, dass es mit dem CS-Dateinamen übereinstimmt (z. B. sucht ssh.vbs nach ssh.cs). Dies erleichtert das Ausführen des Skripts um ein Vielfaches. Wenn Sie jedoch kein Idiot wie ich sind, kann ein generisches Skript die CS-Zieldatei per Drag & Drop abrufen:

 Dim name_, oShell, fso 
 Set oShell = CreateObject ("Shell.Application") 
 Set fso = CreateObject ("Scripting.fileSystemObject") 
 
 'NIMM DEN VBS-SCRIPT-NAMEN ALS ZIELDATEINAMEN 
' ############################# ################## 
 name_ = Split (wscript.ScriptName, ".") (0) 
 
 'GET EXTERNE DLLS UND ICON-NAMEN AUS DER CS-DATEI 
 '############################### ##################### 
 Const OPEN_FILE_FOR_READING = 1 
 Set objInputFile = fso.OpenTextFile (name_ & ".cs", 1) 
 
 'ALLES IN EINEM ARRAY LESEN 
' ########################## ### 
 inputData = Split (objInputFile.ReadAll, vbNewline) 
 
 Für jede StrData In inputData 
 
, wenn übrig (StrData, 7) = "// + ref>" dann 
 csc_references = csc_references & "/ reference:" & trim (replace (strData, "// + ref>", "") & "" 
 ende wenn 
 
 Wenn links (strData, 7) = "// + res>", dann 
 csc_resources = csc_resources & "/ resource:" & trim (replace (strData, "// + res>", "")) & "" 
 end if 
 
 if left (strData, 7) = "// + ico>" then 
 csc_icon = "/ win32icon : "& trim (replace (strData," // + ico> "," ") &" "
 end if 
 Next 
 
 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) 
2
Mark Llewellyn

Im Allgemeinen benötigen Sie eine Art Nachbearbeitungswerkzeug, um eine Baugruppenzusammenführung durchzuführen, wie Sie sie beschreiben. Es gibt ein kostenloses Tool namens Eazfuscator (eazfuscator.blogspot.com/), das für die Bytecode-Mangelung entwickelt wurde und auch das Zusammenführen von Assemblys übernimmt. Sie können dies in eine Befehlszeile mit Visual Studio nach der Erstellung einfügen, um Ihre Assemblys zusammenzuführen. Die Laufleistung kann jedoch aufgrund von Problemen variieren, die in nicht-trivalenten Assembly-Zusammenführungsszenarien auftreten.

Sie können auch überprüfen, ob das Build Make Untility NANT Assemblys nach dem Build zusammenführen kann, aber ich bin nicht mit NANT vertraut, um zu sagen, ob die Funktionalität eingebaut ist oder nicht.

Es gibt auch viele Visual Studio-Plug-ins, die beim Erstellen der Anwendung das Zusammenführen von Assemblys ausführen.

Wenn dies nicht automatisch erfolgen soll, gibt es eine Reihe von Tools wie ILMerge, mit denen .net-Assemblys in einer einzigen Datei zusammengeführt werden.

Das größte Problem beim Zusammenführen von Assemblys ist die Verwendung ähnlicher Namespaces. Oder schlimmer noch, verweisen Sie auf verschiedene Versionen derselben DLL (meine Probleme betrafen im Allgemeinen die NUnit-DLL-Dateien).

0
wllmsaccnt

Es ist möglich, aber nicht so einfach, eine native/verwaltete Hybrid-Assembly in C # zu erstellen. Wenn Sie stattdessen C++ verwenden, ist dies viel einfacher, da der Visual C++ - Compiler ebenso einfach wie alles andere Hybridassemblys erstellen kann.

Sofern Sie nicht die strikte Anforderung haben, eine Hybrid-Assembly zu erstellen, stimme ich MusiGenesis zu, dass sich die Mühe mit C # nicht wirklich lohnt. Wenn Sie dies tun müssen, schauen Sie sich stattdessen vielleicht die Umstellung auf C++/CLI an.

0