[.net] resgen, ResXResourceReader, References

Allez, après 1/2 journée d’épluchage du net, je vous soumet mon problème.

J’utilise NAnt depuis quelques temps déjà pour gérer les build de mes projets. Jusqu’à présent, tout se passait relativement bien avec csc et autres confrères… Jusqu’à ce que je me penche sur la génération d’une de nos applis winforms.

La différence avec les applis classiques, c’est la transformation des fichiers .resx en .resources :

  • NAnt appelle pour cela directement resgen (l’appli du SDK chargée de transformer les .resx en .resources).
  • resgen se charge d’analyser le ficher .resx au format xml à l’aide de ResXResourceReader, qui se fait un plaisir de Deserializer les données.

Problème : je prends une erreur de Deserialization, comme quoi une certaine librairie externe utilisée dans un de nos formulaire ne pouvait être atteinte pour permettre la déserialisation… J’en déduis qu’un des composants utilisé serialise une de ses classes dans le fichier .resx.

[quote]error: Entrée ResX non valide.
error: Specific exception: SerializationException  Message: Entrée ResX non valide. —> SerializationException: Impossible de trouver l’assembly TrucMuche…[/quote]

Resgen est une appli c# assez « classique », dont le source est trouvable à grand coup de reflector ou de google, et ne permet pas d’indiquer un éventuel répertoire où se trouvent les librairies susceptibles d’être utilisées dans ce fichier. Il manque en fait la notion de « référence » au même titre que les références pour le compilo csc.

Pour contourner le problème, il faudrait donc que la librairie utilisée dans le .resx se trouve dans le répertoire de resgen (ou, plus simple, dans le GAC), pour que celui-ci, fonctionnant comme une appli .net classique, trouve tout seul la librairie.

Par contre, pour le coup, je n’arrive pas :

  1. A piger comment fonctionne VS.NET 2003, qui, bien sûr, génère les .resources comme une fleur, en se basant sur les librairies référencées par le projet.

  2. A comprendre quelle solution appliquer (j’ai trouvé un sujet sur la ML nant-users, mais je cherche « mieux ») : est-ce le composant qui est mal foutu ? Est-ce que je devrais vraiment mettre dans le GAC de mon serveur de build mon composant ? copier resgen un peu partout ? copier mon composant un peu partout ?

  3. A comprendre en quoi la version 2 de .net résout ce problème (il y a bien une propriété BasePath qui est ajoutée, mais elle permet quoi ? une recherche tardive de librairies pour le chargement de types ?)

Merci à tous ceux qui auront eu le courage de lire :stuck_out_tongue:

Ha tiens, pour une fois :stuck_out_tongue:
C’est une des zone dont je suis officiellement le « owner » et dont j’ai code le nouveau code pour .Net 2.0.

Le TypeResolutionService, un des trucs les plus complexe de .Net et de VS2005, le truc re-ecrit 20 fois pour etre plus performant et plus malin :stuck_out_tongue: tu touches a une zone sensible hehe. Mais je vais essayer de repondre a tes questions.

Dans VS2003 resgen tourne inprocess dans le meme app domain que VS et il partage donc un service qui s’appelle le ITypeResolutionService qui permet de trouver les types et les assemblies. C’est cet objet qui est utilise/passe a un resgen inprocess et qui sait trouver les librairies dans les references du projet et autre. Si tu mattes ResXResourceReader il prend un ITypeResolutionService en param optionel a priori meme sur 2003 (sur 2005 sur et certain).

J’avoue que pour 2003 je suis pas super a jour de la meilleure maniere de proceder. Je pense que copier la/les dll dans le rep ou tu compile tes resx est encore la solution la moins couteuse comme ils en parlent sur la mailing list de nant. Sinon une autre solution pour .Net 1.1 serait de faire comme fait VS2003, c’est a dire utiliser ResXResourceReader en passant un ITypeResolutionService custom. C’est pas la mort et une version toute simple qui prend une liste de chemin vers des assemblies c’est facile a ecrire. Nous on se base sur un tableau d’AssemblyName dans .net 2.0. Si tu veux te lancer dans ca et que tu as des problemes fais signe. (au passage content de voir que mon taf ils apprecient sur la mailing list de nant :stuck_out_tongue: a chaque fois c’est « dans .net 2.0 ca marche », niark!).

