单组装多语言Windows Forms部署(ILMerge和附属程序集/本地化) - 可能吗?

Tao*_*Tao 55 .net c# assemblies localization winforms

我有一个简单的Windows窗体(C#,. NET 2.0)应用程序,使用Visual Studio 2008构建.

我想支持多种UI语言,并使用表单的"Localizable"属性和特定于文化的.resx文件,本地化方面可以无缝轻松地工作.Visual Studio会自动将特定于文化的resx文件编译为附属程序集,因此在我编译的应用程序文件夹中,存在包含这些附属程序集的特定于文化的子文件夹.

我希望将应用程序部署(复制到位)作为单个程序集,但仍保留包含多组特定于文化的资源的能力.

使用ILMerge(或ILRepack),我可以将附属程序集合并到主可执行程序集中,但标准的.NET ResourceManager回退机制找不到编译到主程序集中的特定于文化的资源.

有趣的是,如果我采用我的合并(可执行)程序集并将其副本放入特定于文化的子文件夹中,那么一切正常!同样,当我使用Reflector(或ILSpy)时,我可以在合并的组合中看到主要和特定于文化的资源.但是将主程序集复制到特定于文化的子文件夹中无论如何都会破坏合并的目的 - 我真的需要只有一个单一程序集的副本......

我想知道是否有任何方法可以劫持或影响ResourceManager回退机制,以在同一个程序集中查找特定于文化的资源,而不是在GAC和文化命名的子文件夹中查找.我看到了以下文章中描述的回退机制,但没有关于它如何被修改的线索:BCL团队关于ResourceManager的博客文章.

有谁有想法吗?这似乎是一个相对常见的在线问题(例如,Stack Overflow上的另一个问题:" ILMerge和本地化资源程序集 "),但我没有在任何地方找到任何权威答案.


更新1:基本解决方案

按照下面的casperOne的建议,我终于能够做到这一点.

我在这里提出解决方案代码,因为casperOne提供了唯一的答案,我不想添加自己的答案.

通过从"InternalGetResourceSet"方法中实现的Framework资源查找回退机制中拉出内容并使我们的同一组件搜索成为第一个使用的机制,我能够使它工作.如果在当前程序集中找不到资源,那么我们调用基本方法来启动默认搜索机制(感谢下面的@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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

要实际使用此类,您需要替换Visual Studio创建的"XXX.Designer.cs"文件中的System.ComponentModel.ComponentResourceManager - 每次更改设计的表单时都需要执行此操作 - Visual Studio将替换为代码自动.(问题在" 自定义Windows窗体设计器以使用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
Run Code Online (Sandbox Code Playgroud)

使用该命令行,我在合并程序集中获得了以下资源集(使用ILSpy反编译器观察到):

InputProg.resources
InputProg.es.resources
InputProg.es.resources <-- Duplicated!
Run Code Online (Sandbox Code Playgroud)

经过一些游戏后,我最终意识到这只是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
Run Code Online (Sandbox Code Playgroud)

当我这样做时,最终装配中产生的资源是正确的:

InputProg.resources
InputProg.es.resources
InputProg.fr.resources
Run Code Online (Sandbox Code Playgroud)

最后,如果这有助于澄清,这里是一个完整的构建后批处理文件:

"%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
Run Code Online (Sandbox Code Playgroud)

更新3:ILRepack

另一个快速注意事项 - ILMerge困扰我的一件事是,它是一个额外的专有Microsoft工具,默认情况下不安装Visual Studio,因此额外的依赖性使得第三方开始使用起来有点困难与我的开源项目.

我最近发现了ILRepack,它是一个开源(Apache 2.0)的等价物,到目前为止对我来说也很好用(直接替代),并且可以随项目源自由分发.


我希望这可以帮助那里的人!

cas*_*One 25

我能看到这个工作的唯一方法是创建一个派生自然ResourceManager后覆盖InternalGetResourceSetGetResourceFileName方法的类.从那里开始,您应该能够在给定CultureInfo实例的情况下覆盖获取资源的位置.