AppDomain.Load & ShadowCopy
Bonjour à tous,
Avant tout, je parle ci-après des AddIns Visual Studio MAIS je pense que mon problème est générique et n'a pas grand chose à voir avec le fait qu'il s'agit d'un AddIn. D'ailleur je poste ici car c'est en C# (et aussi parce que j'arrive pas à poster dans le .Net général) mais mon problème n'est pas lié au langage.
Je suis bloqué sur un problème technique assez commun :
J'ai créé un AddIn pour Visual Studio pour ma boite, le truc c'est qu'on travail tous sur la même machine via TSE (ou Citrix, au choix).
En conséquence mon AddIn est toujours chargé et donc impossible de le remplacer simplement sans demander a tout le monde de s'arrêter, sortir de tous leurs Visual Studio, attendre que tout le monde l'ai fait, déployer la mise à jour et refaire le tour de la boite pour indiquer a tout le monde qu'il peuvent à nouveau bosser.
En faisant quelques recherches pour solutionner le problème je suis tombé sur les ShadowCopy. Le principe est assez simple, on créer un AppDomain en spécifiant qu'il fait de la ShadowCopy, ensuite quand un assembly est chargé dans ce domaine, le fichier est d'abord copier dans un répertoire temporaire puis chargé de la-bas. Par conséquent la copie originale n'est pas verrouillée et peux être mise à jour. La mise à jour sera effective pour les utilisateur au prochain démarrage de l'AddIn.
Voilà, sur le papier c'est exactement ce qu'il me faut. Sauf que ...
Quand on lance une appli .Net (y compris les AddIn) l'AppDomain existe déjà et apparement il ne peut pas être reconfiguré. En conséquence j'ai splité en AddIn en deux assembly. Le premier est celui chargé par Visual Studio, son unique rôle est de créer l'AppDomain qui sera en ShadowCopy et d'y transmettre les appels faits par Visual Studio.
La création du nouvel AddDomain fonctionne. Par contre je n'arrive pas à y charger l'assembly.
Pour être plus clair, voici le code complet du premier assembly (qui ne contient qu'une classe donc) :
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
|
#region Références
using System;
using Extensibility;
using System.Reflection;
using System.Runtime.Serialization;
using System.IO;
#endregion
/// <summary>Objet permettant d'implémenter un complément.</summary>
/// <seealso class='IDTExtensibility2' />
public partial class ADT
: IDTExtensibility2
{
#region Attribtus
private AppDomain _RealDomain;
private Assembly _ShadowAssembly;
private IDTExtensibility2 _ADTVS;
#endregion
#region Propriétés
#endregion
#region Constructeur
/// <summary>
/// Implémente le constructeur de l'objet Add-in. Placez votre code
/// d'initialisation dans cette méthode.
/// </summary>
public ADT()
{
try
{
// Création d'un nouveau domaine pour y charger ADT.VS en shadow copy.
AppDomainSetup setup = new AppDomainSetup();
string CurrentLocation = Path.GetDirectoryName(typeof(ADT).Assembly.Location);
setup.ApplicationBase = CurrentLocation;
setup.ShadowCopyFiles = "true";
_RealDomain = AppDomain.CreateDomain("ADTShadowCopyDomain", null, setup);
// Charge les assemblys
LoadAssemblyInDomain(_RealDomain, Path.Combine(CurrentLocation, "nop.net.vshelper.dll"));
_ShadowAssembly = LoadAssemblyInDomain(_RealDomain, Path.Combine(CurrentLocation, "ADT.VS.dll"));
// Parcourt les types de l'assembly pour en obtenir un qui implémente IDTExtensibility2
Type[] exposedTypes = _ShadowAssembly.GetExportedTypes();
Type SearchForType = typeof(IDTExtensibility2);
foreach (Type t in exposedTypes)
{
if (SearchForType.IsAssignableFrom(t))
{
_ADTVS = (IDTExtensibility2)_ShadowAssembly.CreateInstance(t.FullName);
break;
}
}
}
catch (Exception ex)
{
}
}
#endregion
#region Méthodes
/// <summary>
/// Obtient le contenu d'un fichier sous forme d'un tableau d'octets.
/// </summary>
/// <param name="filename">Nom du fichier.</param>
/// <returns>Tableau d'octet contenant le fichier ou null si la méthode à échoué.</returns>
private static byte[] GetFileContent(string filename)
{
if (File.Exists(filename) == false) return null;
byte[] buffer = null;
using (FileStream fs = new FileStream(filename, FileMode.Open))
{
buffer = new byte[fs.Length];
fs.Read(buffer, 0, buffer.Length);
fs.Close();
}
return buffer;
}
private static Assembly LoadAssemblyInDomain(AppDomain appd, string filename)
{
string pbdfile = Path.Combine(Path.GetDirectoryName(filename), String.Format("{0}.pdb", Path.GetFileNameWithoutExtension(filename)));
byte[] rawAsm = GetFileContent(filename);
byte[] rawPdb = GetFileContent(pbdfile);
return appd.Load(rawAsm, rawPdb);
}
#endregion
#region Implémentation de l'interface IDTExtensibility2
/// <summary>
/// Implémente la méthode OnConnection de l'interface IDTExtensibility2.
/// Reçoit une notification indiquant que le complément est en cours de
/// chargement.
/// </summary>
/// <param term='application'>
/// Objet racine de l'application hôte.
/// </param>
/// <param term='connectMode'>
/// Décrit la manière dont le complément est
/// chargé.
/// </param>
/// <param term='addInInst'>Objet représentant ce complément.</param>
/// <seealso class='IDTExtensibility2' />
public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
{
_ADTVS.OnConnection(application, connectMode, addInInst, ref custom);
}
/// <summary>
/// Implémente la méthode OnDisconnection de l'interface IDTExtensibility2.
/// Reçoit une notification indiquant que le complément est en cours de
/// déchargement.
/// </summary>
/// <param term='disconnectMode'>
/// Décrit la manière dont le complément est déchargé.
/// </param>
/// <param term='custom'>
/// Tableau des paramètres propres à l'application hôte.
/// </param>
/// <seealso class='IDTExtensibility2' />
public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)
{
_ADTVS.OnDisconnection(disconnectMode, ref custom);
}
/// <summary>
/// Implémente la méthode OnAddInsUpdate de l'interface IDTExtensibility2.
/// Reçoit une notification lorsque la collection de compléments a été
/// modifiée.
/// </summary>
/// <param term='custom'>
/// Tableau des paramètres propres à l'application hôte.
/// </param>
/// <seealso class='IDTExtensibility2' />
public void OnAddInsUpdate(ref Array custom)
{
_ADTVS.OnAddInsUpdate(ref custom);
}
/// <summary>
/// Implémente la méthode OnStartupComplete de l'interface
/// IDTExtensibility2. Reçoit une notification indiquant que le chargement
/// de l'application hôte est terminé.</summary>
/// <param term='custom'>
/// Tableau des paramètres propres à l'application hôte.
/// </param>
/// <seealso class='IDTExtensibility2' />
public void OnStartupComplete(ref Array custom)
{
_ADTVS.OnStartupComplete(ref custom);
}
/// <summary>
/// Implémente la méthode OnBeginShutdown de l'interface IDTExtensibility2.
/// Reçoit une notification indiquant que l'application hôte est en cours
/// de déchargement.
/// </summary>
/// <param term='custom'>
/// Tableau des paramètres propres à l'application hôte.
/// </param>
/// <seealso class='IDTExtensibility2' />
public void OnBeginShutdown(ref Array custom)
{
_ADTVS.OnBeginShutdown(ref custom);
}
#endregion
} |
Comme vous pouvez le voir je tente de charger deux assembly dans l'AppDomain créé. La première est une dépendance de la seconde. Je ne suis pas sur que je soit tenu de la charger à la main.
Bref, sur AppDomain.Load() , j'ai l'exception suivante :
Citation:
System.IO.FileNotFoundException: Impossible de charger le fichier ou l'assembly 'nop.net.vshelper, Version=1.0.3877.19775, Culture=neutral, PublicKeyToken=null' ou une de ses dépendances. Le fichier spécifié est introuvable.
Nom du fichier : 'nop.net.vshelper, Version=1.0.3877.19775, Culture=neutral, PublicKeyToken=null'
à System.Reflection.Assembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection)
à System.Reflection.Assembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, Assembly locationHint, StackCrawlMark& stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection)
à System.Reflection.Assembly.InternalLoad(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection)
à System.Reflection.Assembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark& stackMark, Boolean forIntrospection)
à System.Reflection.Assembly.Load(String assemblyString)
à System.UnitySerializationHolder.GetRealObject(StreamingContext context)
à System.AppDomain.Load(Byte[] rawAssembly, Byte[] rawSymbolStore)
à ADT.LoadAssemblyInDomain(AppDomain appd, String filename) dans E:\[chemin masqué]\Utilitaires\ADT\ADT\ADT.cs:ligne 86
à ADT..ctor() dans E:\[chemin masqué]\Utilitaires\ADT\ADT\ADT.cs:ligne 41
=== Informations d'état de liaison préalable ===
JRN : utilisateur = [donnée masquée]
JRN : DisplayName = nop.net.vshelper, Version=1.0.3877.19775, Culture=neutral, PublicKeyToken=null
(Fully-specified)
JRN : Appbase = file:///C:/Program Files (x86)/Microsoft Visual Studio 9.0/Common7/IDE/
JRN : PrivatePath initial = NULL
Assembly appelant : (Unknown).
===
JRN : cette liaison démarre dans le contexte de chargement de default.
JRN : utilisation du fichier de configuration de l'application : C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\devenv.exe.Config
JRN : utilisation du fichier de configuration de l'ordinateur à partir de C:\Windows\Microsoft.NET\Framework\v2.0.50727\config\machine.config.
JRN : stratégie non appliquée à la référence à ce stade (liaison d'assembly privée, personnalisée, partielle ou basée sur l'emplacement).
JRN : la même liaison a été vue avant et a échoué avec hr = 0x80070002.
J'ai tenté plusieurs méthodes de chargement mais rien y fait. Ma solution serait a priori de poser un handler sur _RealDomain.AssemblyResolve mais pour ca il faut que je rende ma classe sérializable et je n'ai pas trop d'idée de comment. Enfin si je sais, je doit implémenter ISerializable et poser l'attribut Serializable sur la classe, sauf que je n'ai pas d'idée de ce qu'il faut mettre dans la méthode ISerializable.GetObjectData.
Bref, si vous avez une idées de où ca peut venir, comment corriger, ou même si vous connaissez une autre manière de répondre à ma problématique initiale, je suis tout ouïe.