Seul l'assemblage multi-langue Windows Forms de déploiement (ILMerge et les assemblys satellites / localisation) - possible?

J'ai un simple Windows Forms (C#, .NET 2.0) de l'application, construit avec Visual Studio 2008.

Je voudrais en charge plusieurs langues pour l'INTERFACE, et à l'aide de la "Localisable", une propriété de la forme et de la culture spécifiques .fichiers resx, la localisation aspect fonctionne de manière transparente et facilement. Visual Studio compile automatiquement la culture spécifique de fichiers resx dans les assemblys satellites, donc, dans mon application compilée dossier il y a de la culture spécifique des sous-dossiers contenant ces assemblys satellites.

Je voudrais avoir l'application sera déployée (copié dans le lieu) comme un assemblée unique, et encore conserver la capacité de contenir plusieurs ensembles de ressources de culture-spécifique.

À l'aide de ILMerge (ou ILRepack), je peux fusionner les assemblys satellites dans l'exécutable principal de l'assemblée, mais la norme .NET ResourceManager de secours mécanismes de ne pas retrouver la culture de ressources spécifiques qui ont été compilées dans l'assemblage principal.

Fait intéressant, si je prends mon fusionné (exécutable) de montage et de placer des copies de celui-ci dans la culture spécifique des sous-dossiers, puis tout fonctionne! De même, je peux voir les principaux spécifique de culture et de ressources dans la fusion de montage lorsque j'utilise Réflecteur (ou ILSpy). Mais la copie de l'assemblage principal dans la culture spécifique des sous-dossiers de défaites le but de la fusion de toute façon - je vraiment besoin de n'être qu'un seul exemplaire de l'assemblée unique...

Je me demandais savoir si il existe un moyen de détourner ou de l'influence de la classe ResourceManager de secours mécanismes de regarder pour la culture-ressources spécifiques dans la même assemblée, plutôt que dans le GAC et de la culture-nommé sous-dossiers. Je vois le mécanisme de secours décrits dans les articles suivants, mais aucune idée de la façon dont il serait modifié: Équipe BCL Article de Blog sur ResourceManager.

Quelqu'un a une idée? Cela semble être relativement fréquentes question en ligne (par exemple, une autre question sur Stack Overflow: "ILMerge et ressource localisée assemblées"), mais je n'ai pas trouvé de réponse faisant autorité n'importe où.


Mise à JOUR 1: Solution de Base

Suivantes casperOne de la recommandation ci-dessous, j'ai finalement été en mesure de faire ce travail.

Je suis en train de mettre la solution de code ici dans la question parce que casperOne fourni la seule réponse, je ne veux pas ajouter mes propres.

J'ai été en mesure de l'obtenir pour fonctionner en tirant le courage de sortir du Cadre des ressources à trouver de secours mécanismes mis en œuvre dans le "InternalGetResourceSet" méthode et de faire de notre même assemblée de recherche de la première mécanisme utilisé. Si la ressource n'est pas trouvée dans l'actuelle assemblée, puis nous appelons la méthode de base pour lancer la recherche par défaut des mécanismes (merci à @Wouter de commentaire ci-dessous).

Pour ce faire, j'ai tiré le "ComponentResourceManager" de la classe, et emportait une seule méthode (et re-mise en œuvre d'un cadre privé (méthode):

class SingleAssemblyComponentResourceManager : 
System.ComponentModel.ComponentResourceManager
{
private Type _contextTypeInfo;
private CultureInfo _neutralResourcesCulture;
public SingleAssemblyComponentResourceManager(Type t)
: base(t)
{
_contextTypeInfo = t;
}
protected override ResourceSet InternalGetResourceSet(CultureInfo culture, 
bool createIfNotExists, bool tryParents)
{
ResourceSet rs = (ResourceSet)this.ResourceSets[culture];
if (rs == null)
{
Stream store = null;
string resourceFileName = null;
//lazy-load default language (without caring about duplicate assignment in race conditions, no harm done);
if (this._neutralResourcesCulture == null)
{
this._neutralResourcesCulture = 
GetNeutralResourcesLanguage(this.MainAssembly);
}
//if we're asking for the default language, then ask for the
//invariant (non-specific) resources.
if (_neutralResourcesCulture.Equals(culture))
culture = CultureInfo.InvariantCulture;
resourceFileName = GetResourceFileName(culture);
store = this.MainAssembly.GetManifestResourceStream(
this._contextTypeInfo, resourceFileName);
//If we found the appropriate resources in the local assembly
if (store != null)
{
rs = new ResourceSet(store);
//save for later.
AddResourceSet(this.ResourceSets, culture, ref rs);
}
else
{
rs = base.InternalGetResourceSet(culture, createIfNotExists, tryParents);
}
}
return rs;
}
//private method in framework, had to be re-specified here.
private static void AddResourceSet(Hashtable localResourceSets, 
CultureInfo culture, ref ResourceSet rs)
{
lock (localResourceSets)
{
ResourceSet objA = (ResourceSet)localResourceSets[culture];
if (objA != null)
{
if (!object.Equals(objA, rs))
{
rs.Dispose();
rs = objA;
}
}
else
{
localResourceSets.Add(culture, rs);
}
}
}
}

