我有一个简单的Windows窗体(C,.NET 2.0)应用程序,它是用Visual Studio 2008构建的。
我希望支持多种UI语言,并且使用表单的“可本地化”属性和特定于区域性的.resx文件,本地化方面可以无缝、轻松地工作。Visual Studio会自动将区域性特定的Resx文件编译为附属程序集,因此在“已编译的应用程序”文件夹中有包含这些附属程序集的区域性特定子文件夹。
我希望将应用程序作为
单组件
同时保留包含多组特定于文化的资源的能力。
使用
ILMerge
(或)
ILRepack
,我可以将附属程序集合并到主可执行程序集中,但是标准的.NET ResourceManager回退机制找不到编译到主程序集中的特定于区域性的资源。
有趣的是,如果我将合并的(可执行的)程序集放在特定于区域性的子文件夹中,那么一切都正常!同样,当我使用时,我可以看到合并资产中的主要资源和特定于文化的资源
Reflector
(或)
ILSpy
)。但是,将主程序集复制到特定于区域性的子文件夹中会破坏合并的目的——我真的需要只有单个程序集的一个副本……
我想知道
是否有任何方法劫持或影响ResourceManager回退机制,以便在同一程序集中而不是在GAC和文化命名子文件夹中查找特定于文化的资源
. 我在下面的文章中看到了回退机制,但是没有关于如何修改它的线索:
BCL Team Blog Article on ResourceManager
.
有人知道吗?这似乎是一个相对频繁的在线问题(例如,堆栈溢出的另一个问题:“
ILMerge and localized resource assemblies
“),但是我在任何地方都没有找到任何权威的答案。
更新1:基本解决方案
跟随
casperOne's recommendation below
,我终于能做到这一点。
我把解决方案代码放在这里,因为casperone提供了唯一的答案,我不想添加我自己的答案。
我可以通过将其从“InternalGetResourceSet”方法中实现的框架资源查找回退机制中拉出来并使我们的相同程序集搜索
第一
使用的机制。如果在当前程序集中找不到资源,那么我们调用基方法来启动默认的搜索机制(感谢下面@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自动替换该代码。(问题讨论在
Customize Windows Forms Designer to use MyResourceManager
“,我没有找到更优雅的解决方案-我使用
fart.exe
在预生成步骤中自动替换。)
更新2:另一个实际考虑-超过2种语言
在我报告上述解决方案时,我实际上只支持两种语言,而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!
在玩了几次之后,我终于意识到这只是我遇到的一个错误。
多个同名文件
在单个命令行调用中。解决方案只是在不同的命令行调用中合并每个附属程序集:
"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
更新3:ilrepack
另一个简短的说明——ILmerge让我困扰的事情之一是它是一个额外的专有的微软工具,默认情况下不与Visual Studio一起安装,因此它是一个额外的依赖项,这使得第三方很难开始我的开源项目。
我最近发现
伊莱克包
这是一个开放源码(apache 2.0)等价物,到目前为止它对我(插入替换)同样有效,并且可以与您的项目源代码自由分发。
我希望这能帮到别人!