c#插件DLL的本地化资源路径

时间:2011-07-08 06:55:21

标签: c# plugins localization internationalization satellite-assembly

在我的C#应用​​程序中,我有一个插件机制,可以从配置XML文件中指定的不同路径加载插件DLL。我的应用程序可以本地化。主程序集(* .exe)具有标准.NET方式的exe旁边的本地化语言的附属程序集(例如.\en\en-US\main.resources.dll; .\de\de_DE\main.resources.dll;等等)。

我开始本地化插件并且必须发现必须将附属程序集放在exe旁边的文件夹中。将它放在插件DLL旁边时,资源管理器找不到它。

但是,由于我的插件是可互换的,并且可能位于不同的文件夹中,因此我非常希望将本地化资源程序集放在插件旁边,将而不是放到exe上。

这可能吗?!?!

我可以使用的替代方法是将本地化资源嵌入到DLL中。这可能吗?

干杯, 菲利克斯

2 个答案:

答案 0 :(得分:0)

确定如果您希望从标准化的本地化资源绑定中“分离”yoursefl,并希望能够自由地从任何位置加载程序集,其中一个选项是

a)实现与该程序集中的翻译交互的界面

b)使用Assembly.Load函数从您想要的位置加载所需的.NET程序集

答案 1 :(得分:0)

在为我们公司处理产品时,我遇到了这个问题。我没有在任何地方找到答案,所以我将在这里发布我的解决方案以防万一其他人发现自己处于相同的情况。

从.NET 4.0开始,有一个解决这个问题的方法,因为现在卫星程序集被传递给AssemblyResolve处理程序。如果您已经有一个可以从远程目录加载程序集的插件系统,那么您可能已经有了一个程序集解析处理程序,您只需要扩展它以对卫星资源程序集使用不同的搜索行为。如果您没有,那么实现并非易事,因为您基本上要对所有程序集搜索行为负责。我将发布完整的工作解决方案代码,以便您接受任何一种方式。首先,您需要将AssemblyResolve处理程序挂钩到某处,如下所示:

AppDomain.CurrentDomain.AssemblyResolve += ResolveAssemblyReference;

然后假设您有几个变量来保存主应用程序和插件目录的路径信息,如下所示:

string _processAssemblyDirectoryPath;
List<string> _assemblySearchPaths;

然后你需要一个看起来有点像这样的辅助方法:

static Assembly LoadAssembly(string assemblyPath)
{
    // If the target assembly is already loaded, return the existing assembly instance.
    Assembly[] loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
    Assembly targetAssembly = loadedAssemblies.FirstOrDefault((x) => !x.IsDynamic && String.Equals(x.Location, assemblyPath, StringComparison.OrdinalIgnoreCase));
    if (targetAssembly != null)
    {
        return targetAssembly;
    }

    // Attempt to load the target assembly
    return Assembly.LoadFile(assemblyPath);
}

最后,您需要所有重要的AssemblyResolve事件处理程序,它看起来像这样:

Assembly ResolveAssemblyReference(object sender, ResolveEventArgs args)
{
    // Obtain information about the requested assembly
    AssemblyName targetAssemblyName = new AssemblyName(args.Name);
    string targetAssemblyFileName = targetAssemblyName.Name + ".dll";

    // Handle satellite assembly load requests. Note that prior to .NET 4.0, satellite assemblies didn't get
    // passed to AssemblyResolve handlers. When this was changed, there is a specific guarantee that if null is
    // returned, normal load procedures will be followed for the satellite assembly, IE, it will be located and
    // loaded in the same manner as if this event handler wasn't registered. This isn't sufficient for us
    // though, as the normal load behaviour doesn't correctly locate satellite assemblies where the owning
    // assembly has been loaded using Assembly.LoadFile where the assembly is located in a different folder to
    // the process assembly. We handle that here by performing the satellite assembly search process ourselves.
    // Also note that satellite assemblies are formally documented as requiring the file name extension of
    // ".resources.dll", so detecting satellite assembly load requests by comparing with this known string is a
    // valid approach.
    if (targetAssemblyFileName.EndsWith(".resources.dll"))
    {
        // Retrieve the owning assembly which is requesting the satellite assembly
        string owningAssemblyName = targetAssemblyFileName.Replace(".resources.dll", ".dll");
        Assembly owningAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((x) => x.Location.EndsWith(owningAssemblyName));
        if (owningAssembly == null)
        {
            return null;
        }

        // Retrieve the directory containing the owning assembly
        string owningAssemblyDirectory = Path.GetDirectoryName(owningAssembly.Location);

        // Search for the required satellite assembly in resource subdirectories, and load it if found.
        CultureInfo searchCulture = System.Threading.Thread.CurrentThread.CurrentCulture;
        while (searchCulture != CultureInfo.InvariantCulture)
        {
            string resourceAssemblyPath = Path.Combine(owningAssemblyDirectory, searchCulture.Name, targetAssemblyFileName);
            if (File.Exists(resourceAssemblyPath))
            {
                Assembly resourceAssembly = LoadAssembly(resourceAssemblyPath);
                if (resourceAssembly != null)
                {
                    return resourceAssembly;
                }
            }
            searchCulture = searchCulture.Parent;
        }
        return null;
    }

    // If the target assembly exists in the same directory as the requesting assembly, attempt to load it now.
    string requestingAssemblyPath = (args.RequestingAssembly != null) ? args.RequestingAssembly.Location : String.Empty;
    if (!String.IsNullOrEmpty(requestingAssemblyPath))
    {
        string callingAssemblyDirectory = Path.GetDirectoryName(requestingAssemblyPath);
        string targetAssemblyInCallingDirectoryPath = Path.Combine(callingAssemblyDirectory, targetAssemblyFileName);
        if (File.Exists(targetAssemblyInCallingDirectoryPath))
        {
            try
            {
                return LoadAssembly(targetAssemblyInCallingDirectoryPath);
            }
            catch (Exception ex)
            {
                // Log an error
                return null;
            }
        }
    }

    // If the target assembly exists in the same directory as the process executable, attempt to load it now.
    string processDirectory = _processAssemblyDirectoryPath;
    string targetAssemblyInProcessDirectoryPath = Path.Combine(processDirectory, targetAssemblyFileName);
    if (File.Exists(targetAssemblyInProcessDirectoryPath))
    {
        try
        {
            return LoadAssembly(targetAssemblyInProcessDirectoryPath);
        }
        catch (Exception ex)
        {
            // Log an error
            return null;
        }
    }

    // Build a list of all assemblies with the requested name in the defined list of assembly search paths
    Dictionary<string, AssemblyName> assemblyVersionInfo = new Dictionary<string, AssemblyName>();
    foreach (string assemblyDir in _assemblySearchPaths)
    {
        // If the target assembly doesn't exist in this path, skip it.
        string assemblyPath = Path.Combine(assemblyDir, targetAssemblyFileName);
        if (!File.Exists(assemblyPath))
        {
            continue;
        }

        // Attempt to retrieve detailed information on the name and version of the target assembly
        AssemblyName matchAssemblyName;
        try
        {
            matchAssemblyName = AssemblyName.GetAssemblyName(assemblyPath);
        }
        catch (Exception)
        {
            continue;
        }

        // Add this assembly to the list of possible target assemblies
        assemblyVersionInfo.Add(assemblyPath, matchAssemblyName);
    }

    // Look for an exact match of the target version
    string matchAssemblyPath = assemblyVersionInfo.Where((x) => x.Value == targetAssemblyName).Select((x) => x.Key).FirstOrDefault();
    if (matchAssemblyPath == null)
    {
        // If no exact target version match exists, look for the highest available version.
        Dictionary<string, AssemblyName> assemblyVersionInfoOrdered = assemblyVersionInfo.OrderByDescending((x) => x.Value.Version).ToDictionary((x) => x.Key, (x) => x.Value);
        matchAssemblyPath = assemblyVersionInfoOrdered.Select((x) => x.Key).FirstOrDefault();
    }

    // If no matching assembly was found, log an error, and abort any further processing.
    if (matchAssemblyPath == null)
    {
        return null;
    }

    // If the target assembly is already loaded, return the existing assembly instance.
    Assembly loadedAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((x) => String.Equals(x.Location, matchAssemblyPath, StringComparison.OrdinalIgnoreCase));
    if (loadedAssembly != null)
    {
        return loadedAssembly;
    }

    // Attempt to load the target assembly
    try
    {
        return LoadAssembly(matchAssemblyPath);
    }
    catch (Exception ex)
    {
        // Log an error
    }
    return null;
}

该事件处理程序的第一部分处理卫星资源程序集,然后我用于常规程序集的搜索行为就是这样。这应该足以帮助任何人从头开始使用这样的系统。