Incorporare le DLL in un eseguibile compilato

Sai, non ho mai visto una buona risposta per questo. È ansible incorporare una DLL preesistente in un eseguibile compilato in C # (in modo da avere un solo file da distribuire)? Se è ansible, come si fa a farlo?

Normalmente, mi piace lasciare le DLL all’esterno e far sì che il programma di installazione gestisca tutto, ma ci sono state un paio di persone al lavoro che mi hanno chiesto questo e sinceramente non lo so.

Consiglio vivamente di utilizzare Costura.Fody – il modo migliore e più semplice per incorporare le risorse nell’assieme. È disponibile come pacchetto NuGet.

Install-Package Costura.Fody 

Dopo averlo aggiunto al progetto, esso incorpora automaticamente tutti i riferimenti copiati nella directory di output nell’assembly principale . Potresti voler pulire i file incorporati aggiungendo una destinazione al tuo progetto:

 Install-CleanReferencesTarget 

Sarai anche in grado di specificare se includere i pdb, escludere determinati assiemi o estrarre gli assiemi al volo. Per quanto ne so, sono supportati anche gli assembly non gestiti.

Aggiornare

Attualmente, alcune persone stanno cercando di aggiungere il supporto per DNX .

Se sono effettivamente gestiti gli assembly, è ansible utilizzare ILMerge . Per le DLL native, avrai un po ‘più di lavoro da fare.

Vedi anche: Come si può unire Windows C ++ in un exe di un’applicazione C #?

Basta fare clic con il pulsante destro del mouse sul progetto in Visual Studio, scegliere Proprietà progetto -> Risorse -> Aggiungi risorsa -> Aggiungi file esistente … e includere il codice seguente in App.xaml.cs o equivalente.

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

Ecco il mio post sul blog originale: http://codeblog.larsholm.net/2011/06/embed-dlls-easily-in-a-net-assembly/

Sì, è ansible unire eseguibili .NET con le librerie. Ci sono più strumenti disponibili per portare a termine il lavoro:

  • ILMerge è un’utilità che può essere utilizzata per unire più assembly .NET in un singolo assembly.
  • Mono mkbundle , pacchetti un exe e tutti gli assembly con libmono in un unico pacchetto binario.
  • IL-Repack è un alterantivo di FLOSS per ILMerge, con alcune funzionalità aggiuntive.

Inoltre, questo può essere combinato con Mono Linker , che rimuove il codice inutilizzato e quindi riduce l’assemblaggio risultante.

Un’altra possibilità è quella di utilizzare .NETZ , che non solo consente la compressione di un assembly, ma può anche comprimere le DLL direttamente nell’exe. La differenza con le soluzioni sopra menzionate è che .NETZ non le unisce, rimangono assemblaggi separati ma sono racchiuse in un unico pacchetto.

.NETZ è uno strumento open source che comprime e impacchetta i file eseguibili (EXE, DLL) di Microsoft .NET Framework per renderli più piccoli.

ILMerge può combinare gli assiemi in un unico assieme purché l’assembly abbia solo codice gestito. È ansible utilizzare l’app della riga di comando o aggiungere un riferimento a exe e unire a livello di codice. Per una versione della GUI c’è Eazfuscator e anche .Netz, entrambi gratuiti. Le app a pagamento includono BoxedApp e SmartAssembly .

Se si devono unire assiemi con codice non gestito, suggerirei SmartAssembly . Non ho mai avuto singhiozzo con SmartAssembly ma con tutti gli altri. Qui, può incorporare le dipendenze richieste come risorse per il tuo exe principale.

Puoi fare tutto questo manualmente senza preoccuparti se l’assembly è gestito o in modalità mista incorporando dll alle tue risorse e poi affidandoti ResolveHandler ‘s Assembly ResolveHandler . Questa è una soluzione completa adottando il caso peggiore, ovvero gli assembly con codice non gestito.

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

La chiave qui è scrivere i byte in un file e caricare dalla sua posizione. Per evitare problemi con uova e uova, è necessario assicurarsi di dichiarare il gestore prima di accedere all’assemblaggio e di non accedere ai membri dell’assieme (o creare un’istanza di tutto ciò che deve occuparsi dell’assieme) all’interno della parte di caricamento (risoluzione di assemblaggio). Inoltre assicurati che GetMyApplicationSpecificPath() non sia una directory temporanea in quanto si potrebbe tentare di cancellare i file temporanei da altri programmi o da te stesso (non che verranno cancellati mentre il tuo programma sta accedendo alla dll, ma almeno è una seccatura. AppData è una buona posizione). Si noti inoltre che è necessario scrivere i byte ogni volta, non è ansible caricare dalla posizione semplicemente perché la dll risiede già lì.

