Платформа .NET решает многие проблемы, которые досаждали программистам в прошлом. К их числу относятся проблемы, связанные с развертыванием приложений, управлением версиями, утечкой памяти, а также проблемы безопасности. Платформа .NET позволяет разрабатывать мощные, независимые от языка программирования, настольные приложения и масштабируемые (расширяемые) Web-службы, построенные на базе новой мощной полнофункциональной библиотеки классов .NET Framework.
Представьте себе симфонический оркестр, в котором группам струнных смычковых и ударных инструментов предстоит исполнить свои партии, используя при этом разные варианты партитуры. В таком случае, чтобы исполнить даже простейшую музыкальную композицию, музыкантам пришлось бы приложить героические усилия. Этот пример достаточно хорошо иллюстрирует деятельность разработчиков Windows-приложений. В процессе работы перед разработчиком возникает целый ряд вопросов. Использовать ли в приложении классы библиотеки базовых классов Microsoft (Microsoft Foundation Classes — MFC)? На каком языке писать приложение, на Visual Basic или на C++? Какой интерфейс для работы с базами данных использовать в приложении: открытый интерфейс взаимодействия с базами данных (Open Database Connectivity Interface — ODBC) или интерфейс OLE для баз данных, OLEDB? Использовать в приложении интерфейс модели компонентных объектов Microsoft (Component Object Model — COM) или интерфейс прикладного программирования (API) в стиле языка С? Если выбор сделан в пользу интерфейса модели компонентных объектов Microsoft (COM), какой тогда интерфейс использовать: IDispatch, дуальный (двойственный) интерфейс или только интерфейс с виртуальной таблицей? Какая роль во всем этом отводится Internet? До тех пор пока не появилась платформа .NET, довольно часто проект приложения искажался используемыми в процессе его реализации технологиями, которыми в тот период времени владели разработчики. Или же разработчику приходилось изучать еще одну технологию, которой было суждено через пару лет быть вытесненной следующей.
Развертывание приложения может оказаться трудной и неприятной задачей. В процессе развертывания приложения должны быть сделаны соответствующие записи в системном реестре, который является достаточно хрупким, а его восстановление — нелегкий труд. К тому же не существует хорошей стратегии управления версиями компонентов. Новые версии приложений могут разрушить уже существующие программы и при этом остается лишь догадываться о том, что же собственно произошло. Чтобы избежать проблем, связанных с хранением сведений о конфигурации системы в системном реестре, в других технологиях для этой цели используются метабазы или сервер SQL Server.
Еще одной проблемой в Win32 является безопасность. Существующая модель безопасности тяжела для понимания. Еще более тяжело ее использовать на практике. Многие разработчики просто ее игнорируют. Разработчики, которые были вынуждены использовать существующую систему безопасности, пытались в этой трудной модели программирования делать все от них зависящее. Возрастающее значение безопасности, связанное с развитием Internet, грозит изменить плохую ситуацию на потенциальный кошмар.
Даже там, где компания Microsoft попыталась облегчить процесс разработки приложений, проблемы все еще оставались. Многие системные службы приходилось разрабатывать с самого начала, по существу создавая инфраструктуру приложения, которая имела мало общего с бизнес-логикой. Гигантским шагом в сторону создания служб более высокого уровня стали сервер транзакций корпорации Microsoft (Microsoft Transaction Server, MTS) и COM+. Тем не менее, потребовалась еще одна парадигма разработки приложений. Модель компонентных объектов Microsoft (Component Object Model — COM) сделала возможным настоящее программирование с использованием компонентов. При этом приложение можно было создать достаточно просто с помощью языка Visual Basic. Но такие приложения не обладали достаточной гибкостью. Значительно более мощные приложения можно было создать с помощью языка C++, но при этом нужно было приложить значительные усилия. И это не говоря уже о том, что на языке C++ приходилось постоянно писать (постоянно воссоздавать) повторяющийся каркас (инфраструктуру) приложения. Если бы от этого всего можно было избавиться, скучать за ILJnknown я бы не стал.
Даже если бы платформа .NET смогла устранить все проблемы прошлого, этого все равно было бы недостаточно. Постоянный рост требований со стороны клиентов к функциональным возможностям приложений является одним из непреложных законов программирования.
Возможность беспрепятственной работы приложений в разных компьютерных сетях, обусловленная развитием Internet, стала императивом. Функциональные возможности компонентов должны быть доступны также и с других машин. При этом никто из программистов не хочет писать базовый каркас; все они жаждут писать приложения, предназначенные для непосредственного решения проблем своих клиентов.
Платформа .NET содержит общеязыковую среду выполнения (Common Language Runtime — CLR). Общеязыковая среда выполнения CLR поддерживает управляемое выполнение, которое характеризуется рядом преимуществ. Совместно с общей системой типов (Common Type System — CTS) общеязыковая среда выполнения CLR поддерживает возможность взаимодействия языков платформы .NET. Кроме того, платформа .NET предоставляет большую полнофункциональную библиотеку классов .NET Framework.
Чтобы решить все проблемы, связанные с разработкой Windows-приложений, платформа .NET должна обладать базовым набором служб, которые в любой момент доступны в любом языке программирования. Чтобы предоставить такие службы, платформа .NET должна иметь достаточно сведений о приложении.
Сериализация (преобразование в последовательную форму) объекта может послужить в качестве простого примера. Перед каждым программистом, рано или поздно, возникает проблема сохранения данных. Но зачем каждому программисту вновь изобретать колесо, решая вопрос о том, как следует сохранять вложенные объекты и сложные структуры данных? Зачем каждому программисту понимать, как эти объекты и данные хранятся в разных информационных хранилищах? Платформа .NET позволяет выполнить сериализацию объекта без вмешательства программиста. При желании разработчик может это сделать и самостоятельно.
Чтобы понять, как происходит сериализация объектов, мы рассмотрим относящийся к данной главе пример Serialize (Сериализация). Не станем акцентировать внимание на применяемых приемах программирования. Они будут рассмотрены позже. Сейчас же мы сосредоточимся на используемых в этом примере понятиях.
//Serialize.cs
>fusing
>fusing
>// <Система. Время выполнения.
>// Преобразование в последовательную форму. Форматеры. Soap.dll>
>using namespace System;
>// использование пространства имен Система;
>using namespace System::Collections;
>// использование пространства имен Система:: Коллекции;
>using namespace System::10;
>// использование пространства имен Система:: Ввод-вывод;
>using namespace
>System::Runtime:Serialization::Formatters::Soap; // использование пространства имен
>// Система:: Время выполнения:: Преобразование в последовательную // форму:: Форматеры:: Soap;
>[Serializable]
>// [Преобразование в последовательную форму]
>_gc class Customer
>// класс сборщика мусора Клиент
>{
>public:
>String *pname; // Строка long id; // идентификатор
>};
>_gc class Test
>// класс сборщика мусора Испытание
>{
>public:
>static void Main()
>{
>ArrayList *plist = new ArrayList;
>Customer *pcust = new Customer; // новый Клиент pcust->pname = "Charles Darwin";
// Чарльз Дарвин pcust->id = 10; // идентификатор plist->Add(pcust); // Добавить
>pcust = new Customer; // новый Клиент pcust->pname = "Isaac Newton";
// Исаак Ньютон pcust->id =20; // идентификатор plist->Add(pcust); // Добавить
>for (int i=0; i < plist->get_Count(); i++)
>{
>Customer *pcust = // Клиент
>dynamic_cast // <Клиент _ сборщик
>//мусора *> (plist->get_Item(i)); Console::WriteLine( "{0}: {!}",
>pcust->pname, _box(pcust->id)); // идентификатор } ~
>Console::WriteLine("Saving Customer List"); // ("Сохранение списка клиентов"); FileStream *ps = new FileStream(
>"cust.txt", FileMode::Create); // Создать SoapFormatter *pf = new SoapFormatter; pf->Serialize(ps, plist);
>// Преобразование в последовательную форму; ps->Close ();
>Console::WriteLine("Restoring to New List");
>// "Восстановить в новом списке");
>ps = new FileStream("cust.txt", FileMode::Open); // Открыть
>pf = new SoapFormatter();
>ArrayList *plist2 =
>dynamic_cast
>(pf->Deserialize(ps)); ps->Close();
>for (int i=0; i < plist->get_Count(); i++) {
>Customer *pcust = // Клиент
>dynamic_cast // <Клиент _ сборщик мусора *>
>(plist->get_Item(i)); Console::WriteLine( "{0}: {!}",
>pcust->pname, _box(pcust->id)); // идентификатор } } };
>void main(void) {
>Test::Main(); }
Мы определили класс Customer (Клиент) с двумя полями: pname и id (идентификатор). Сначала программа создает экземпляр коллекции, в котором будут храниться экземпляры класса Customer (Клиент). Мы добавляем в коллекцию два объекта Customer (Клиент), а затем распечатываем содержимое коллекции. Потом коллекция сохраняется на диске. Она восстанавливается в новый экземпляр коллекции и выводится на печать. Распечатанные теперь данные будут идентичны данным, которые были распечатаны перед сохранением коллекции [В результате инсталляции примеров программ, которыми сопровождается данная книга, должен быть создан пример, готовый к выполнению. Если он отсутствует, щелкните два раза на том файле решения Visual Studio.NET, который имеет расширение .sin. Когда откроется Visual Studio, нажмите комбинацию клавиш CtrI-F5 для того чтобы построить и выполнить пример.]. Если вы запустите приложение и откроете получившийся в результате файл cust. txt, вы увидите, что он содержит данные в необычном XML-формате, который известен как простой протокол доступа к объектам (Simple Object Access Protocol -r- SOAP). Этот протокол специально разработан для хранения и передачи объектов.
Мы не писали код для того, чтобы указать, как сохраняются или восстанавливаются поля объекта Customer (Клиент). Но мы определили формат (SOAP) и создали среду, в которой затем были сохранены данные. Классы библиотеки .NET Framework сгруппированы таким образом, что каждый выбор — среды, формата и способа загрузки (восстановления) или сохранения объекта — можно сделать независимо друг от друга. Такого типа разделение классов существует в библиотеке .NET Framework повсеместно.
Класс Customer (Клиент) имеет атрибут Serializable (Преобразуемый в последовательную форму, упорядочиваемый). Аналогично поле имени имеет атрибут public (общедоступный). Когда вы не хотите, чтобы объект можно было преобразовывать в последовательную форму, не приписывайте ему соответствующий атрибут. Если будет предпринята попытка сохранения объекта, который не имеет атрибута Serializable (Преобразуемый в последовательную форму, упорядочиваемый), возникнет исключительная ситуация и произойдет отказ в работе программы [Выделите в программе атрибут Serializable (Преобразуемый в последовательную форму, упорядочиваемый) как комментарий и посмотрите, что при этом произойдет. Для того чтобы ввести комментарий в программу, вы можете использовать синтаксис языка С и C++, то есть применять пары символов /* и */ в качестве открывающей и закрывающей цепочек комментария.].
При программировании на платформе .NET атрибуты можно применять повсеместно. Использование атрибутов позволяет описать способ обработки кода и данных библиотекой классов .NET Framework. При помощи атрибутов можно также указать используемую модель безопасности. Атрибуты можно использовать для того, чтобы организовать с помощью каркаса синхронизацию многопоточной обработки. Благодаря использованию атрибутов становится очевидной идея удаленного размещения объектов.
Чтобы указать, что объект может сохраняться и восстанавливаться библиотекой .NET Framework, компилятор добавляет атрибут Serializable (Преобразуемый в последовательную форму, упорядочиваемый) к.метаданным класса Customer (Клиент). Метаданные представляют собой дополнительную информацию о программном коде и данных, которая содержится в самом .NET-приложении. Метаданные, являющиеся характерным свойством общеязыковой среды выполнения CLR, могут содержать также и другую информацию о коде, включая:
- номер версии и информацию о местной специфике (регион, язык);
- все используемые типы;
- подробности о каждом типе, включая его имя. область видимости и т.д.;
- подробную информацию о членах каждого типа, в частности, используемые ими методы, сигнатуры методов, и т.д.;
- атрибуты.
Метаданные хранятся вместе с программным кодом, а не в каком-то центральном хранилище наподобие системного реестра в операционной системе Windows. Способ их хранения не зависит от используемого языка программирования. Благодаря всему этому .NET-приложения содержат самоописания. Во время выполнения приложения может быть выдан запрос метаданных с целью получения информации о коде (например, о наличии или отсутствии атрибута Serializacle (Преобразуемый в последовательную форму, упорядочиваемый)). Вы можете расширить метаданные, дополнив их своими собственными атрибутами.
В нашем примере библиотека .NET Framework может запросить метаданные для того, чтобы получить информацию о структуре объекта Customer (Клиент), которая затем используется для сохранения и восстановления объекта.
В предыдущем примере Serialize (Сериализация) используются классы SoapFor-matrer и FileStream. Они являются лишь двумя из более чем 2500 классов библиотеки .NET Framework. Классы библиотеки .NET Framework создают каркас (инфраструктуру) приложения и предоставляют системные службы .NET-приложениям. Ниже перечислены лишь некоторые из функциональных возможностей библиотеки классов .NET Framework:
- библиотека базовых классов. — содержит основные функциональные возмож ности, такие как строки, массивы и элементы форматирования;
- передача данных посети;
- система безопасности;
- удаленная обработка;
- диагностика;
- ввод/вывод;
- базы данных;
- язык ХМ L;
- Web-службы, которые позволяют использовать интерфейсы компонентов в любом месте Internet;
- Web-программирование;
- пользовательский интерфейс операционной системы Windows.
Предположим, что вы хотите зашифровать ваши данные, и, следовательно, не желаете полагаться на сериализацию (преобразование в последовательную форму), реализованную на основе простого протокола доступа к объектам (Simple Object Access Protocol — SOAP), входящего в состав библиотеки .NET Framework. Ваш класс может наследовать интерфейс ISeriaiizaole и содержать реализацию соответствующего алгоритма (как это сделать, мы обсудим в следующих главах). Тогда при сохранении и восстановлении данных библиотека .NET Framework будет использовать ваши методы.
Но как библиотека .NET Framework узнает о том. что вы реализовали интерфейс 15-erializable? Оказывается, она может запросить метаданные соответствующего класса для того, чтобы узнать, наследчет ли он указанный интерфейс! Затем при сериадизации объекта или преобразовании его из последовательной формы в "параллельную" библиотека классов .NET Framework может использовать либо ее собственный алгоритм, либо код соответствующего класса.
Программирование на основе интерфейсов используется платформой .NET для того, чтобы при помощи разработанных программистом объектов дополнить стандартные функционачьные возможности библиотеки классов .NET Framework. Использование интерфейсов позволяет также привести работу с разными объектами к общему знаменателю, не зная точного типа объекта. Например, средства форматирования (скажем, форматер SOAP, который используется в данном примере) наследуют интерфейс I Formatter. Программы могут быть написаны безотносительно к какому бы то ни было конкретному (двоичному, SOAP) форматеру, используемому сейчас, или форматеру, который будет использоваться в будущем, и при этом они будут работать должным образом.
Если тип содержит метаданные, тогда среда выполнения может делать многие замечательные вещи. Но все ли объекты в .NET содержат метаданные? Да! Каждый тип, будь то тип, определенный пользователем (например, Customer (Клиент)) или тип, являющийся частью библиотеки классов .NET Framework (например, FileStream). является объектом среды .NET. Все объекты среды .NET являются производными от одного базового класса— системного класса Object (Объект). Поэтому все. что выполняется в среде .NET, имеет тип и, следовательно, содержит метаданные.
Типы
Типы — сердце модели программирования, основанной на общеязыковой среде выполнения CLR. Тип аналогичен классу в большинстве объектно-ориентированных языков программирования и сочетает в себе используемую абстракцию данных и их поведение. Тип в общеязыковой среде выполнения CLR содержит:
>поля (элементы данных);
методы;
свойства;
события.
Имеются также встроенные простые типы данных. Например, целочисленный тип данных, тип чисел с плавающей точкой, строки, и так далее.
В нашем примере код преобразования объектов в последовательную форму может просматривать список (типа ArrayList) объектов Customer (Клиент), и сохранять каждый объект, а также весь массив, к которому принадлежит объект. Это возможно благодаря тому, что метаданные содержат информацию как о типе объекта, так и о его размещении.
Из дальнейшего станет ясно, что благодаря тому, что все .NET-объекты являются производными от общего базового класса, открываются и некоторые другие возможности.
Типы, передаваемые библиотеке классов .NET Framework, имеют некоторую общую природу. Эти типы определяются обшей системой типов (Common Type System — CTS). Общая система типов CTS определяет правила для типов и действий, которые поддерживает среда выполнения CLR. Именно общая система типов CTS накладывает на классы .NET ограничение единичного наследования реализации. Хотя общая система типов CTS определена для широкого множества языков программирования, не все эти языки должны поддерживать все свойства типов данных, предусмотренные в общей системе типов CTS. Например, в языке C++ множественное наследование разрешено для неуправляемых классов, но запрещено для управляемых.
Промежуточный язык Microsoft (Microsoft Intermediate Language — MSIL, или просто IL) определяет систему команд, которая используется всеми компиляторами, транслирующими на язык платформы .NET. Этот промежуточный язык не зависит от используемой платформы. Код на языке MSIL затем преобразуется во внутренний (собственный) код платформы. Мы можем быть уверены, что классы библиотеки .NET Framework будут работать со всеми языками, поддерживаемыми платформой .NET. Особенности создаваемого приложения больше не накладывают ограничений на выбор языка программирования, а выбор языка программирования больше не ограничивает возможности создаваемого приложения.
Промежуточный язык MSIL и общая система типов CTS позволяют многим языкам программирования, компиляторы для которых могут генерировать код на языке MS1L, использовать библиотеку классов .NET Framework. Именно в этом состоит одно из наиболее заметных различий между платформами .NET и Java, которые в значительной степени используют одну и ту же философию.
Дисассемблер промежуточного языка Microsoft ILDASM (Microsoft Intermediate Language Disassembler) может отображать метаданные и инструкции языка MSIL, связанные с соответствующим .NET-кодом. Дисассемблер ILDASM является очень полезной утилитой, которая используется при отладке приложений. Он позволяет более глубоко понять инфраструктуру платформы .NET. Кроме того, дисассемблер промежуточного языка Microsoft ILDASM можно использовать для изучения кода библиотеки классов .NET Framework [Дисассемблер ILDASM можно найти в меню Tools (Сервис) Visual Studio.NET. Он находится также в подкаталоге Microsoft.NET\FrameworkSDK\Bm. Дисассемблер можно активизировать, щелкнув два раза на его названии в окне Проводника (Explorer) или с помощью командной строки. Если вы активизируете дисассемблер ILDASM с помощью командной строки (или из среды VS.NET), то используйте ключ /ADV для получения доступа к некоторым его дополнительным возможностям.]. На рис. 2.1 приведен фрагмент кода на языке MSIL, взятый из примера Serialize (Сериализация). В данном фрагменте описывается создание двух новых объектов Customer (Клиент) и их добавление в список [Откройте пример Senahze.exe и щелкните на знаке плюс (+) рядом с пунктом Test (Тестирование). Щелкните два раза на элементе Main (Главная), чтобы инициировать в MSIL главную процедуру.].
Инструкция newobj создает новую объектную ссылку, используя параметр конструктора [Формально он не является параметром. В промежуточном языке IL используется стек; конструктор представляет собой лексему метаданных, записанную в стек.]. Инструкция stloc сохраняет значение в локальной переменной. Инструкция Idloc загружает значение локальной переменной [Подробно промежуточный язык Microsoft MSIL описан в документах Европейской Ассоциации производителей ЭВМ (European Computer Manufacturers' Association — ЕСМА). Особенно рекомендуется изучить раздел "Partition III- CIL Instruction Set", посвященный системе команд.]. Настоятельно рекомендуем вам поэкспериментировать с дисассемблером ILDASM и изучить его возможности.
Рис. 2.1. Фрагмент кода из примера Serialize (Сериализация)
Так как компиляторы всех языков программирования транслируют на один общий промежуточный язык и используют общую библиотеку базовых классов (Base Class Library), то открывается возможность взаимодействия поддерживаемых языков. Иными словами, поддерживаемые языки могут в определенных пределах рассматриваться как функционально совместимые. Но поскольку все части общей системы типов CTS реализованы не во всех языках, не удивительно, что один язык может обладать свойствами, которые отсутствуют в другом.
Спецификация общего языка (Common Language Specification — CLS) определяет подмножество общей системы типов CTS, содержащее основные функциональные возможности, которые должны быть реализованы во всех .NET-языках для того, чтобы они могли взаимодействовать друг с другом. Именно согласно этой спецификации класс, написанный на Visual Basic.NET, может быть производным от класса, написанного на управляемом C++ или С#. Следствием использования спецификации общего языка CLS является возможность межъязыковой отладки. Примером соблюдения правил спецификации общего языка CLS является то, что вызовы методов могут не поддерживать переменное число аргументов, хотя такая конструкция и может быть выражена в языке MSIL.
Требование совместимости со спецификацией общего языка CLS предъявляется только к общедоступным свойствам. Например, класс может содержать приватный член, который не совместим со спецификацией общего языка CLS, и при этом являться базовым классом для класса, реализованного на другом языке, поддерживаемом платформой .NET. Например, общедоступные (public) и защищенные (protected) имена классов, код которых написан на языке C++ и С#, не должны отличаться только регистром используемых символов, поскольку в таких языках как VB.NET регистр клавиатуры (регистр прописных и строчных букв) не учитывается. Но имена приватных (private) полей могут различаться именно регистром клавиатуры (т.е. прописными и строчными буквами).
Компанией Microsoft предлагается несколько языков программирования, совместимых со спецификацией общего языка CLS: C#, Visual Basic.NET, и C++ с управляемыми расширениями. Независимые разработчики предлагают и другие языки программирования (их уже больше дюжины). Компания ActiveState занимается реализацией языков Perl и Python. Компания Fujitsu реализует язык COBOL.
В примере Serialize (Сериализация) второй экземпляр объекта Customer (Клиент) был присвоен той же переменной, которой раньше был присвоен первый экземпляр. При этом для удаления первого экземпляра класса из памяти деструктор не вызывался. В данном примере ни одна из выделенных областей памяти никогда не освобождалась. Для освобождения памяти от объектов, которые являются экземплярами классов, объявленных с помощью ключевого слова _дс (сборщик мусора), платформа .NET использует автоматическую сборку мусора. Если память, выделенная в управляемой динамически распределяемой области памяти, становится висячей или выходит из области видимости, то она заносится в список участков памяти, подлежащих освобождению. Периодически система инициирует процесс сборки мусора. Освободившаяся в результате этого память возвращается в динамически распределяемую область памяти.
Благодаря автоматическому управлению памятью утечка памяти в системе исключается. Утечка памяти является одной из самых распространенных ошибок при программировании на языках С и C++. В большинстве случаев за счет использования автоматической сборки мусора распределение памяти в динамически распределяемой области памяти происходит значительно быстрее по сравнению с классическими схемами. Обратите внимание, что такие переменные KaKpcust и plist являются управляемыми указателями на объекты, а не самостоятельными объектами. Именно вследствие этого и возможна сборка мусора.
Сборка мусора — одна из нескольких служб, предоставляемых общеязыковой средой выполнения CLR программам, выполняющимся на платформе .NET [Формально метаданные, общая система типов CTS (Common Type System), спецификация общего языка CLS и виртуальная система выполнения (Virtual Execution System— VES) также являются частью общеязыковой среды выполнения CLR. Прилагательное "общеязыковая" означает, что среда выполнения является общей для всех языков. Система виртуального выполнения VES загружает и выполняет .NET-программы и поддерживает динамическое связывание. Обратитесь к документам, описывающим общеязыковую инфраструктуру (Common Language Infrastructure — CLI), а именно, к разделу "Partition I: Concepts and Architecture", посвященному концепциям и архитектуре. Эти документы переданы для дальнейшего рассмотрения Европейской Ассоциации производителей ЭВМ (European Computer Manufacturers' Association — ЕСМА). Упомянутые документы загружаются вместе с набором инструментальных средств разработки программного обеспечения .NET Framework SDK.].
Данные, участвующие в процессе сборки мусора, инициируемом общеязыковой средой выполнения CLR, называются управляемыми данными. Управляемый код— это код, который способен использовать службы, предоставляемые общеязыковой средой выполнения CLR. .NET-компиляторы, которые генерируют код на языке MSIL, могут генерировать управляемый код.
Управляемый код не удовлетворяет требованиям типовой безопасности автоматически. Язык C++ может послужить тому классическим примером. Чтобы класс участвовал в сборке мусора (т.е. был управляемым), при его объявлении следует использовать атрибут _дс (сборщик мусора). Компилятор для C++ запрещает в таких классах использовать арифметические операции над указателями. Тем не менее, код на C++ не может быть надежно проверен на типовую безопасность, поскольку используются библиотеки на С и C++.
Проверка кода на типовую безопасность происходит перед его компиляцией. Такая проверка является необязательной и может быть пропущена, если код аттестован. Одно из самых существенных отличий проверяемого кода от непроверяемого состоит в том, что в проверяемом коде не могут использоваться указатели. Код, в котором используются указатели, может разрушить общую систему типов CTS и в результате его трансляции может получиться код, не удовлетворяющий типовой безопасности.
Код, который удовлетворяет типовой безопасности, не может быть легко разрушен. Например, перезапись буфера не может повредить другие структуры данных или программы. К коду, удовлетворяющему требованиям типовой безопасности, можно применить политику безопасности [Более подробно этот вопрос обсуждается в главе 13 "Зашита".]. Например, можно разрешить или запретить доступ к некоторым файлам, или элементам пользовательского интерфейса. Вы можете предотвратить выполнение кода, полученного из неизвестных вам источников. Чтобы предотвратить разрушение системы безопасности платформы .NET, вы можете запретить доступ к неуправляемому коду. Благодаря типовой безопасности можно также изолировать выполняемые ветви кода .NET друг от друга [Области приложений (Application Domains) обсуждаются в главе 8 "Классы каркаса .NET Framework".].
Еще одной функцией общеязыковой среды выполнения CLR является загрузка и запуск .NET-программ. .NET-программы разворачиваются в виде одной или нескольких сборок. Сборкой является один или несколько исполняемых файлов или файлов динамически подключаемых библиотек (DLL) вместе со связанными с ними метаданными. Метаданные, которые описывают всю сборку целиком, хранятся в декларации (манифесте) сборки. Декларация сборки, или манифест сборки содержит, например, список сборок, от которых зависит данная сборка.
В нашем примере Serialize (Сериализация) сборка содержит всего лишь один файл, Serialize . ехе. Этот файл включает и метаданные, и код. Поскольку декларация хранится в самой сборке, а не в отдельном файле (как это имеет место для библиотеки типов или системного реестра), она всегда синхронизирована с этой сборкой. На рис. 2.2 представлены метаданные декларации сборки, соответствующей данному примеру [В дисассемблере откройте файл Serialize . ехе и затем щелкните дважды на элементе MANIFEST.]. Обратите внимание на операторы assembly extern. Они указывают, что данная сборка зависит от сборок библиотеки .NET Framework, которые имеют название mscorlib и System.Runtime.Serialization.Formatters.SOAP (Система.Время выполнения.Преобразование в последовательную форму.Форматеры.ЗОАР). С помощью оператора assembly extern указываются также версии сборок, от которых зависит наш пример Serialize.exe.
Могут существовать несколько версий одной сборки, причем номер версии является частью имени сборки. Если необходимо использовать уникальное имя сборки, вы можете использовать открытый/индивидуальный ключ шифрования, для того чтобы сгенерировать уникальное (строгое) имя.
Сборки могут быть развернуты приватно (privately) либо публично (publicly). В случае приватного (privately) развертывания, все необходимые приложению сборки копируются в тот же каталог, в котором находится само приложение. Как мы уже знаем, версия сборки является частью ее имени. Следовательно, несколько версий сборки могут быть развернуты в одном или в разных каталогах и при этом мешать друг другу они не будут. "Проклятие (ад) динамически подключаемых библиотек (DLL)" больше не действует.
Если сборка должна быть общедоступна для совместного использования, в глобальном кэше сборок (Global Assembly Cache — GAC) делается соответствующая запись, позволяющая другим сборкам отыскать ее. Для сборок, при разворачивании которых применяется глобальный кэш сборок GAC, необходимо использовать строгие имена [Более детально это обсуждается в главе 7 "Сборки и развертывание".]. Возможность развертывания сборок, а также функциональная совместимость языков программирования позволяют создавать компоненты почти автоматически.
Рис. 2.2. Декларации сборки Serialize (Сериализация)
Перед выполнением на конкретной машине, код на промежуточном языке Microsoft— MS1L (Microsoft Intermediate Language) транслируется оперативным компилятором, или ЛТ-компилятором (JIT — "just-in-time" или "как раз вовремя") в собственный (внутренний) код. Во время работы программы некоторые участки кода выполняться никогда не будут. Следовательно, более эффективной может оказаться трансляция кода из промежуточного языка MSIL в собственный (внутренний) код, осуществляемая по мере необходимости в процессе выполнения приложения. Собственный (внутренний) код при этом сохраняется с целью повторного его использования.
После загрузки типа, к каждому его методу загрузчик присоединяет заглушку. При первом вызове заглушка передает управление ЛТ-компилятору, который генерирует собственный (внутренний) код и сохраняет адрес оттранслированного собственного (внутреннего) кода в заглушке. При последующих вызовах метода управление передается непосредственно собственному (внутреннему) коду.
Возможно, вам понравилась предложенная модель безопасности и простота использования управляемого кода, но вас волнует вопрос о производительности приложений. В то время, когда появились высокоуровневые языки программирования, программистов, пишущих приложения на самых примитивных языках ассемблера, волновал этот же вопрос.
Общеязыковая среда выполнения CLR разрабатывалась с прицелом на высокую производительность приложений. Ведь только при первом вызове метода общеязыковая среда выполнения CLR осуществляет проверку правильности и затем выполняет оперативную компиляцию в собственный (внутренний) код, в который встроены средства безопасности, например проверка границ массивов. Когда метод вызывается в следующий раз, непосредственно выполняется собственный (внутренний) код. Схема управления памятью спроектирована так, чтобы достичь высокой производительности. Распределение памяти происходит почти мгновенно, при этом из управляемой динамически распределяемой области памяти берется следующий доступный участок. Освобождение памяти выполняется сборщиком мусора, который реализован на основе эффективного алгоритма.
За все это вы расплачиваетесь тогда, когда при проверке безопасности приходится просматривать стек.
При создании Web-страниц используется компилируемый, а не интерпретируемый код. В результате, ASP.NET-приложения работают намного быстрее, чем ASP-приложения.
В 95 процентах кода надежность и легкость его создания с лихвой компенсирует незначительные потери в производительности. При создании высокопроизводительных серверных приложений также могут использоваться такие технологии как библиотека ATL Server или неуправляемый C++.
Платформа .NET решает многие проблемы, которые в прошлом омрачали процесс разработки Windows-приложений. Теперь существует одна для всех поддерживаемых платформой языков программирования парадигма разработки приложений. Нет больше противоречий между ожидаемыми возможностями приложения и возможностями языка программирования, на котором реализуется приложение. Развертывание приложений стало более рациональным и включает совершенную стратегию управления версиями. Метаданные, система безопасности на основе атрибутов, проверка правильности кода и автономность сборок, удовлетворяющих требованиям типовой безопасности, делают разработку безопасных приложений значительно более легкой. Детальнее это будет обсуждаться дальше. Кроме того, платформа .NET обеспечивает создание каркаса (инфраструктуры) для базовых системных служб. Но в случае необходимости вы можете расширить или даже заменить его.
Общеязыковая среда выполнения CLR является прочным базисом, который служит отправной точкой при разработке приложений будущего. Общеязыковая среда выполнения CLR представляет собой основу. Ее элементами являются общая система типов CTS, метаданные, спецификация общего языка CLS, а также система виртуального выполнения (Virtual Execution System— VES), которая выполняет управляемый код12. В следующих главах мы увидим, что платформа .NET облегчает разработку как приложений, предназначенных для поставщиков услуг, так и для клиентских решений. Использование унифицированной платформы .NET позволяет компании Microsoft, а также независимым разработчикам более легко реализовывать нужные расширения.
Все это стало возможным благодаря творческому объединению старых технологий — промежуточного языка, проверки типовой безопасности, и, конечно же, метаданных — в общеязыковой среде выполнения CLR. Изучая различные свойства платформы .NET, вы увидите, что метаданные таятся повсюду.
Более подробно затронутые вопросы будут обсуждаться в данной книге в ходе дальнейшего изложения материала. В следующей главе рассматриваются управляемые расширения языка C++.