Recentemente ho dovuto creare un’applicazione WPF che fosse facilmente distribuibile (leggi: distribuire solo l’exe).
Purtroppo il tool ILMerge non funziona per applicazioni WPF, a causa di problemi con le risorse contenute in esse (esistono comunque tool funzionanti di terze parti, a pagamento).
Seguendo questo post, ho creato un esempio che qui illustro e che potete scaricare qui.
L’applicazione visualizza semplicemente il fullname di due classi presenti in 2 assembly referenziati:
Per prima cosa è necessario modificare il file di progetto dell’applicazione WPF aggiungendo, dopo “Microsoft.CSharp.targets” , il seguente snippet:
- <Target Name="AfterResolveReferences">
- <ItemGroup>
- <EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
- <LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
- </EmbeddedResource>
- </ItemGroup>
- </Target>
Semplicemente, andiamo ad indicare di inserire tutti i file referenziati con estensione “.dll” come “emdedded resource” nell’exe principale. In questo modo eviteremo di eseguire a mano l’inclusione dell’ultima versione delle librerie compilate.
Nelle proprietà del progetto, impostiamo il seguente comando da eseguire durante la fase di post-build: “del $(TargetDir)*.dll” per cancellare tutte le librerie presenti nella “bin”, che non ci serviranno più...
Impostiamo quindi come oggetto di avvio la classe Bootstrapper (potete ovviamente cambiare il nome):
Questa, infine, è come definita la classe BootStrapper:
- public class BootStrapper
- {
- [STAThread]
- public static void Main(string[] args)
- {
- AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
- App.Main();
- }
-
- private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
- {
- var executingAssembly = Assembly.GetExecutingAssembly();
- var assemblyName = new AssemblyName(args.Name);
-
- string path = string.Format("{0}.dll", assemblyName.Name);
- if (assemblyName.CultureInfo.Equals(CultureInfo.InvariantCulture) == false)
- {
- path = string.Format(@"{0}\\cf4 {1}", assemblyName.CultureInfo, path);
- }
-
- using (var 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);
- }
- }
- }
Sostanzialmente ci agganciamo all’evento AssemblyResolve dell’AppDomain corrente, che viene sollevato ogni volta che la risoluzione di un’assembly fallisce, e ritorniamo l’assembly che abbia in canna nelle embedded resources.
Ecco infatti come si presenta il nostro exe “aprendolo” con ILSpy:
E’ anche più semplice di usare la riga di comando di ILMerge !