Per le DLL gestite, non è necessario scrivere byte, ma caricare direttamente dal percorso della DLL, o semplicemente leggere i byte e caricare l’assembly dalla memoria. Ad esempio:

  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. 

Se l’assembly è completamente non gestito, puoi vedere questo link o questo su come caricare tali DLL.

L’ estratto di Jeffrey Richter è molto buono. In breve, aggiungi la libreria come risorse incorporate e aggiungi una richiamata prima di ogni altra cosa. Ecco una versione del codice (che si trova nei commenti della sua pagina) che ho inserito all’inizio del metodo Main per un’app console (assicurati che tutte le chiamate che utilizzano la libreria siano in un metodo diverso da 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); } }; 

Per espandere su @ Bobby’s asnwer sopra. Puoi modificare il tuo .csproj per usare IL-Repack per impacchettare automaticamente tutti i file in un unico assemblaggio quando costruisci.

  1. Installa il pacchetto ILRepack.MSBuild.Task di nuget con il pacchetto di Install-Package ILRepack.MSBuild.Task
  2. Modifica la sezione AfterBuild del tuo .csproj

Ecco un semplice esempio che unisce ExampleAssemblyToMerge.dll all’output del progetto.

         

Ti consiglio di controllare l’utilità .NETZ, che comprime anche l’assembly con uno schema a tua scelta:

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

È ansible aggiungere le DLL come risorse incorporate e quindi fare in modo che il programma venga decompresso nella directory dell’applicazione all’avvio (dopo aver controllato se sono già presenti).

I file di installazione sono così semplici da rendere, comunque, che non penso che ne varrebbe la pena.

EDIT: questa tecnica sarebbe facile con gli assembly .NET. Con DLL non- .NET sarebbe molto più lavoro (dovresti capire dove decomprimere i file e registrarli e così via).

Controlla il boxedapp

Può incorporare una DLL in qualsiasi app. Scritto anche in C #, ovviamente 🙂

Spero che sia d’aiuto.

Né l’approccio di ILMerge né quello di Lars Holm Jensen che gestisce l’evento AssemblyResolve funzioneranno per un host di plugin. Dire dynamicmente l’assembly H caricabile P e accedervi tramite l’interfaccia IP definita in un assieme separato. Per incorporare l’ IP in H, è necessario apportare alcune modifiche al codice di Lars:

 Dictionary loaded = new Dictionary(); 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; }; 

Il trucco per gestire ripetuti tentativi di risolvere lo stesso assembly e restituire quello esistente invece di creare una nuova istanza.

EDIT: per non rovinare la serializzazione di .NET, assicurati di restituire null per tutti gli assembly non incorporati nel tuo, in modo da rendere predefinito il comportamento standard. È ansible ottenere un elenco di queste librerie per:

 static HashSet IncludedAssemblies = new HashSet(); string[] resources = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceNames(); for(int i = 0; i < resources.Length; i++) { IncludedAssemblies.Add(resources[i]); } 

e restituisce null se l'assembly passato non appartiene a IncludedAssemblies .

Un altro prodotto che può gestire questo elegantemente è SmartAssembly, su SmartAssembly.com . Questo prodotto, oltre a unire tutte le dipendenze in una singola DLL, (facoltativamente) offusca il codice, rimuove i metadati aggiuntivi per ridurre le dimensioni del file risultante e può anche ottimizzare effettivamente l’IL per aumentare le prestazioni di runtime. C’è anche un qualche tipo di funzionalità globale di gestione / segnalazione delle eccezioni che aggiunge al tuo software (se lo desideri) che non ho avuto il tempo di capire, ma potrebbe essere utile. Credo che abbia anche un’API a riga di comando in modo che tu possa farne parte del processo di compilazione.

ILMerge fa esattamente quello che vuoi.

Oltre a ILMerge , se non vuoi preoccuparti delle opzioni della riga di comando, consiglio vivamente ILMerge-Gui . È un progetto open source, davvero buono!

Può sembrare semplicistico, ma WinRar offre la possibilità di comprimere un gruppo di file in un eseguibile autoestraente.
Ha molte opzioni configurabili: icona finale, estrai file nel percorso dato, file da eseguire dopo l’estrazione, logo / testi personalizzati per popup mostrati durante l’estrazione, nessuna finestra popup, testo del contratto di licenza, ecc.
Può essere utile in alcuni casi.

Io uso il compilatore csc.exe chiamato da uno script .vbs.

