Часть 4. Процессы, происходящие при компиляции и запуске программы
Для получения представления о том, как работает .NET и какие службы она предлагает, рассмотрим, Что происходит при запуске программы, разработанной для среды исполнения .NET.
Пусть для примера приложение состоит из основного кода, написанного на С#, и библиотеки, созданной с помощью VB.NET.
Приложению необходимо общаться с существующим СОМ-компонентом, и подразумевается, что оно будет использовать некоторые из базовых классов .NET (практически невозможно создать приложение, которое будет делать что-нибудь полезное, если при этом оно не будет использовать базовые классы .NET):
Рис. 2. Процессы, сопровождающие компиляцию и исполнение приложения
На диаграмме прямоугольники показывают основные компоненты, связанные с компиляцией и исполнением программы, а стрелки - исполняемые задачи. В верхней части рисунка представлен процесс раздельной компиляции каждого проекта в сборку.
Две сборки способны взаимодействовать друг с другом благодаря свойствам совместимости языков .NET. Нижняя часть диаграммы демонстрирует процесс JIT-компиляции в сборках из IL в машинный код, который выполняется в области приложения внутри процесса. Показаны некоторые действия, которые выполняет код внутри CLR для достижения этой цели.
Компиляция
Перед запуском программа должна быть откомпилирована. Однако, в отличие от прежних исполняемых файлов и DLL, теперь компилированный код программы не содержит инструкций ассемблера. Вместо этого он содержит инструкции на промежуточном языке Microsoft (MSIL или IL). Промежуточный язык в чем-то похож на байт-код Java.
IL - это низкоуровневый код, который может быть быстро преобразован (откомпилирован JIT) a родной машинный код.
Пакет, внутри которого будет содержаться откомпилированная программа, состоит из нескольких сборок. Каждая сборка содержит код на промежуточном языке и метаданные, описывающие типы данных и методы внутри отдельной сборки. Кроме того метаданные содержат простой хэш, который строится на основе содержимого сборки и можетбыть использован для проверки ее целостности; информацию о версиях; сведения о том, какие сборки будут вызываться данной сборкой; и, возможно, информацию о том, какие привилегии потребуются для выполнения кода сборки.
Прежний программный продукт состоял бы из исполняемого файла, содержащего точку входа основной программы, и одной или нескольких библиотек или СОМ-компонентов. Продукт для .NET состоит из определенного числа сборок, одна из которых является исполняемой и содержит точку входа основной программы, а другие представляют собой библиотеки.
В примере, приведенном на рисунке, имеются всего две сборки: исполняемая, содержащая код на С#, и библиотека, содержащая компилированный код VB.NET.
Исполнение
В период исполнения программы среда исполнения .NET загружает первую сборку, ту, что содержит точку входа основной программы.
Среда использует имеющийся хэш для проверки целостности сборки и метаданные для того, чтобы просмотреть определенные типы и убедиться, что среда сможет выполнить сборку.
Правильно разработанные коммерческие приложения должны явно указывать, какие привилегии .NET им могут потребоваться (например, понадобится ли приближению доступ к файловой системе, реестру и т.д.). В этом случае CLRобратится к политике безопасности системы и к учетной записи, под которой выполняется программа, и проверит, может ли она предоставить необходимые привилегии. Если код не запрашивает права явно, они будут предоставлены ему по первому требованию, если, конечно, это возможно.
На этом этапе CLRвыполнит еще одно действие для проверки так называемой безопасности кода по типу памяти(memory type safely). Код считается безопасным по типу памяти только в том случае, если он обращается к памяти способами, которые может контролировать CLR. Безопасный по типу памяти код гарантированно не будет пытаться прочесть или записать в память, не принадлежащую ему. Это важно, так как .NET имеет механизм (так называемые области приложений), позволяющий нескольким приложениям выполняться в одном процессе. При этом необходимо гарантировать, что никакое приложение не будет пытаться обратиться к памяти другого приложения. ЕслиCLR не будет уверена в том, что код безопасен по типу памяти, то в зависимости от местной политики безопасности она может даже отказать в исполнении кода.
Затем CLR выполняет код. Она создаст процесс, в котором будет исполняться код, и отмечает область приложения, в которой размещается главный поток приложения.
В некоторых случаях программа может потребовать поместить ее в уже имеющийся процесс запущенного ранее кода, тогдаCLR создаст для нее только новую область приложения.
CLR берет первую часть кода, которая требуется для исполнения, и компилирует ее с промежуточного языка на язык ассемблера, после чего выполняет ее из соответствующего потока программы.
Каждый раз, когда в процессе исполнения встречается новый метод, не исполнявшийся ранее, он компилируется в исполняемый код. Процесс компиляции происходит только один раз. Как только метод откомпилирован, его адрес заменяется адресом компилированного кода. Таким образом, производительность не ухудшается, так как компилируются только те участки кода, которые действительно используются.
Этот процесс носит название компиляции Just-In-Time. Отметим, что JIT-компилятор может в зависимости от параметров компиляции, указанных в сборке, оптимизировать код в процессе компиляции, например, путем подстановки некоторых методов (inline) вместо их вызовов.
Во время выполнения кода CLR следит за использованием памяти. На основе этих наблюдений она в определенные моменты будет останавливать программу на короткий промежуток времени (обычно миллисекунды) и запускать сборщика мусора, который проверит переменные программы и выяснит, какие из областей памяти активно используются программой, для того, чтобы освободить неиспользуемые участки.
CLR также производит загрузку сборок по мере необходимости, в том числе СОМ-компонентов, используя службы .NET COM interop.