Программирование для Silverlight с помощью CoreCLR
ОГЛАВЛЕНИЕ
За прошедший десяток (или около того) лет возникло много различных технологий веб-программирования от CSS до вариантов ECMAScript. Большинство из них относятся только к веб-программированию – навыки, приобретенные при программировании CSS, неприменимы в других областях. В противоположность им, Silverlight 2 позволяет применять напрямую к приложениям веб-клиента навыки для .NET Framework, используемые при программировании для настольных систем, например библиотеки базовых классов, XAML и C#. Вдобавок, это позволяет избавиться от необходимости создавать отдельную среду разработки CoreCLR: можно просто использовать Visual Studio для проектирования, разработки, отладки и профилирования кода на C# или Visual Basic, точно так же, как в случае с настольным приложением. Мы создали CoreCLR в Silverlight 2 именно для того, чтобы сделать веб-программирование столь же богатым возможностями, как и настольное программирование.
Хотя для разработчиков удобно иметь среду программирования с богатыми возможностями, пользователи не желают загружать большие надстройки для обозревателя. Чтобы Silverlight пользовался успехом у пользователей, мы должны были сделать установку быстрой. Нам удалось сократить бета-версию 1 до 4,3 МБ – примерно 6-10 секунд установки по широкополосному подключению. Это выдающееся достижение, если учесть, что каждая из двух крупных частей CLR .NET Framework 2.0 (mscorwks.dll и mscorlib.dll) не уступает по размерам coreclr.dll и mscorlib.dll от Silverlight 2 вместе взятым.
Внутри ядра CoreCLR
Разработка CoreCLR началась сразу после выпуска CLR версии 2.0 в октябре 2005. Двумя основными целями разработчиков были размер и совместимость: с точки зрения программиста, написание кода для CLR всегда должно быть одинаковым, тогда как с точки зрения пользователя загружаемый файл должен быть очень маленьким. Поскольку Silverlight изначально предназначен для иного набора ситуаций, чем настольный CLR, у нас имелась возможность внести некоторые изменения, упростившие CoreCLR и позволившие нам уменьшить размер установки Silverlight. Но единообразие внизу стека имеет ключевое значение. Различия в поведении (даже если они верны) проявляют себя как ошибки выше в стеке.
Чтобы гарантировать совместимость, мы использовали одинаковый код для компонентов внизу стека. Механизм исполнения и виртуальная машина идентичны. В них входят система типов и метаданные, сборщик мусора (garbage collector – GC), динамический компилятор и пул потоков, а также прочие ключевые части механизма среды выполнения.
Однако для соответствия случаю веб-приложения были внесены некоторые изменения. Например, поскольку расширенные приложения Интернета обычно являются простыми и короткоживущими, динамический компилятор сосредотачивается на уменьшении времени запуска, а не на выполнении более сложных оптимизаций. Аналогично, серверный режим сбора мусора, настроенный под несколько рабочих потоков, использующих похожие шаблоны выделения, не имеет смысла для размещенных в сети приложений. Так что в Silverlight включает лишь стандартный сборщик мусора рабочей станции, который настроен под интерактивные приложения. Но язык MSIL и метаданные, используемые в приложениях Silverlight, идентичны используемым в управляемых приложениях для рабочей среды; поведение приложения в рабочей среде и в обозревателе будет единообразным.
Тот факт, что Silverlight не предназначен для замены настольной CLR, вызвал крупнейшее изменение в его базовом механизме: CoreCLR будет работать в процессе параллельно с настольной CLR. В прошлом у нас никогда не было возможности выполнять две версии CLR изнутри одного процесса. Это непростая проблема по ряду причин. Одна из них – управление состоянием для всего процесса: каждый экземпляр CLR предполагает, что является единственным в процессе и, следовательно, единственным, касающимся его статических данных. Если переменная staticFoo существует в версиях CLR 1.1 и 2.0, и обе версии CLR загружаются в один и тот же процесс одновременно, ни одна из версий не может записывать в переменную staticFoo, не изменяя при этот состояние другой CLR.
Хотя состояние всего процесса является наиболее очевидной проблемой, параллельное выполнение двух CLR в одном процессе может породить и другие. Например, если два сборщика мусора работают разом, как удержать один сборщик мусора от приостановки потока второго сборщика мусора? Вдобавок, существует проблема с местом: при загрузке нескольких CLR в процесс каждой из них приходится загружать код, который может быть для них общим и каждая из них получает свое место для статических переменных и управляемых куч.
Существует несколько важных случаев, в которых требуется возможность размещать CoreCLR параллельно с настольной рабочей средой. Если CoreCLR и настольная CLR не могут работать рядом друг с другом, то невозможно написать настольную форму Windows Forms или приложение WPF, размещающее элемент управления веб-обозревателя, который может перейти к веб-странице, использующей Silverlight. Чтобы обойти эту потенциальную проблему, мы могли бы просто заставить Silverlight полагаться на CLR, установленную на компьютере Windows разработчика: в каждой установке Windows XP с пакетом обновления 2 или Windows Vista имеется достаточно свежая CLR, установленная вместе с системой. Но работа всего кода Silverlight на CoreCLR гарантирует абсолютную совместимость вне зависимости от того, какая CLR установлена на компьютере (или, в случае Mac OS X, есть ли CLR на компьютере вообще!) Так что мы проделали работу по обеспечению параллельной работы CoreCLR в процессе с настольной CLR, и мы думаем, что Silverlight сможет предоставить пользователям гораздо большее удобство благодаря нашим усилиям.
Модель безопасности CoreCLR
Другое большое изменение в ядре относится к новой модели безопасности. Обратите внимание, что разработчики .NET традиционно использовали разграничение доступа из кода (Code Access Security – CAS) для предотвращения выполнения привилегированных операций не пользующимся доверием кодом. CAS обладает большими возможностями, но довольно замысловато. Оно позволяет пользователю или администратору определять различные «песочницы» для кода, используя наборы разрешений, и затем сопоставлять индивидуальные сборки с этими «песочницами». Для приложений Silverlight нам нужна лишь одна «песочница», эквивалентная той, что Internet Explorer использует для выполнения сценариев на веб-странице. Этот упрощенный случай позволил нам целиком удалить политику CAS.
Мы также упростили модель обеспечения безопасности. Новая модель основана на прозрачности безопасности – концепции, появившейся в CLR версии 2.0. В центре модели прозрачности лежит разделение кода на три категории: Transparent («Прозрачный»), SafeCritical («Безопасный ключевой») и Critical («Ключевой»). Transparent, низший уровень доверия для кода, не может повышать уровень прав, а также получать доступ к конфиденциальным ресурсам и информации на компьютере. В Silverlight 2 весь код приложений имеет уровень Transparent. Код уровня Critical, наивысшего уровня доверия, может взаимодействовать с системой через P/Invoke или даже содержать непроверяемый код. Для Silverlight 2 весь код уровня Critical должен быть частью платформы Silverlight. Тогда код SafeCritical действует как мост, позволяющий коду уровня Transparent получать доступ к системным ресурсам путем вызова кода Critical. Считайте коде уровня Critical интерфейсом API ядра Windows, код уровня Transparent кодом приложений пользователя и коде SafeCritical интерфейсом API между пользовательским кодом и кодом ядра.
Код уровня Transparent может вызывать только код уровней Transparent и SafeCritical. Код SafeCritical может затем вызвать код уровня Critical от имени пользовательского кода. Обязанностью кода уровня SafeCritical является «канонизация», или помещение в стандартный формат, ввода и «дезинфекция» вывода кода уровня Critical для защиты безопасности системы (см. рис. 1).
Необходимость канонизации ввода в код уровня Critical чуть более самоочевидна, чем необходимость дезинфекции вывода. Например, если моему веб-приложению необходимо записать файл на локальный диск, оно может сделать это используя изолированное хранилище. Однако весьма желательно, чтобы мое приложение не требовало записи в файл, именуемый "..\..\..\..\bootmgr", так что важно убедиться, что ввод находится в регулярном, каноническом формате. Мысль о том, что вывод кода уровня Critical является угрозой безопасности, несколько более необычна. Ключевая концепция безопасности состоит в том, что контроль над раскрытием информации очень важен при уменьшении общей области, открытой для атак. Предположим, я попытаюсь получить доступ к какому-то элементу пользовательской информации на вашей системе и получу ответ «в разрешении отказано». Когда я повторю ту же операцию доступа, но для другого пользователя, я получу ответ «пользователь Боб не существует». Если я знаю, что выдаются оба ответа, я могу повторять неверные попытки доступа, чтобы собрать список имен пользователей системы.
Упрощенная политика безопасности является явным выигрышем для разработчиков, работающих в коде .NET, но она также помогает разработчикам, работающим над кодом .NET. Мы постарались отмечать код как Critical и SafeCritical, только когда это необходимо. Пребывание большинства кода на уровне Transparent помогает нам уменьшить объем кода, которому необходимо внимательное рассмотрение с точки зрения безопасности. Нам все еще приходится просматривать наш код уровня Transparent на предмет верности и безопасности, но, по крайней мере, мы знаем, что он не может выполнять привилегированных операций. Большие куски Silverlight, включая среду выполнения динамического языка (DLR), целиком написаны в коде уровня Transparent. Ограничение привилегированной части Silverlight позволяет нам поставлять более безопасный продукт путем сосредоточения нашего внимания на областях, которым оно действительно требуется.
Библиотека базовых классов
Развитие .NET Framework в настольной среде позволило ей удовлетворять нужды и пользователей, и серверов. Поэтому в библиотеке базовых классов (Base Class Library – BCL) имеется масса функций, лишенных смысла при работе с веб-клиентами. Например, поскольку Silverlight не поддерживает CAS, значительная часть System.Security является ненужной. Многие другие классы, вроде System.Console, нет смысла применять в сети. (Зачем же тогда мы включили урезанный класс System.Console? Он помогает нам тестировать продукт.)
Наша цель в случае библиотек идентична таковой для базового механизма: урезание до минимально возможного набора функций, который позволил бы разработчикам .NET успешно работать, не изучая совершенно новой технологии. Определенное вдохновение и руководство нам дала .NET Compact Framework, которая столкнулась с той же проблемой в иной ситуации. При доводке BCL для Silverlight мы также сохранили совместимость между .NET Compact Framework и Silverlight. Наличие одной библиотеки для всех платформ позволяет довести до максимума универсальность навыков, связанных с .NET.
В BCL имеется ряд мест, где можно найти продублированные функции. Порой функции дублируются внутри самой BCL – как в случае универсальных и неуниверсальных коллекций. Порой функции уже существуют в базовой ОС, как в случае поддержки глобализации. Поддерживать каждую альтернативу в библиотеке базовых классов Silverlight не только нет необходимости – вычеркивание такого дублирования поведения дает выигрыши в производительности и единообразии.
С тех пор, как мы внедрили поддержку универсальных коллекций в .Net Framework версии 2.0, мы выступали за переход на них. В версии среды 1.x структуры данных общего назначения приходилось основывать на объектах, чтобы тот же базовый класс структуры данных можно было использовать для создания различных типов коллекций. С помощью параметров универсального типа компилятор может расширить эти структуры данных общего назначения, чтобы обеспечить безопасность типов, упрощая тем самым написание и обслуживание кода. Вдобавок, универсальные коллекции обычно работают на типах значений лучше, чем неуниверсальные, благодаря отсутствию потребности в элементах окон. В целом, универсальные коллекции предоставляют все функции неуниверсальных. А поскольку дублирование излишне, мы не включали неуниверсальные коллекции, вроде ArrayList, в библиотеку базовых классов Silverlight.
Все знакомы по крайней мере с некоторыми проблемами глобализации: во многих европейских странах запятая используется в качестве разделителя дробной части; в китайском числа группируются в четверки (1000,0000) и так далее. В платформе .NET Framework функции глобализации реализуются внутренне, чтобы иметь возможность правильно работать в различных культурах. Для этого в нее входят данные глобализации для всех поддерживаемых культур, позволяя предназначенным для .NET приложениям вести себя единообразно во всех поддерживаемых версиях Windows. Однако у этого есть свои недостатки. В CLR должны входить большие таблицы данных, и данные часто становятся устаревшими с течением времени. Вдобавок, данные имеют отношение только к Windows, так что данные для некоторых культур .NET отличаются от тех же культур в Mac OS X. По этим причинам в состав CoreCLR не входит собственных данных глобализации. Вместо этого System.Globalization.CultureInfo использует функции глобализации, предоставленные размещающей ОС. Таким образом, приложения Silverlight будут вести подобно приложениям Mac на Mac OS X и подобно приложениям Windows на Windows.
В целом, мы попытались обеспечить схожую контактную зону интерфейсов API между CLR, .NET Compact Framework и Silverlight, но по BCL рассеяны другие мелкие различия. Например, поскольку Silverlight имеет единственный поток интерфейса пользователя, у него имеется единственный объект Dispatcher, содержащий очередь рабочих элементов для интерфейса пользователя. Использование Dispatcher позволит обновлять интерфейс пользователя из не относящегося к нему потока. Данный код позволит обновить элемент интерфейса пользователя (MyListBox) с помощью коллекции, созданной в другом потоке (скажем, в фоновом потоке):
MyListBox.Dispatcher.BeginInvoke(() => MyListBox.ItemsSource = MyItems);
Мы рекомендуем использовать System.ComponentModel.BackgroundWorker в Silverlight, поскольку он инкапсулирует обновление интерфейса пользователя по завершении, но с целью совместимости мы всё же включим низкоуровневые потоковые интерфейсы API, такие как System.Threading.ThreadPool.QueueUserWorkItem и System.Threading.Monitor.Enter.
Подобно модели прозрачности безопасности, некоторые из функций, новых для библиотеки базовых классов Silverlight, уже появлялись в предыдущих версиях .NET Framework. Хорошим примером этого является изолированное хранилище, предоставляющее виртуализованную систему файлов приложениям, помещенным в «песочницу». Оно существовало со времен .NET Framework 1.0, но всегда затрагивало ограниченный набор ситуаций. Silverlight концентрируется на приложениях в «песочнице» и может использовать изолированное хранилище в полной мере:
using (IsolatedStorageFile isoStore =
IsolatedStorageFile.GetUserStoreForApplication())
{
using (StreamWriter writer = new StreamWriter(isoStore))
{
writer.Write("This is an isolated storage file.");
}
}
Подобно файлам cookie в веб-обозревателе, изолированное хранилище позволяет приложениям Silverlight поддерживать одинаковость состояния между вызовами. Однако изолированное хранилище предоставляет полную, виртуализованную файловую систему, поддерживающую создание каталогов и файлов. Хотя изолированное хранилище и не предназначено для хранения ценной информации, такой как пароли, местоположение хранилища скрывается, и доступ в него дается только приложению, владеющему хранилищем.
Квоты для изолированных хранилищ определяются группами приложений, которые основаны на доменном имени приложения Silverlight. Например, два приложения Майкрософт, расположенных в каталогах microsoft.com, будут входить в одну группу приложения, а это значит, что они будут делить одну квоту. По умолчанию группе приложений дается 1 МБ пространства.
Однако, если приложение нуждается в дополнительном пространстве для хранения, оно может запросить большую квоту, запросив пользователя с помощью диалогового окна, которое, для примера, укажет, что microsoft.com желает увеличить свою квоту до 8 МБ. Пользователи могут включать или отключать изолированные хранилища, а также удалять текущие случаи их использования в диалоге настройки Silverlight (в диалоге оно именуется Application Storage («Хранилищем приложения»)). Группы приложений также могут иметь общие хранилища, что позволяет связанным приложениям делиться данными.
Хотя изолированные хранилища существуют не первый день, их использование никогда не было так заманчиво, как в случае Silverlight. Настраиваемая, безопасная система файлов для интерактивных веб-приложений позволяет разрабатывать традиционные офисные приложения, такие как текстовые редакторы, или приложения, обслуживающие большие объемы данных, такие как системы торговли акциями.
Работа на различных платформах
Silverlight работает не только на Windows. Мы находимся в партнерских отношениях с Novell, чтобы поддерживать Linux через среду выполнения Moonlight проекта Mono. Майкрософт также работает над версиями Silverlight для передовой ОС Symbian и Windows Mobile. Moonlight работает на Mono, а мобильная версия Silverlight будет работать на .NET Compact Framework (занимающей гораздо меньше памяти, чем CoreCLR). Но версия Silverlight для Mac OS X работает на точно той же CoreCLR, что и Windows.
Мы добились этого с помощью слоя платформенной адаптации (Platform Adaptation Layer – PAL). PAL – это интерфейс API, написанный для работы на различных платформах. Он предоставляет абстракции для обработки ошибок, обработки файлов, сетевых служб, семантики потоков и так далее. Функции в PAL имеют те же имена, что и интерфейсы API Win32, но различаются по реализации. Некоторые из интерфейсов API просто передают параметры функции PAL к функции OS X, тогда как другим необходимо использовать специальную логику для согласования функциональности OS X с сигнатурами интерфейсов API Windows. Часть функциональности Windows, используемой CoreCLR, не существует на Mac, и в силу этого ее приходится целиком реализовывать в PAL (см. рис. 2).
Значительная часть PAL Silverlight пользуется уроками, извлеченными при разработке инфраструктуры общего языка с кодом совместного пользования (SSCLI), также известной как Rotor. SSCLI работала на ряде платформ в стиле UNIX, помимо Windows. Базовая функциональность ОС на платформах типа UNIX сильно изменяется. PAL SSCLI нужно было работать как на микроядрах (таких как ядро Mach в Mac OS X), так и на монолитных ядрах, а также взаимодействовать с различными службами ОС, вроде работы с потоками, обработки исключений и сетевых стеков. Поскольку Silverlight предназначен только на компьютеры Windows и Intel Mac, у нас была возможность писать специальные реализации под Mac для многих из функций PAL, что помогло с размером и производительностью PAL.
PAL поддерживает лишь часть Win32, необходимую для работы Silverlight. В поддержке реестра, GDI+ или COM нет нужды. Мы не реализовывали Windows поверх OS X, а также не реализовывали Windows в достаточной мере, чтобы поддержать полные возможности настольной CLR. Ограничение PAL только поддержкой Silverlight позволило ему быть маленьким и быстрым.
Сокрытие различий между операционными системами является непростой проблемой с учетом того, насколько OS X отличается от Windows. Значительная часть OS X написана на Objective C и предоставляет систему обработки исключений, несовместимую с C++. CLR создает потоки ввода/вывода, отдельные от рабочих потоков. Они основаны на портах завершения ввода/вывода, появившихся в Windows NT 3.5 и не существующих в OS X. Даже простое обнаружение файла отличается в Mac благодаря разделению каталогов обратной косой чертой в Windows.
В ходе проектирования и разработки CoreCLR мы сосредотачивались на предоставлении среды, позволяющей разработчикам пользоваться существующими навыками и средствами при разработке насыщенного содержания для небольшой, безопасной среды выполнения. Большинство наших решений было определялось сокращенными случаями приложений для Интернета с широкими возможностями, но некоторые решения также извлекли пользу из нашей прошлой работы. Некоторые из решений, сделанных нами в случае CoreCLR, со временем доберутся обратно до настольных систем. Например, можно ожидать, что следующая версия настольной CLR будет работать параллельно в процессе с другими версиями CLR. Кроме того, большинство изменений, связанных с улучшенной моделью прозрачность безопасности, появятся в следующей CLR.
Мы тщательно рассмотрели, что имело смысл в случаях веб-приложений, а что не требовалось в среде. Мы надеемся, что сделанные решения верны и верим, что пользователи сообщат нам, что еще можно улучшить. Удачного программирования в Silverlight 2 и следите за этой рубрикой, чтобы не пропустить более глубоких рассмотрений CoreCLR.
Эндрю Парду (Andrew Pardoe)