Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save eterekhin/404621e102bc573acc103a762253c856 to your computer and use it in GitHub Desktop.
Save eterekhin/404621e102bc573acc103a762253c856 to your computer and use it in GitHub Desktop.

Как CLR ищет сборки

При попытке создать динамическую сборку я столкнулся с проблемой. У меня был проект, который генерировался утилитой, я программно компилировал его используя Roslyn api.
Это апи требует передачи Metadata References, ссылок на сборки, которые нужны компилируемому проекту, я добавил всё из папки ProgramFiles/dotnet/sdk/3.2.1/ и все из папки с опубликованным вручную проектом. Таким образом я разрешил все статические привязки и "добавил" в манифест сборки всю необходимую информацию о зависимых сборках(имя, token, culture и т.д)

Все успешно скомпилировалось, roslyn не вернул никаких сообщений об ошибках компиляции, и я загрузил сборку используя csharp Assembly.LoadFrom(pathToAssembly). Далее, когда попытался рефлексивно обратиться к ее типам(csharp assembly.GetTypes().ToArray()) получил сообщение об ошибке :

В итоге при попытке обратиться к информации о типах, CLR нужно было подгрузить сборку в которой эти типы лежат, но найти ее она не могла. А почему могла найти большинство других типов в сборке? На самом деле большинство типов на которые ссылалась эта сборка уже были загружены в домен, поэтому среда даже не стала их искать. Не смогла же найти только специфичные типы, которые использовала динамически созданная сборка. А как вообще среда загружает типы? Есть хорошая статья, по этому поводу, там написано:

These locations(where find dependencies) include:

  • App base directory (in the same folder as the entry point application, no config required)
  • Package cache folders (NuGet restore cache or NuGet fallback folders)
  • An optimized package cache or packages store
  • The servicing index (rarely used. For Windows Update purposes.)
  • Shared framework (configured via runtimeconfig.json)

В моем случае можно ограничиться только App Base Directory, Package cache Folders(это указывается в projectname.runtimeconfig.dev.json) и Shared Framework.

App Base Directory - это все dll которые лежат в той же директории что и dll основного проекта Поиск в shared Framework и Package cache folders осуществляется указанием в prjectname.runtimeconfig.dev.json в моем случае он выглядит так:

{
  "runtimeOptions": {
    "additionalProbingPaths": [
      "C:\\Users\\evgeniy\\.dotnet\\store\\|arch|\\|tfm|",
      "C:\\Users\\evgeniy\\.nuget\\packages",
      "C:\\Program Files\\dotnet\\sdk\\NuGetFallbackFolder"
    ]
  }
}

Этот json индивидуален для каждой машины, он показывает, откуда нужно разрешать все зависимости, в данном случае из трех папок. Работает он совместно с projectname.deps.json в котором указаны все статические зависимости проекта и путь до них

Тем самым комбинируя additionalProbingPaths и выделенные красной стрелочкой, пути разрешаются зависимости, получается в моем случае можно либо добавить недостающие зависимости в папку где лежит dll проекта, либо добавить все необходимые зависимости в prjectname.deps.json, а именно в разделы libraries и targets:

Я попробовал и то, и то, все работало, но я предпочту первый вариант.

Также есть еще файл projectname.runtimeconfig.json, в нем указывается версия используемая версия runtime'a:

{
  "runtimeOptions": {
    "tfm": "netcoreapp3.1",
    "framework": {
      "name": "Microsoft.NETCore.App",
      "version": "3.1.0"
    }
  }
}

Таким образом приложение использует общие сборки конкретной версии фреймворка, которые лежат в C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.2(не точно)

В завершение, проблема возникла из-за двух типов линковки сборок, статического добавления имен сборок в манифест и загрузки во время исполнения, о чем я не знал. Решение проблемы, это перемещение dll от которых зависит приложение на уровень dll файла этого приложения

Также интересный факт

Если хотите загрузить сборку со всеми ее зависимостями(которые должны располагаться в файле с загружаемой .dll) нужно использовать Assembly.LoadFrom, а не Assembly.LoadFile. Во втором случае зависимые сборки не загружаются. Дело в том, что LoadFile, автоматически не разрешает никакие сборки, нужно делать это самостоятельно, подписавшись на AppDomain.AssemblyResolve event. Assembly.LoadFile загружает сборку и начинает разрешать все ее зависимости в папке с этой сборкой, глобальном кеше nuget или GAC

В чем отличие поиска сборок в .net core и .net framework

Места где CLR будет искать сборки конфигурируются по-разному. В обоих случаях среда будет искать сборки в рантайме в директориях где лежит dll (это AppDomain.BaseDirectory), далее для .net framework существуют private path subdirectory and codebase locations. Они конфигурируются в (appconfig'e)[https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/specify-assembly-location]. .net core же будет грузить сборки в путях указанных в .runtimeconfig.dev.json'e, в секции additionalProbingPaths, там by default пути к nuget кешам.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment