我有一个使用Visual Studio 2008构建的简单Windows窗体(C#、. NET 2.0)应用程序。
我想支持多种UI语言,并使用表单的“ Localizable”属性以及特定于文化的.resx文件,本地化方面可以无缝且轻松地工作。Visual Studio会自动将区域性特定的resx文件编译为附属程序集,因此在我的已编译应用程序文件夹中,包含这些区域性程序集的特定于区域性的子文件夹。
我希望将应用程序作为 单个程序集 部署(复制到位),但仍保留包含多组特定于文化的资源的能力。
使用ILMerge(或ILRepack),我可以将附属程序集合并到主要的可执行程序集中,但是标准的.NET ResourceManager后备机制找不到编译到主要程序集中的特定于区域性的资源。
有趣的是,如果将合并的(可执行)程序集放入其特定于区域性的子文件夹中,则一切正常!同样,当我使用Reflector(或ILSpy)时,可以在合并的assemby中看到主要资源和特定于文化的资源。但是将主程序集复制到特定于文化的子文件夹中仍然无法实现合并的目的- 我真的需要只有单个程序集的一个副本…
我想知道 是否有任何方法可以劫持或影响ResourceManager后备机制,以在同一程序集中而不是在GAC和以文化命名的子文件夹中查找特定于文化的资源 。我看到了以下文章中描述的回退机制,但是没有关于如何修改的线索:Resource Manager上的BCL团队博客文章。
有谁有想法吗?这似乎是一个在线上相对频繁的问题(例如,Stack Overflow上的另一个问题:“ ILMerge和本地化资源程序集 ”),但是我在任何地方都没有找到任何权威性的答案。
遵循casperOne的建议,我终于能够完成这项工作。
我将解决方案代码放在问题中,因为casperOne提供了唯一的答案,我不想添加自己的答案。
通过将胆量从“ InternalGetResourceSet”方法中实现的Framework资源查找后备机制中剔除,并使我们的程序集搜索成为 第一个 使用的机制,我能够使其工作。如果在当前程序集中找不到该资源,则我们调用base方法来启动默认的搜索机制(由于下面的@Wouter注释)。
为此,我派生了“ ComponentResourceManager”类,并覆盖了仅一个方法(并重新实现了私有框架方法):
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); } } } }
要实际使用此类,您需要在Visual Studio创建的“ XXX.Designer.cs”文件中替换System.ComponentModel.ComponentResourceManager- 并且每次更改设计表单时都需要执行此操作-Visual Studio会替换该类自动编码。(该问题在“ 自定义Windows窗体设计器以使用MyResourceManager ”中进行了讨论,但没有找到更优雅的解决方案- 我在预构建步骤中使用fart.exe来自动替换。)
在我报告上述解决方案时,我实际上仅支持两种语言,而ILMerge在将卫星程序集合并到最终合并程序集方面做得很好。
最近,我开始从事一个类似的项目,该项目有 多种 辅助语言,因此有多个附属程序集,而ILMerge所做的事情很奇怪:它不是合并我请求的多个附属程序集,而是多次合并了第一个附属程序集。 !
例如命令行:
"c:\Program Files\Microsoft\ILMerge\ILMerge.exe" /t:exe /out:%1SomeFinalProg.exe %1InputProg.exe %1es\InputProg.resources.dll %1fr\InputProg.resources.dll
通过该命令行,我在合并的程序集中获得了以下资源集(通过ILSpy反编译器观察):
InputProg.resources InputProg.es.resources InputProg.es.resources <-- Duplicated!
经过一番摸索,我最终意识到这只是ILMerge中的一个错误,当它在一个命令行调用中遇到 多个具有相同名称的文件时 。解决方案是简单地将每个附属程序集合并到不同的命令行调用中:
"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
当我这样做时,最终程序集中的结果资源是正确的:
InputProg.resources InputProg.es.resources InputProg.fr.resources
因此,最后,以防万一,下面是一个完整的构建后批处理文件:
"%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
另一个快速说明-使用ILMerge困扰我的一件事是它是附加的专有Microsoft工具,默认情况下未随Visual Studio一起安装,因此存在额外的依赖关系,这使得第三方上手有点困难与我的开源项目。
我最近发现了ILRepack,它是一种等效于开源(Apache 2.0)的工具,到目前为止,它对我也可以正常工作(直接替换),并且可以随项目源自由分发。
我希望这可以帮助某个人!
我看到此工作的唯一方法是创建一个派生自ResourceManager并覆盖InternalGetResourceSetand GetResourceFileName方法的类。从那里,给定一个CultureInfo实例,您应该能够覆盖从何处获取资源。
ResourceManager
InternalGetResourceSet
GetResourceFileName
CultureInfo