Какво е .NET Framework?
До момента направихме преглед на .NET платформата и разгледахме компонентите, от които тя се състои. Сега ще разгледаме в детайли .NET Framework, неговата архитектура и модела за изпълнение на приложения, който тя използва.
.NET Framework e среда за разработка и изпълнение на приложения за .NET платформата. Тя предоставя програмен модел, библиотеки от типове и единна инфраструктура за разработка на приложения и поддържа различни езици за програмиране.
Приложенията, базирани на .NET Framework, се компилират до междинен код (на езика IL) и се изпълняват контролирано от средата за изпълнение на .NET Framework. Компилираният .NET код се нарича още управляван код и може да работи без да се прекомпилира върху различни платформи, за които има имплементация за .NET Framework (Windows, Linux, FreeBSD).
Компоненти на .NET Framework
Можем да разделим .NET Framework на два основни компонента:
-
Common Language Runtime (CLR) – средата, в която се изпълнява управляваният код на .NET приложенията. Представлява виртуална машина, която контролирано изпълнява .NET кода и осигурява различни услуги, като управление на сигурността, управление на паметта и др.
-
Framework Class Library (FCL) – представлява основната библиотека от типове, които се използват при изграждането на .NET приложения. Съдържа основната функционалност за разработка, необходима за повечето приложения, като вход/изход, връзка с бази данни, работа с XML, изграждане на уеб приложения, използване на уеб услуги, изграждане на графичен потребителски интерфейс и др. Стандартните класове и типове от FCL можем да използваме навсякъде, където има инсталиран .NET Framework.
Архитектура на .NET Framework
Архитектурата на .NET Framework често пъти се разглежда на нива, както това е направено на следната схема:
Ще разгледаме отделните слоеве един по един и ще обясним тяхната роля в .NET Framework. Ще започнем от най-долния.
Операционна система
Операционната система управлява ресурсите, процесите и потребителите на машината. Тя предоставя и някои услуги на приложенията като например: COM+, MSMQ, IIS, WMI и други.
Средата, която изпълнява .NET приложенията (CLR), е обикновен процес в операционната система и се управлява от нея, както останалите процеси.
Най-често операционната система, която изпълнява CLR е Microsoft Windows, но .NET Framework има имплементации и за други операционни системи (например проектът Mono).
Common Language Runtime
Общата среда за изпълнение Common Language Runtime (CLR) управлява процеса на изпълнение на .NET код. Тя се грижи за заделяне и освобождаване на паметта, управлява конкурентността, грижите за сигурността на приложенията и изпълнява други важни задачи, свързани с изпълнението на кода. Ще обърнем специално внимание на CLR малко по-нататък.
Base Class Library
Base Class Library (BCL) е част от стандартната библиотека на .NET Framework – Framework Class Library (FCL).
BCL представлява богата обектно-ориентирана библиотека с основни класове, които осигуряват базова системна функционалност. BCL осигурява вход-изход, работа с колекции, символни низове, мрежови ресурси, сигурност, отдалечено извикване, многонишковост и др.
Технологиите ADO.NET, XML, ASP.NET и Windows Forms не са част от BCL, тъй като те са по-скоро допълнителни библиотеки, отколкото базова системна функционалност.
ADO.NET и XML
Слоят на ADO.NET и XML предоставя удобен начин за работа с релационни и други бази от данни и средства за обработка на XML. ADO.NET поддържа два моделa на работа с данни – свързан и несвързан. XML поддръжката реализира DOM модела и модел, подобен на SAX, за достъп до XML. Ще разгледаме в детайли XML и ADO.NET в темите "Работа с XML" и "Достъп до данни с ADO.NET".
ASP.NET и Windows Forms
ASP.NET и Windows Forms изграждат слоя за интерфейс към крайния потребител на приложенията и ни предоставят богата функционалност за създаване на уеб и Windows базиран потребителски интерфейс, както и уеб услуги. ASP.NET позволява по лесен начин да бъдат изграждани гъвкави динамични уеб сайтове и уеб приложения и уеб услуги. Windows Forms позволява изграждане на прозоречно-базиран графичен потребителски интерфейс с богати възможности.
ASP.NET и Windows Forms използват компонентно-базирана архитектура и благодарение на нея позволяват изграждане на потребителския интерфейс визуално, чрез сглобяване на компоненти в специално разработени за това редактори, предоставени от средите за разработка. Ще разгледаме в детайли технологиите Windows Forms и ASP.NET в темите "Графичен потребителски интерфейс с Windows Forms", "Изграждане на уеб приложения с ASP.NET" и "Уеб услуги с ASP.NET".
.NET Framework позволява на разработчика да използва различни езици за програмиране, както и да интегрира в едно приложение компоненти, разработвани на различни езици. Възможно е дори клас, написан на един език, да бъде наследен и разширен от клас, написан на друг език.
Microsoft .NET Framework поддържа стандартно езиците C#, VB.NET, Managed C++ и J#, но трети доставчици предлагат допълнително .NET версия на още много други езици, като Pascal, Perl, Python, Fortran, Cobol и други.
Съвместимостта на езиците за програмиране в .NET Framework се дължи на архитектурни решения, които ще разгледаме в детайли след малко.
Common Language Runtime
След като се запознахме накратко с архитектурата на .NET Framework, нека сега разгледаме в детайли и най-важният компонент от нея – CLR.
Common Language Runtime (CLR) е сърцето на .NET Framework. Той представлява среда за контролирано изпълнение на управляван код. На практика CLR е тази част от .NET Framework, която изпълнява компилираните .NET програми в специална изолирана среда.
В своята същност CLR представлява виртуална машина, която изпълнява инструкции, на езика IL (Intermediate Language), езикът до който се компилират всички .NET езици. CLR е нещо като виртуален компютър, който обаче не изпълнява асемблерен код за процесор Pentium, AMD или някакъв друг, а IL код.
Има голямо сходство между .NET CLR и Java Virtual Machine, но между двете технологии и много разлики. По предназначение те служат за едно също нещо – да изпълняват код за някакъв виртуален процесор. В .NET това е IL кода, а при Java платформата – т. нар. Java bytecode. Основната разлика между IL и bytecode е, че IL е език от по-високо ниво, а това позволява да бъде компилиран много по-ефективно от Java bytecode.
Отговорностите на CLR включват:
-
Изпълнение на IL кода. Реално IL инструкциите, преди да бъдат изпълнени за първи път, се компилират до инструкции за текущия процесор и след това се изпълняват от системния процесор. Този процес на междинно компилиране до машиннозависим (native) код се нарича JIT компилация (Just-In-Time compilation).
-
Управление на паметта и ресурсите на приложенията. CLR включва в себе си система за заделяне на памет и система за почистване на неизползваната памет и ресурси (т. нар. garbage collector). Управлението на паметта при .NET приложенията се извършва в голяма степен автоматизирано и в повечето случаи програмистът не трябва да се грижи за освобождаване на заделената памет. Ще разгледаме в детайли как .NET Framework управлява паметта в темата "Управление на паметта и ресурсите".
-
Осигуряване безопасността на типовете. .NET Framework е среда за контролирано изпълнение на програмен код (managed execution environment). Тя не позволява директен достъп до паметта, не позволява директна работа с указатели, не позволява преобразуване от един тип към друг, който не е съвместим с него, не позволява излизане от границите на масив, както и всякакви други опасни операции. По тази причина .NET се нарича управлявана среда – защото тя управлява изпълнението на кода и по този начин предпазва програмите от много досадни проблеми, които възникват при неуправляваните среди.
-
Управление на сигурността. NET Framework има добре изградена концепция за сигурност на различни нива. От една страна .NET приложенията могат да се изпълняват с различни права. Правата могат да се задават от администраторите чрез т. нар. политики за сигурност. CLR следи дали кодът, който се изпълнява, спазва зададената политика за сигурност и не позволява тя да бъде нарушена. Тази техника се нарича "code access security". От друга страна .NET Framework поддържа и средства за управление на сигурността, базирана на роли (role-based security). Ще разгледаме в детайли всички тези техники и средства в темата "Сигурност в .NET Framework".
-
Управление на изключенията. .NET Framework е изцяло обектно-ориентирана среда за разработка и изпълнение на програмен код. В нея механизмът на изключенията е залегнал като основно средство за управление на грешки и непредвидени ситуации. Една от задачите на CLR е да се грижи за изключенията, които възникват по време на изпълнение на кода. При настъпване на изключение CLR има грижата да намери съответния обработчик и да му предостави управлението. Ще разгледаме в детайли всичко това в темата "Управление на изключенията в .NET".
-
Управление на конкурентността. CLR контролира паралелното изпълнението на нишки (threads) като за целта си взаимодейства с операционната система. Повече за работата с нишки ще научим в темата "Многонишково програмиране и синхронизация".
-
Взаимодействие с неуправляван код. CLR осигурява връзка между управляван (.NET) код и неуправляван (Win32) код. За целта той изпълнява доста сложни задачи, свързани с конвертиране на данни, синхронизация, прехвърляне на извиквания, взаимодействие с компонентния модел на Windows (COM) и много други. Ще разгледаме в детайли тези проблеми в темата "Взаимодействие с неуправляван код".
-
Подпомагане процесите на дебъгване (debugging) и оптимизиране (profiling) на управлявания код. CLR осигурява инфраструктура и средства за реализацията на дебъгване и оптимизиране на кода от външни специализирани програми.
Управляван код
Управляваният код (managed code) е кодът, който се изпълнява от CLR. Той представлява поредица от IL инструкции, които се получават при компилацията на .NET езиците. По време на изпълнение управляваният код се компилира допълнително до машиннозависим код за текущата платформа и след това се изпълнява директно от процесора.
Управляван код и неуправляван код
Управляваният код (.NET код) се различава значително от неуправлявания код (например Win32 кода).
Управляваният код е машиннонезависим, т. е. може да работи на различни хардуерни архитектури, процесори и операционни системи, стига за тях да има имплементация на CLR.
Неуправляваният код е машиннозависим, компилиран за определена хардуерна архитектура и определен процесор. Например програмите, написани на езика C, се компилират до неуправляван код за определена архитектура.
Ако компилираме една C програма за Embedded Linux върху платформа StrongARM, ще получим неуправляван машиннозависим (native) код за Linux за тази платформа. Кодът ще съдържа инструкции за микропроцесор StrongARM и ще използва системни извиквания към операционната система Embedded Linux. Съответно на друга платформа няма да може да работи без прекомпилация на сорс кода на C програмата.
По същия начин, ако компилираме една C програма за Windows върху архитектура x86, ще получим неуправляван код за процесор x86 (примерно Pentium, Athlon и т.н.), който използва системни извиквания към Windows. Този код се нарича Win32 код и може да работи само върху 32-битова Windows операционна система. За да се стартира върху друга платформа, трябва да се компилира.
При управлявания код нещата стоят по различен начин. Ако компилираме една C# програма за платформа .NET Framework 1.1, ще получим управляван, машиннонезависим IL код, който може да работи върху различен хардуер. Кодът реално ще е компилиран за платформа CLR 1.1 и ще се състои от IL инструкции за виртуалния процесор на CLR и ще използва системни извиквания към .NET Base Class Library.
Управляваният код лесно може да бъде пренесен върху различни платформи без да се променя или прекомпилира. Така например програма на C#, която е компилирана под Windows до управляван IL код, може да се изпълнява без промени както върху Windows под .NET Framework, така и върху Linux под Mono, а също и върху мобилни устройства под Windows CE и .NET Compact Framework.
Метаданните в управлявания код
Управляваният код се самоописва чрез метаданни и носи в себе си описание на типове данни, класове, интерфейси, свойства, полета, методи, параметри на методите и други, както и описание на библиотеки с типове, описание на изисквания към сигурността при изпълнение и т. н. Това дава голяма гъвкавост на разработчика и възможност за динамично зареждане, изследване и изпълнение на функционалност, компилирана като управляван (IL) код.
Неуправляваният код стандартно не съдържа метаданни и това силно затруднява динамичното зареждане и изпълнение на неуправлявана функционалност.
Управляваният код е обектно-ориентиран
Управляваният код задължително е обектно-ориентиран, докато за неуправлявания няма такова изискване. Всички .NET езици са обектно-ориентирани. Всички .NET програми се компилират до класове и други типове от общата система от типове на .NET Framework. Всички данни, използвани от управлявания код, са наследници (в смисъла на обектно-ориентираното програмиране) на базовия тип System.Object. Ще разгледаме това в подробности в темата "Обща система от типове".
Управляваният код е високо надежден
Управляваният код е защитен от неправилна работа с паметта и типовете и това го прави по-сигурен и високо надежден. Управляваният код не може да извършва неправилен достъп до паметта, достъп до чужда памет и неправилна работа с типове. Това предпазва програмисти от много досадни проблеми, присъщи при писането на неуправляван код, като загуба на памет, достъп до неинициализирана памет, повторно освобождаване на памет, работа с невалиден указател и т.н.
Управляваният код интегрира различни езици
До управляван код се компилират всички .NET езици. Това дава възможност за широко взаимодействие между код, писан на различни езици за програмиране. Възможно е дори клас, написан на един .NET език, да бъде наследен и разширен от клас, написан на друг .NET език.
За .NET Framework няма значение на какъв език е бил написан кода преди да бъде компилиран. Всичкият код се компилира до IL и се изпълнява от CLR по еднакъв начин.
Управление на паметта
Управлението на паметта е една от важните задачи на CLR. Идеята за автоматизирано управление на паметта е залегнала в .NET Framework на дълбоко архитектурно ниво. Целта е да се улесни разработчика като се освободи от досадната задача сам да следи за освобождаването на заделената памет.
CLR, като средата за изпълнение, управлява заделянето на памет, инициализирането й, и автоматичното й освобождаването посредством garbage collector.
Динамично заделените обекти се разполагат в динамичната памет, в тъй наречения "managed heap". След като техния живот завърши и те вече не са необходими на приложението, системата за почистване на паметта (garbage collector) освобождава заеманата от тях памет автоматично. По този начин се избягват най-често срещаните проблеми като загуба на памет и достъп до освободена или неинициализирана памет. Повече за управлението на паметта в .NET Framework ще научим в темата "Управление на паметта и ресурсите".
Важна особеност при работата с управляван код е, че при него няма указатели. Вместо указатели се работи с референции, които са силно типизирани и се управляват автоматично. Референцията (reference) прилича на указател, но не е просто адрес в паметта, а има тип, т. е. тя е указател към определен тип данни и не може да сочи към място в паметта, където няма инстанция на този тип.
Intermediate Language (IL)
Междинният език Intermediate Language (IL), е език за програмиране от ниско ниво, подобен на асемблерните езици. За разлика от тях, обаче, IL е от много по-високо ниво, отколкото асемблерите за съвременните микропроцесори.
IL е обектно-ориентиран език. Той разполага с инструкции за заделяне на памет, за създаване на обект, за предизвикване и обработка на изключения, за извикване на виртуални методи и други инструкции, свързани с обектно-ориентираното програмиране.
Тъй като не е процесорно-специфичен, IL предоставя голяма гъвкавост и възможност за изпълнение на кода върху различни платформи чрез компилиране до съответния за платформата машинен език.
Възможна е и предварителна компилация до код за текущата платформа, но тази техника не носи голяма полза и рядко се използва.
Имплементацията на IL в .NET Framework се нарича MSIL (Microsoft Intermediate Language). IL може да има и други имплементации в други платформи и среди за изпълнение на .NET код.
Езикът IL е стандартизиран от организацията ECMA и в съответния стандарт се нарича CIL (Common Intermediate Language).
|
Често пъти термините IL и MSIL се използват като взаимозаменяеми и затова винаги трябва да имате предвид, че става въпрос за кода, който се изпълнява от CLR – машинният код, получен при компилацията на .NET езиците.
| Intermediate Language (IL) – пример
За да илюстрираме по-добре казаното до тук, нека разгледаме една проста програмка, написана на MSIL – класическият пример "Hello world!":
.method private hidebysig static void Main() cil managed
{
.entrypoint
// Code size 11 (0xb)
.maxstack 8
ldstr "Hello, world!"
call void [mscorlib]System.Console::WriteLine(string)
ret
} // end of method HelloWorld::Main
|
Всичко, което прави тази MSIL програма, е да изведе съобщението "Hello, world!" на конзолата. Тя дефинира един статичен метод без параметри с име Main, в който извиква с параметър "Hello, world!" статичния метод WriteLine() от класа System.Console, който отпечатва посочения текст.
Компилация и изпълнение
Вече споменахме няколко пъти междинния код IL и обяснихме, че .NET езиците (C#, VB.NET и т. н.) се компилират до него, а след това полученият код се изпълнява от CLR.
Сега ще разгледаме детайлно процеса на компилиране и изпълнение на .NET приложенията. Ще изясним как се извършва компилирането на програми от високо ниво, как се получава IL код, как този код се записва в специален файлов формат (асембли) и как след това компилираните асемблита се изпълняват от CLR като се компилират междувременно до машинен код от JIT компилатора.
Целият този процес е изобразен схематично на фигурата:
Изходният код на .NET програмите може да е написан на предпочитания от нас .NET език, например C#, VB.NET, Managed C++ или друг. За да го компилираме до IL управляван код, използваме компилатора за съответния език. В резултат получаваме асембли.
Асемблито представлява изпълним файл, съдържащ .NET управляван код и метаданни, които описват съдържанието на асемблито. Метаданните съдържат имената на класовете и типовете в асемблито, информация за членовете на класовете (методи, полета, свойства и други).
Едно асембли може да бъде изпълним файл (.exe файл) или динамична библиотека (.dll файл). Изпълнимите файлове съдържат допълнителна информация, която подпомага началното им стартиране (например входна точка на изпълнение).
При изпълнение на дадено асембли CLR го зарежда в паметта и анализира метаданните му. Извършват се различни проверки на кода – дали е коректен спрямо IL стандарта, дали има необходимите права за изпълнение и др.
След това управляваният IL код преминава през специфичния за текущата платформа JIT компилатор и се компилира до машинен код за текущия процесор. Компилираният вече код след това се изпълнява директно от процесора.
JIT компилаторът не компилира в началото цялото асембли, а само методът, от който започва изпълнението му. След това при опит за изпълнение на некомпилиран метод, той се компилира. Така кодът се компилира само при нужда и това осигурява добро бързодействие. Забавянето е незначително и скоростта на изпълнение на управлявания код на практика е почти еднаква със скоростта на изпълнение на неуправлявания код.
Предимството на JIT компилацията е, че може да оптимизира кода за текущата хардуерна платформа по най-добрия начин. Например ако е наличен най-мощният процесор на Intel или AMD и CLR поддържа този процесор, той ще компилира IL кода по начин, оптимизиран специално за него, и ще използва пълните му възможности. При неуправляваният код това не е възможно, защото кодът се компилира така, че да работи върху всички процесори, без да използва пълните възможности на текущата хардуерна платформа. По тази причина в някои случаи управляваният код може да е дори по-бърз от неуправлявания въпреки нуждата от JIT компилация, която отнема време.
Когато разполагаме с компилирано асембли и искаме да го изпълним, имаме право на избор кога да компилираме IL кода до машинен код. Това може да стане по време на изпълнение (посредством JIT компилатора) и предварително (с прекомпилация за текущата платформа).
Прекомпилацията на асемблита се извършва с инструмента ngen.exe, който е стандартна част от .NET Framework.
Архитектура на CLR
Общата среда за изпълнение CLR се състои от доста модули, всеки от които изпълнява конкретна задача. Схематично архитектурата можем да представим по следния начин:
Ще разгледаме всеки от посочените компоненти съвсем накратко, тъй като функциите им са от много ниско ниво и рядко ще ни се налага да взаимодействаме директно с тях:
-
Base Class Library Support – предоставя системни услуги, необходими за работата на Base Class Library (BCL).
-
Thread Support – предоставя услуги за манипулация на нишки в .NET приложенията – създаване на нишка, управление на състоянието на нишка, синхронизация и др.
-
COM Marshaler – грижи се за комуникацията с COM обекти. Осигурява извикването на COM сървъри от .NET код и извикването на .NET код от COM. Негова грижа са прехвърлянето на заявки, преобразуването на данни, управлението на жизнения цикъл на COM обектите и др.
-
Type Checker – осъществява проверка на типовете за съответствие при извикване и поддържа класовите йерархии.
-
Exception Manager – грижи се за управление на изключенията –предизвикване на изключение, прихващане, обработване и др.
-
Security Engine – отговаря за проверките на сигурността при изпълнение на кода.
-
Debug Engine – осигурява функционалност, свързана с дебъгването и оптимизирането на управляван код.
-
JIT Compiler – един от най-важните модули – по време на изпълнение компилира IL кода в специфичен за процесора код.
-
Code Manager – управлява изпълнението на кода.
-
Garbage Collector – управлява паметта автоматичното почистване на паметта и ресурсите. Контролира живота на обектите.
-
Class Loader – служи за зареждане на класове и типове. Използва се при началното изпълнение на приложението, както и при динамично зареждане на код по време на изпълнение.
Как CLR изпълнява IL кода?
Нека сега разгледаме по-подробно как CLR изпълнява IL кода. Изпълнението на кода, както можем да видим от схемата по-долу, е итеративен процес, който се състои от много стъпки.
При изпълнение на метод от едно асембли Class Loader подсистемата на CLR зарежда всички нужни за неговата работа класове и типове. В зависимост от това дали кодът е вече компилиран до машинен или не Class Loader предава кода за директно изпълнение или го компилира с JIT компилатора (при първо извикване на всеки метод).
Преди JIT компилацията се извършва процес, известен като верификация. Той проверява дали IL кодът е безопасен – дали не се опитва да осъществява директен достъп до паметта, дали не се опитва да заобикаля механизмите за сигурност и т. н. Ако системният администратор е определил кода за сигурен (trusted) неговото верифициране може да бъде се прескочено.
JIT компилаторът създава специфичен за машината код (native код), който се изпълнява директно от процесора. Този машинен код съдържа в себе си много допълнителни инструкции, чрез които си взаимодейства със CLR. Целта е кодът да се изпълнява по контролиран начин, за да не нарушава принципите за сигурност и надеждност, но без да се забавя излишно заради всички допълнителни проверки.
При изпълнението на кода, при достъп до ресурси, при извикване на системни библиотеки и в много други случаи се извършват проверки на сигурността (чрез т. нар. security engine).
Ако трябва да бъде извикан некомпилиран метод, този метод се връща в JIT компилатора и така се затваря цикълът на компилация до машинен код. Като резултат от описания алгоритъм не се налага компилиране на един и същ метод повече от веднъж и освен това, ако някой метод не се извиква никъде в приложението, той въобще не се компилира от JIT компилатора.
Сподели с приятели: |