单程序集多语言 Windows 窗体部署(ILMerge 和附属程序集/本地化) - 可能吗? [英] Single-assembly multi-language Windows Forms deployment (ILMerge and satellite assemblies / localization) - possible?

查看:27
本文介绍了单程序集多语言 Windows 窗体部署(ILMerge 和附属程序集/本地化) - 可能吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a simple Windows Forms (C#, .NET 2.0) application, built with Visual Studio 2008.

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

I would like to support multiple UI languages, and using the "Localizable" property of the form, and culture-specific .resx files, the localization aspect works seamlessly and easily. Visual Studio automatically compiles the culture-specific resx files into satellite assemblies, so in my compiled application folder there are culture-specific subfolders containing these satellite assemblies.

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

I would like to have the application be deployed (copied into place) as a single assembly, and yet retain the ability to contain multiple sets of culture-specific resources.

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

Using ILMerge (or ILRepack), I can merge the satellite assemblies into the main executable assembly, but the standard .NET ResourceManager fallback mechanisms do not find the culture-specific resources that were compiled into the main assembly.

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

Interestingly, if I take my merged (executable) assembly and place copies of it into the culture-specific subfolders, then everything works! Similarly, I can see the main and culture-specific resources in the merged assemby when I use Reflector (or ILSpy). But copying the main assembly into culture-specific subfolders defeats the purpose of the merging anyway - I really need there to be just a single copy of the single assembly...

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

I'm wondering whether there is any way to hijack or influence the ResourceManager fallback mechanisms to look for the culture-specific resources in the same assembly rather than in the GAC and culture-named subfolders. I see the fallback mechanism described in the following articles, but no clue as to how it would be modified: BCL Team Blog Article on ResourceManager.

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

Does anyone have any idea? This seems to be a relatively frequent question online (for example, another question here on Stack Overflow: "ILMerge and localized resource assemblies"), but I have not found any authoritative answer anywhere.

遵循 casperOne 的建议下面,我终于能够完成这项工作.

Following casperOne's recommendation below, I was finally able to make this work.

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

I'm putting the solution code here in the question because casperOne provided the only answer, I don't want to add my own.

通过从在InternalGetResourceSet"方法中实现的框架资源查找回退机制中抽出勇气,并使我们的相同程序集搜索成为使用的第一个机制,我能够让它工作.如果在当前程序集中没有找到资源,那么我们调用 base 方法来启动默认搜索机制(感谢@Wouter 在下面的评论).

I was able to get it to work by pulling the guts out of the Framework resource-finding fallback mechanisms implemented in the "InternalGetResourceSet" method and making our same-assembly search the first mechanism used. If the resource is not found in the current assembly, then we call the base method to initiate the default search mechanisms (thanks to @Wouter's comment below).

为此,我派生了ComponentResourceManager"类,并仅覆盖了一个方法(并重新实现了一个私有框架方法):

To do this, I derived the "ComponentResourceManager" class, and overrode just one method (and re-implemented a private framework method):

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——而且每次更改设计的表单时都需要这样做——VisualStudio 会自动替换该代码.(该问题在Customize WindowsForms Designer 使用 MyResourceManager",我没有找到更优雅的解决方案 - 我使用 fart.exe 在预构建步骤中自动替换.)

To actually use this class, you need to replace the System.ComponentModel.ComponentResourceManager in the "XXX.Designer.cs" files created by Visual Studio - and you will need to do this every time you change the designed form - Visual Studio replaces that code automatically. (The problem was discussed in "Customize Windows Forms Designer to use MyResourceManager", I did not find a more elegant solution - I use fart.exe in a pre-build step to auto-replace.)

在我报告上述解决方案时,我实际上只支持两种语言,ILMerge 在将我的附属程序集合并到最终合并程序集方面做得很好.

At the time I reported the solution above, I was actually only supporting two languages, and ILMerge was doing a fine job of merging my satellite assembly into the final merged assembly.

最近我开始从事一个类似的项目,其中有多种次要语言,因此有多个附属程序集,而 ILMerge 正在做一些非常奇怪的事情:而不是合并我请求的多个附属程序集,它多次合并第一个卫星组件!

Recently I started working on a similar project where there are multiple secondary languages, and therefore multiple satellite assemblies, and ILMerge was doing something very strange: Instead of merging the multiple satellite assemblies I had requested, it was merging the first satellite assembly in multiple times!

例如命令行:

"c:Program FilesMicrosoftILMergeILMerge.exe" /t:exe /out:%1SomeFinalProg.exe %1InputProg.exe %1esInputProg.resources.dll %1frInputProg.resources.dll

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

With that command-line, I was getting the following sets of resources in the merged assembly (observed with ILSpy decompiler):

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

经过一番尝试后,我最终意识到这只是 ILMerge 中的一个错误,因为它在单个命令行调用中遇到多个同名文件.解决方案只是在不同的命令行调用中合并每个附属程序集:

After some playing around, I ended up realizing this is just a bug in ILMerge when it encounters multiple files with the same name in a single command-line call. The solution is simply to merge each satellite assembly in a different command-line call:

"c:Program FilesMicrosoftILMergeILMerge.exe" /t:exe /out:%1TempProg.exe %1InputProg.exe %1esInputProg.resources.dll
"c:Program FilesMicrosoftILMergeILMerge.exe" /t:exe /out:%1SomeFinalProg.exe %1TempProg.exe %1frInputProg.resources.dll

当我这样做时,最终组装中的结果资源是正确的:

When I do this, the resulting resources in the final assembly are correct:

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

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

So finally, in case this helps clarify, here's a complete post-build batch file:

"%ProgramFiles%MicrosoftILMergeILMerge.exe" /t:exe /out:%1TempProg.exe %1InputProg.exe %1esInputProg.resources.dll 
IF %ERRORLEVEL% NEQ 0 GOTO END

"%ProgramFiles%MicrosoftILMergeILMerge.exe" /t:exe /out:%1SomeFinalProg.exe %1TempProg.exe %1frInputProg.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 困扰我的一件事是它是一个额外的专有 Microsoft 工具,默认情况下没有安装在 Visual Studio 中,因此额外的依赖项使第三方变得有点困难开始我的开源项目.


UPDATE 3: ILRepack

Another quick note - One of the things that bothered me with ILMerge was that it is an additional proprietary Microsoft tool, not installed by default with Visual Studio, and therefore an extra dependency that makes it that little bit harder for a third party to get started with my open-source projects.

我最近发现了 ILRepack,这是一个开源 (Apache 2.0) 等价物,目前有效对我来说也一样(直接替换),并且可以随您的项目源免费分发.

I recently discovered ILRepack, an open-source (Apache 2.0) equivalent that so far works just as well for me (drop-in replacement), and can be freely distributed with your project sources.

我希望这对那里的人有所帮助!

I hope this helps someone out there!

推荐答案

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

The only way I can see this working is by creating a class that derives from ResourceManager and then overriding the InternalGetResourceSet and GetResourceFileName methods. From there, you should be able to override where resources are obtained, given a CultureInfo instance.

这篇关于单程序集多语言 Windows 窗体部署(ILMerge 和附属程序集/本地化) - 可能吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