Salut à tous, cette nuit, pour les besoins d’un programme, j’ai voulu embarquer mes DLL dans mon application. Les puristes me diront : « Quoi ? tu veux utiliser une DLL comme une librarie statique ??? » Et bien… oui et non… Les 2 DLL utilisées ne le sont que pour le programme en question. Je voulais donner la possibilité d’utiliser le programme comme un single file executable et non une archive à décompresser etc… Le problème a été le WPF. En effet, si des outils comme ILMerge fonctionnent très bien sur des applis consoles ou winforms, les assembly de WPF n’ont pas l’air d’apprécier ce merging complet. Voici donc un solution que j’ai trouvé et que je vous partage.
Alors la solution n’est pas de moi. Mais je l’ai trouvé tellement mal expliqué, pour quelqu’un qui ne fait pas/plus ( 😉 ) joujou avec la CLR et l’interop, que je me sens obligé de détailler un peu le bidule pour le commun des dev’s.
Dans notre exemple nous aurons une application console dans laquelle on appelle la DLL XYZ. L’assembly de notre projet est MyLoader et la DLL se trouve dans un dossier nommé DLL.
Voici a quoi cela ressemble au début :
J’ai donc une référence vers ma DLL tout ce qui a de plus classique (référence de DLL, pas du projet de la DLL !)
Le problème est que en output de ma compilation j’obtiens deux fichier, MyLoader.exe et XYZ.DLL.
Pour régler cela nous allons d’abord rajouter un dossier DLL au projet et y mettre notre DLL comme une ressource incorporée. Pour cela il faut se rendre sur la propriété du fichier et sélectionner… Ressource incorporé ^^
Ensuite nous allons passer l’attribut « Copie Locale » de notre référence XYZ à False, ainsi aucune copie ne sera effectué de notre DLL dans le répertoire de l’exécutable.
Maintenant que notre DLL va être incorporée au ressources de notre programme et que l’on ne généré plus une copie de cette référence, il faut charger la DLL incorporée. Et c’est la que ça commence a se « compliquer ».
Alors, premièrement rajouter un using sur System.Reflection. Ensuite, que se passe-t-il ?
Lorsque l’on fait appel à une fonction dans une DLL, la CLR va chercher à la trouver puis la charger, quand cette résolution échoue (ce qui sera le cas ici) une dernière chance nous est donné avec ce handler.
Chaque fois qu’une DLL introuvable devra être chargée, le programme exécutera le handler.
On définit tout d’abord un ressourcename. Il s’agit du nom complet d’accès a votre DLL incluant le namespace du projet.
Ici c’est MyLoader + DLL (dossier ou est le fichier). Ensuite on récupère le nom de la DLL a charger avec AssemblyName(args.Name).Name et enfin on y rajoute le suffixe .dll.
Quand il cherchera l’assembly XYZ, cela donnera : “MyLoader.DLL.XYZ.dll”
Ensuite on se charge de récupérer un flux que l’on lit et que l’on copie dans le tableau de byte AssemblyData. Ce flux correspond a notre DLL, on a donc la le contenu. Enfin on se charge de la retourner chargée en faisait le Load().
Au final notre handler aura été recherché notre DLL, lu en binaire le fichier puis chargé en mémoire et retourné a l’appelant l’assembly qui pourra être utilisé par la suite.
Notre output de compilation ne comportera que notre exe avec la dll embarqué dedans a la manière de librairie statiques.
Inform@tiquement
Istace Emmanuel
Sources :
http://blogs.msdn.com/b/microsoft_press/archive/2010/02/03/jeffrey-richter-excerpt-2-from-clr-via-c-third-edition.aspx
CLR 3 via C# (Microsoft Press, 2010; ISBN: 9780735627048), Chap. 23 – “Assembly Loading and Reflection,”
Bonjour, tout d’abord merci pour les explications c’est très util. Par contre je rencontre un probleme. Je ne peux pas créer de fonction main car mon application a plusieurs points d’entrée, du coup ou pourrais je appeler ma fonction pour éviter de me prendre l’exception levée ? j’ai essayé de placer le code dans le constructeur de l’application WPF mais rien y fait, j’avoue être à court d’idée.
Après quelques corrections puis recherche je suis arrivé à avoir ce code la :
public App()
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(ResolveAssembly);
}
static Assembly ResolveAssembly(object sender, ResolveEventArgs args)
{
String resourceName = “Flux.lib.” + 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);
return Assembly.Load(assemblyData);
}
}
Flux étant le projet et les 2 dll que j’utilise se trouvant dans le dossier lib de la même façon que décrite dans votre article, ceux-ci sont configuré comme dit également (copie locale à false et action de génération à Ressource incorporée”) Seulement lors du lancement de l’application j’ai le droit à une jolie exception
“Une exception non gérée du type ‘System.Windows.Markup.XamlParseException’ s’est produite dans PresentationFramework.dll
Informations supplémentaires : ‘La définition de connectionId a levé une exception.’ numéro de ligne ‘167’ et position de ligne ’18’.”
Je ne pense pas m’être trompé quelque part et je suis réellement dans une impasse, j’ai regardé sur plusieurs site et je ne trouve pas vraiment comment régler ce soucis, le projet fonctionne quand les dll ont une copie locale mais pas quand il n’y en a pas (logique me direz vous” mais cela veut donc dire qu’il doit y avoir une erreur quelque part dans mes lignes et je n’arrive vraiment pas à trouver cette erreur.
En espérant une réponse, merci ^^
Il me faudrait plus de détails…
Si c’est toujours d’actu, avez vous une stacktrace ou mieux un dump avec les symboles de l’appli ?
Cordialement
Emmanuel Istace
Essayez l’override du startup de app.xaml.cs, dans 90% des cas en WPF c’est ok.
Sinon, quels sont ces multiples points d’entrées ?