Ответ 1
Большое спасибо Hans Passant и dthorpe за объяснение того, что происходит.
Я нашел dthorpe отличное объяснение того, как JIT-компилятор работает здесь: С# JIT-компиляция и .NET
Чтобы процитировать dthorpe здесь:
Да, код JIT'ing IL включает перевод IL на нативную машину инструкции.
Да, среда выполнения .NET взаимодействует с собственным машинным кодом JIT'ed, в том смысле, что среда выполнения имеет блоки памяти, занимаемые собственный машинный код, вызовы во время выполнения в собственный машинный код, и др.
Вы правы, что среда выполнения .NET не интерпретирует код IL в ваших сборках.
Что происходит, когда выполнение достигает функции или блока кода (например, else else блока if), который еще не был JIT, скомпилированный в собственный машинный код, JIT'r вызывается для компиляции этого блока IL в собственный машинный код. Когда это будет сделано, выполнение программы входит свежевыпущенный машинный код для выполнения его программной логики. Если при выполнении этого исполняемого машинного кода выполнение функции вызов функции, которая еще не была скомпилирована для машинного кода, JIT'r вызывается для компиляции этой функции "как раз вовремя". И так далее.
JIT'r не обязательно компилирует всю логику тела функции в машинный код сразу. Если функция имеет операторы if, Блоки операторов команд if и else могут не составлять JIT пока выполнение фактически не пройдет через этот блок. Кодовые пути, которые не выполняются, остаются в форме IL до тех пор, пока они не выполняются.
Скомпилированный собственный машинный код хранится в памяти, так что он может быть снова используется в следующий раз, когда выполняется секция кода. Второй время, когда вы вызываете функцию, она будет работать быстрее, чем в первый раз, когда вы вызовите его, потому что во второй раз не нужно делать шаг JIT.
В настольном компьютере .NET собственный машинный код хранится в памяти для время жизни приложения. В .NET CF собственный машинный код может быть выбрасывается, если приложение работает с низким объемом памяти. Это будет JIT снова скомпилирован из исходного кода IL в следующий раз проходит через этот код.
С информацией из этого вопроса и информацией от Hans Passant, очень ясно, что происходит:
- Компилятор JIT пытается преобразовать весь код точки входа
блок (в этом случае моя функция
Main()
) в собственный код. Эта требует разрешения всех ссылок. - Встроенная сборка LoaderLibrary.dll НЕ загружена в
AppDomain еще, потому что код, который это делает, определен в
Main()
(и он не может выполнить код, который не был скомпилирован). - Компилятор JIT пытается разрешить ссылку на LoaderLibrary.dll, выполнив поиск в AppDomain, Global Assembly Cache, App.config/Web.config и зондирование (среда PATH, текущая рабочий каталог и т.д.). Более подробную информацию об этом можно найти в MSDN статья здесь: Как Runtime находит сборки
- Компилятор JIT не может разрешить ссылку на
LoaderLibrary.LoaderLibrary.Test();
и приводит к ошибкеCould not load file or assembly or one of its dependencies
Как обойти это, как предложил Ханс Пассант, нужно загрузить ваши сборки в блок кода, который получает JIT, скомпилированный ранее, чем любой блок кода, который ссылается на эти сборки.
Добавив [MethodImpl (MethodImplOptions.NoInlining)] к методам, которые ссылаются на динамически загруженные сборки, это предотвратит попытку оптимизатора встроить код метода.