c # путь локализованных ресурсов для DLL плагинов - PullRequest
5 голосов
/ 08 июля 2011

В моем приложении на C # есть механизм плагинов, который загружает библиотеки плагинов из разных путей, как указано в файле конфигурации XML. Мое приложение локализуемо. Основная сборка (* .exe) имеет спутниковые сборки для локализованных языков рядом с exe стандартным способом .NET (например, .\en\en-US\main.resources.dll; .\de\de_DE\main.resources.dll; и т. Д.).

Я начал локализацию плагина и должен был обнаружить, что сателлитная сборка должна быть помещена в папки рядом с exe. Помещая его рядом с плагином DLL, менеджер ресурсов не находит его.

Однако, поскольку мои плагины являются взаимозаменяемыми и потенциально находятся в разных папках, я бы предпочел поместить локализованные сборки ресурсов рядом с плагинами, а , а не - в exe.

Возможно ли это?!?!

Альтернативой, с которой я мог бы жить, было бы встраивание локализованных ресурсов в библиотеки DLL. Это возможно ??

Ура, Felix

Ответы [ 2 ]

0 голосов
/ 27 марта 2017

Я столкнулся с этой проблемой при работе над продуктом для нашей компании. Я нигде не нашел ответа, поэтому я опубликую свое решение здесь на случай, если кто-то окажется в такой же ситуации.

Начиная с .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;
}

Первая часть этого обработчика событий имеет дело со спутниковыми сборками ресурсов, затем следует поведение поиска, которое я использую для обычных сборок. Этого должно быть достаточно, чтобы кто-нибудь мог заставить систему, подобную этой, работать с нуля.

0 голосов
/ 08 июля 2011

Ok. Если вы хотите «отсоединить» yoursefl от стандартной привязки ресурса локализации и хотите иметь возможность загружать сборку из любого места, один из вариантов

a) реализовать интерфейс для взаимодействия спереводы внутри этой сборки

b) использовать функцию Assembly.Load для загрузки нужной сборки .NET из нужного вам места

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...