Pour utiliser cette classe, vous avez besoin de remplacer le Système.ComponentModel.ComponentResourceManager dans le "XXX.Le concepteur.cs" les fichiers créés par Visual Studio et vous aurez besoin de faire cela chaque fois que vous changez la forme de Visual Studio remplace le code automatiquement. (Le problème a été discuté dans "Personnaliser Windows Forms Designer à utiliser MyResourceManager", je n'ai pas trouvé une solution plus élégante - je utiliser fart.exe dans une pré-étape de génération automatique de la remplacer.)


Mise à JOUR 2: une Autre Considération Pratique - plus de 2 langues

À l'époque je l'ai signalé à la solution ci-dessus, en réalité, j'étais en soutenant deux langues, et ILMerge a fait un excellent travail de la fusion de mon assembly satellite dans la phase finale et a fusionné assemblée.

Récemment, j'ai commencé à travailler sur un projet similaire, où il y a plusieurs secondaire langues, et donc plusieurs assemblys satellites, et ILMerge était en train de faire quelque chose de très étrange: au Lieu de fusionner les plusieurs assemblys satellites je l'avais demandé, c'était de fusionner le premier satellite de l'assemblée en plusieurs fois!

par exemple de ligne de commande:

"c:\Program Files\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1SomeFinalProg.exe %1InputProg.exe %1es\InputProg.resources.dll %1fr\InputProg.resources.dll

Avec cette ligne de commande, je recevais les ensembles suivants de ressources dans la fusion de l'assemblée (observé avec ILSpy decompiler):

InputProg.resources
InputProg.es.resources
InputProg.es.resources <-- Duplicated!

Après certains de jouer, j'ai fini par réaliser que c'est juste un bug dans ILMerge lorsqu'il rencontre plusieurs fichiers avec le même nom en une seule ligne de commande d'appel. La solution est tout simplement de fusionner chaque satellite de montage dans une autre ligne de commande d'appel:

"c:\Program Files\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1TempProg.exe %1InputProg.exe %1es\InputProg.resources.dll
"c:\Program Files\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1SomeFinalProg.exe %1TempProg.exe %1fr\InputProg.resources.dll

Quand je fais cela, les ressources qui en résultent dans l'assemblage final sont corrects:

InputProg.resources
InputProg.es.resources
InputProg.fr.resources

Donc, finalement, dans le cas où cela permet de clarifier, voici un poste complet-construire le fichier de commandes:

"%ProgramFiles%\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1TempProg.exe %1InputProg.exe %1es\InputProg.resources.dll 
IF %ERRORLEVEL% NEQ 0 GOTO END
"%ProgramFiles%\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1SomeFinalProg.exe %1TempProg.exe %1fr\InputProg.resources.dll 
IF %ERRORLEVEL% NEQ 0 GOTO END
del %1InputProg.exe 
del %1InputProg.pdb 
del %1TempProg.exe 
del %1TempProg.pdb 
del %1es\*.* /Q 
del %1fr\*.* /Q 
:END

Mise à JOUR 3: ILRepack

Une autre note rapide - l'Une des choses qui me dérangeait avec ILMerge est que c'est un complément de propriétaires de Microsoft outil, il n'est pas installé par défaut avec Visual Studio, et, par conséquent, un supplément de dépendance qui le rend un peu plus difficile pour un tiers de commencer à utiliser mes projets open-source.

J'ai récemment découvert ILRepack, un open-source (Apache 2.0) équivalent qui pour l'instant fonctionne tout aussi bien pour moi (drop-in de remplacement), et peut être distribué librement avec votre projet sources.


J'espère que cela aide quelqu'un là-bas!

  • Après, certains plus (relativement obscessive) recherche, j'ai trouvé quelques prometteur extraits de code: neowin.net/forum/lofiversion/index.php/t625641.html social.msdn.microsoft.com/Forums/en/vsx/thread/... Il ressemble à la création d'une coutume ResourceManager sera la voie à suivre - je suis hors de décompiler le défaut ResourceManager (Réflecteur à la rescousse!) pour voir si je peux mieux comprendre ce qu'il fait et comment il fonctionne.
  • Bonjour. J'ai besoin de faire exactement la même chose. J'ai ajouté cette nouvelle classe et remplacé dans un fichier de concepteur, mais je suis NullReferenceException à la ligne: ce.outputPath.Les propriétés.AutoHeight = ((bool)(ressources.GetObject("outputPath.Les propriétés.AutoHeight"))); (ressources est un SingleAssemblyComponentResourceManager) outputPath - est un DevExpress de contrôle. Même après la fusion de tous les DevExpress assemblées.
  • Savez-vous ce qui est fait Nulle? est-ce le "ressources" de l'objet qui n'est en fait pas initialisé? Pas sûr que c'est le meilleur forum pour moi de vous aider à travailler sur votre code, mais n'hésitez pas à m'envoyer les détails des trucs à klerks dot biz.
  • Il fonctionne avant de les fusionner si vous return base.InternalGetResourceSet(culture, createIfNotExists, tryParents); quand store == null ou rs == null
  • merci de me faire sentir comme un idiot! 🙂 - Je vais jouer avec ça ce soir et mise à jour de la question/code
  • Merci encore @Wouter, c'est BEAUCOUP plus agréable/résultat plus propre!
  • Belle solution, je vous remercie. Je vous recommande d'utiliser Fody.Ionad au lieu de fart.exe. Il devrait fonctionner aussi longtemps que le code dans d'autres assemblées ne pas utiliser ResourceManager directement la propriété

InformationsquelleAutor Tao | 2009-12-23