Nel tuo script xyz.cs, aggiungi le seguenti righe dopo le direttive (il mio esempio è per 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" 

I tag ref, res e ico verranno raccolti dallo script .vbs in basso per formare il comando csc.

Quindi aggiungere il chiamante del resolver dell’assieme nel Main:

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

… e aggiungi il risolutore da qualche parte nella class:

     assembly statico CurrentDomain_AssemblyResolve (object mittente, ResolveEventArgs args)
     {
         String resourceName = new AssemblyName (args.Name) .Name + ".dll";

         utilizzando (var stream = Assembly.GetExecutingAssembly (). GetManifestResourceStream (resourceName))
         {
             Byte [] assemblyData = new Byte [stream.Length];
             stream.Read (assemblyData, 0, assemblyData.Length);
             return Assembly.Load (assemblyData);
         }

     }

Io chiamo lo script vbs in modo che corrisponda al nome di file .cs (ad es. Ssh.vbs cerca ssh.cs); questo rende la gestione dello script molte volte molto più semplice, ma se non sei un idiota come me, uno script generico potrebbe raccogliere il file .cs di destinazione da un trascinamento della selezione:

     Dim name_, oShell, fso
     Imposta oShell = CreateObject ("Shell.Application")
     Imposta fso = CreateObject ("Scripting.fileSystemObject")

     'PRENDERE IL NOME DI SCRIPT VBS COME NOME DEL FILE DESTINATARIO
     '################################################
     name_ = Split (wscript.ScriptName, ".") (0)

     'OTTENERE I DATI DELLE DLL E DELLE ICONE ESTERNE DAL FILE .CS
     '################################################# ######
     Const OPEN_FILE_FOR_READING = 1
     Imposta objInputFile = fso.OpenTextFile (nome_ & ".cs", 1)

     'LEGGERE TUTTO IN UN'ARRAY
     '#############################
     inputData = Split (objInputFile.ReadAll, vbNewline)

     Per ogni strData In inputData

         se lasciato (strData, 7) = "// + ref>" allora 
             csc_references = csc_references & "/ reference:" & trim (sostituisci (strData, "// + ref>", "")) e ""
         finisci se

         se lasciato (strData, 7) = "// + res>" allora 
             csc_resources = csc_resources & "/ resource:" & trim (sostituisci (strData, "// + res>", "")) e ""
         finisci se

         se lasciato (strData, 7) = "// + ico>" allora 
             csc_icon = "/ win32icon:" & trim (sostituisci (strData, "// + ico>", "")) e ""
         finisci se
     Il prossimo

     objInputFile.Close


     'COMPILARE IL 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)

È ansible, ma non è così facile, creare un assembly ibrido nativo / gestito in C #. Se stavi usando C ++ invece sarebbe molto più semplice, dato che il compilatore di Visual C ++ può creare assembly ibridi con la stessa semplicità di qualsiasi altra cosa.

A meno che tu non abbia un requisito rigoroso per produrre un assemblaggio ibrido, sono d’accordo con MusiGenesis sul fatto che questo non valga veramente la pena di fare con C #. Se hai bisogno di farlo, forse guarda invece di passare a C ++ / CLI.

In generale, è necessario un qualche tipo di strumento di post-compilazione per eseguire un’unione di assemblaggio come si sta descrivendo. C’è uno strumento gratuito chiamato Eazfuscator (eazfuscator.blogspot.com/) che è stato progettato per il mangling bytecode che gestisce anche la fusione degli assiemi. È ansible aggiungere questo in una riga di comando post build con Visual Studio per unire i propri assembly, ma il percorso sarà diverso a causa di problemi che si presenteranno in qualsiasi scenario di unione non trival.

Si può anche verificare se la build rende inutile NANT ha la capacità di unire assemblaggi dopo la costruzione, ma non sono abbastanza familiare con NANT stesso per dire se la funzionalità è integrata o meno.

Ci sono anche molti plugin per Visual Studio che eseguiranno l’assemblaggio come parte della costruzione dell’applicazione.

In alternativa, se non hai bisogno che ciò avvenga automaticamente, ci sono un certo numero di strumenti come ILMerge che uniranno gli assembly .net in un singolo file.

Il problema più grande che ho avuto con la fusione di assembly è se utilizzano spazi dei nomi simili. O peggio, fare riferimento a diverse versioni della stessa DLL (i miei problemi riguardavano in genere i file NUnit dll).

Ho provato questa soluzione al progetto di codice che incorpora la DLL: http://www.codeproject.com/Articles/528178/Load-DLL-From-Embedded-Resource

E ha funzionato bene.