Non BasePath c’est utilise par les resxfileref qui sont des ressources qui sont prises au dernier moment.
Dans la version 2 tu as un flag qui s’appelle /r qui te permet de passer des references et resgen en interne va construire son propre ITypeResolutionService base sur ces assembly names, et utiliser cela pour le ResXResourceReader, qui sera a son tour utilise par les ResXDataNode (qui sont aussi nouveau en 2.0). Donc par exemple resgen /r:…\MyControlLibrary1\bin\Debug\MyControlLibrary1.dll machin.resx ca va marcher nikel.

C’est d’un compliqué…
J’ai rien compris
Je suis définitivement pas fait pour autre chose qu’un peu de perl de temps en temps :stuck_out_tongue:
Par contre c’est Styx31 qui foit être heureux là :stuck_out_tongue:

[quote name=‹ ColdFire › date=’ 16 Mar 2005, 00:01’]C’est d’un bordel…
J’ai rien compris
Je suis définitivement pas fait pour autre chose qu’un peu de perl de temps en temps :stuck_out_tongue:
[right][post=« 341475 »]<{POST_SNAPBACK}>[/post][/right][/quote]
Mheu non c’est pas bien complique… y a juste un vocabulaire et des notions a choper pour comprendre les trucs meme si ils sont pas compliques. C’est comme pour tout :stuck_out_tongue:

Pour pouvoir enfin permettre de créer les build winforms, je vais commencer par modifier le script nant pour qu’il recopie les lib dans le répertoire d’exécution de resgen temporairement.

Ensuite je commencerai à regarder le constructeur de ResXResourceReader et la construction d’un ITypeResolutionService custom comme tu dis, en me basant sur l’exemple de resgen… M’enfin déjà faut que je vois comment je vais pouvoir inclure ce resgen custom dans NAnt si je décide de le faire.

Je bippe si j’ai le moindre souci.

Merci pour toutes ces infos en tout cas, j’y vois déjà plus clair (et j’ai de suffisament de pistes pour continuer).

Hop, je me suis attellé à la création de CustomTypeResolutionService, mais j’ai quelques problèmes pour comprendre comment je dois implémenter certaines méthodes.

Ce que j’ai - ou ce que je pense avoir - compris :

  • GetAssembly() est la fonction qui permet d’obtenir l’assembly pour un nom d’assembly.

  • GetPathOfAssembly() c’est plus annexe, c’est pour obenir le chemin du fichier. Je pense pas que ResXResourceReader en ai besoin (et reflector me confirme que non)

  • GetType() donc c’est celle là qu’il faut que j’implémente correctement il semblerait : à partir d’un nom de type, il faut retourner le type en question en me parcourant la liste des assemblies que j’ai chargé, puis en parcourant chaque type présent dans chaque assembly. Mais un nom de type ça ressemble à quoi ? Vu la doc, il semblerait que ce soit le nom du type + (optionel) le nom de l’assembly… J’ai commencé à chercher une classe qui implémenterait cette interface pour me guider mon dieu je suis en train de passer reflector sur les librairies de VS, mes neurones sont en train de fondre, sans trop de succès (je suis sur ShellTypeLoader.GetType() là).

  • ReferenceAssembly(). C’est pour dire : « cette assembly tu la charges, comme ça si je te demande un type sous sa forme nom court, tu me le remontes direct. ». Donc ma méthode GetType() ne doit chercher les types court (sans assembly précisée) que dans les librairies chargées par cette méthode.

  • AddAssembly(). Ca c’est un méthode hors de cette interface qui devrait me permettre d’ajouter des assemblies à partir d’un fichier, c’est cette méthode qui serait appellée pour chaque paramètre /r: de mon resgen custom. Elle est nécessaire, sinon ma librairie ne serait capable que de charger des types du GAC ou du répertoire courant de resgen.

:stuck_out_tongue: cette suée :stuck_out_tongue:

Bon, je pense que je vais rester sur ma bidouille nant qui consiste à copier resgen dans un répertoire temporaire où j’envoie aussi les librairies du projet. La personnalisation de resgen, c’est un peu osé étant donné que je n’arriverai surement pas à faire que nant utilise cette version correctement (çàd en utilisant le paramètre /r:)

Je te repond demain du bureau mais Reflector utilise comme ca c’est EVIL :stuck_out_tongue: Au lieu de se poser et de reflechir a ce qu’il faut faire et a la meilleure maniere de le faire, voila ce qui se passe… on cherche « l’exemple », le truc qui ressemble et au final on a passe autant de temps a rien faire que ce que ca aurait pris a vraiment prendre du recul et chercher a comprendre ce dont le machin a besoin et comme il fonctionne en lisant la doc sur les sujets qu’il faut :stuck_out_tongue:

