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 csharp : Sélectionner tout - Visualiser dans une fenêtre à part
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 :
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.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.
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.
Partager