Bon si j’ai le temps je te ferais un sequelette sans caching et sans rien de type resolution service qui prend une liste de fichier. Ca devrait pas etre trop complique.

PS: Rhooo il abandonne trop vite. Bhou. Y a pas de raison que t’arrive pas a faire une tache nant custom qui sache prendre une liste de fichier pour faire son type resolution.

Roh, mais je lis tjs la doc d’abord, mais avec une ou deux phrases seulement, on a quand même pas mal de questions qui restent en suspend… Bref, de toute façon j’ai vite raccroché quand j’ai vu que la (les ?) classe(s) implémentant cette interface rajoutaient 150000 autres choses qui dépassaient, et de loin, le simple cadre de ma petite recherche. Et j’ai surement trop répondu à chaud :stuck_out_tongue:

D’ici quelques heures j’aurais peut-être épluché d’autres voies et trouvé les réponses tout seul, je souhaitais juste indiquer que j’avançais encore un peu à droite à gauche. En fait il faudrait que je lise de la doc plus globale sur la gestion des chargement d’assemblies de manière plus dynamique je pense… A creuser là encore :stuck_out_tongue:

Merci pour ton aide, et de toute façon je laisse tomber « officiellement », mais officieusement, je compte bien réussir à charger un type et au moins faire marcher un resgen avec cette saleté de .resx B)

++

Bon alors sachant qu’avec AssemblyName.GetAssemblyName(filePath) tu peux choper l’objet AssemblyName pour une assembly tu peux faire un ITypeResolutionService qui prend un array d’assembly name. Genre de pseudo memoire et rapidement (donc surement que ca compile pas):

[code]class AssemblyNamesTypeResolutionService : ITypeResolutionService {
private AssemblyName[] names;

	AssemblyNamesTypeResolutionService(AssemblyName[] names) {
		this.names = names;
	}

	public Assembly GetAssembly(AssemblyName name, bool throwOnError) {
		Assembly result = null;
		result = Assembly.Load(name.FullName); // on essaie depuis le GAC ou la path ou whatever d'abord, si on trouve pas on matte dans notre liste a nous qu'on l'a. Si vraiment tu veux tu peux faire LoadWithPartialName mais c'est mal.
		if (result == null && names != null) { // on a pas trouve
			for(int i=0;i<names.Length; i++) {
				if(name.Equals(names[i])) { // oh c'est la bonne!
					try {
						result = Assembly.LoadFrom(GetPathOfAssembly(name));
					}
					catch {
						if(throwOnError) {
							throw;
						}
					}
				}   
			}
		}
		return result;
   }	
	
	public string GetPathOfAssembly(AssemblyName name) {
		return name.CodeBase;
	}

  
	public Type GetType(string name) {
		return GetType(name, true);
	}

	public Type GetType(string name, bool throwOnError) {
		return GetType(name, throwOnError, false);
	}

	public Type GetType(string name, bool throwOnError, bool ignoreCase) {
		Type result = null;

		// pareil, GAC d'abord
		if(name.IndexOf(',') != -1) {
			result = Type.GetType(name, false, ignoreCase);
		}

		// on se tape toute les assembly une par une
		if(result == null && names != null) {
			for(int i=0;i<names.Length; i++) {
				Assembly a = GetAssembly(names[i], throwOnError);
				result = a.GetType(name, false, ignoreCase); // d'abord le full name
				if(result == null) {
					int indexOfComma = name.IndexOf(",");
					if(indexOfComma != -1) {
						string shortName = name.Substring(0, indexOfComma );
						result = a.GetType(shortName, false, ignoreCase); // le nom partiel
					}
				}
				if(result != null)
					break;
			}
		}

		if(result == null && throwOnError) {
			throw new ArgumentException("bhouhou ton type je sais pas c'est quoi");
		}
		return result;
	}
}[/code]

Ou un truc comme ca. Bien sur, tu peux cacher les assembly dans GetAssembly dans une hashtablepour pas que ca soit la folie niveau des chargement et qu’une fois charge tu sois peinard. Si avec ca tu t’en sort pas je peux plus rien pour toi :stuck_out_tongue:

PS: Toute ressemblance avec le code de .Net 2.0 est purement fortuite du fait que c’est moi qui ait ecrit les deux :stuck_out_tongue:

Me revoilà après 1 journée passée au devdays (comme j’ai trop envie de prendre 6 mois de vacances et attendre que VS2005 arrive avant de continuer mon projet).

Je regarde tout ça dès que j’ai 5 minutes et je te dis ce que j’ai pu en faire :stuck_out_tongue:

Merci :stuck_out_tongue: