Each computer where the Common Language Runtime is installed has a machine-wide code cache called the Global Assembly Cache. The Global Assembly Cache stores assemblies specifically designated to be shared by several applications on the computer.
You should share assemblies by installing them into the Global Assembly Cache only when you need to. As a general guideline, keep assembly dependencies private, and locate assemblies in the application directory unless sharing an assembly is explicitly required. In addition, it is not necessary to install assemblies into the Global Assembly Cache to make them accessible to COM interop or unmanaged code.
There are scenarios where you explicitly do not want to install an assembly into the Global Assembly Cache. If you place one of the assemblies that make up an application in the Global Assembly Cache, you can no longer replicate or install the application by using the xcopy command to copy the application directory. You must move the assembly in the Global Assembly Cache as well.
There are two ways to deploy an assembly into the Global Assembly Cache:
Use an installer designed to work with the Global Assembly Cache. This is the preferred option for installing assemblies into the Global Assembly Cache.
Use a developer tool called the Global Assembly Cache tool (Gacutil.exe), provided by the Windows SDK.
In deployment scenarios, use Windows Installer to install assemblies into the Global Assembly Cache. Use the Global Assembly Cache tool only in development scenarios, because it does not provide assembly reference counting and other features provided when using the Windows Installer.
Administrators often protect the systemroot directory using an access control list (ACL) to control write and execute access. Because the Global Assembly Cache is installed in a subdirectory of the systemroot directory, it inherits that directory’s ACL. It is recommended that only users with Administrator privileges be allowed to delete files from the Global Assembly Cache.
Assemblies deployed in the Global Assembly Cache must have a strong name. When an assembly is added to the Global Assembly Cache, integrity checks are performed on all files that make up the assembly. The cache performs these integrity checks to ensure that an assembly has not been tampered with, for example, when a file has changed but the manifest does not reflect the change.
Если необходимо обеспечить возможность совместного использования сборки в нескольких приложениях, то ее можно поместить в глобальный кэш сборок. Этот кэш кода уровня компьютера присутствует на любом компьютере, где установлена среда CLR. В глобальном кэше сборок сохраняются сборки, специально предназначенные для совместного использования на компьютере несколькими приложениями. Для установки в глобальном кэше сборка должна иметь строгое имя.
Имя сборки, установленной в глобальном кэше сборок, должно совпадать с именем файла (без учета расширения имени файла). К примеру, файл сборки с именем myAssembly должен иметь имя myAssembly.exe или myAssembly.dll.
Прибегать к совместному использованию сборок путем их установки в глобальном кэше сборок следует только при необходимости. Как правило, зависимости между сборками следует сохранять закрытыми, а сами сборки нужно размещать в папке приложения, если они не предназначены для совместного использования. Кроме того, установка сборок в глобальном кэше сборок для обеспечения доступа к ним с помощью COM-взаимодействия или из неуправляемого кода не является обязательной.
Существует несколько причин для установки сборки в глобальном кэше сборок.
Используемые несколькими приложениями сборки можно располагать в глобальном кэше сборок. Например, если все приложения используют сборку, расположенную в глобальном кэше сборок, то в файл Machine.config можно добавить оператор политики выбора версий, который перенаправляет ссылки на эту сборку.
Администраторы часто защищают папку systemroot с помощью списка управления доступом, определяющего права на запись и выполнение. Так как глобальный кэш сборок размещается в корневом каталоге системы, он наследует список управления доступом этого каталога. Рекомендуется разрешать удаление файлов из глобального кэша сборок только пользователям, имеющим права доступа администратора.
Управление параллельными версиями.
В глобальном кэше сборок может храниться несколько сборок, имеющих одинаковые имена, но различные сведения о версии.
Дополнительное место для поиска.
Перед проверкой или использованием сведений о базе кода в файле конфигурации среда CLR ищет в глобальном кэше сборки, соответствующие запросу.
Обратите внимание, что существуют сценарии, в которых установка сборки в глобальный кэш сборок явно не требуется. Если одна из составляющих приложение сборок помещается в глобальный кэш сборок, то после этого нельзя будет скопировать или установить приложение с помощью команды XCOPY путем копирования каталога приложения. В этом случае также требуется переместить сборку в глобальный кэш сборок.
В этом разделе
Использование обслуживаемых компонентов с глобальным кэшем сборок Содержит сведения о том, почему обслуживаемые компоненты (управляемые компоненты COM+) следует помещать в глобальный кэш сборок.
Связанные разделы
Создание сборок Содержит общие сведения о создании сборок.
Глобальный кэш сборок Содержит общие сведения о глобальном кэше сборок.
Обнаружение сборок в среде выполнения Описание того, как среда CLR находит и загружает сборки, составляющие приложение.
Программирование с использованием сборок Описывает сборки, «кирпичики», с помощью которых создаются управляемые приложения.
Все сборки, находящиеся в GAC, подписаны строгим именем — при установке сборки среда Common Language Runtime проверяет сборку на уникальность и сравнивает ее с другими, уже имеющимися сборками.
При этом появляется описание команд утилиты (рис. 9.16), среди которых нас интересуют всего три:
Управление сборками при помощи утилиты gacutil.exe — не самый удобный способ. Более широкие возможности управления сборками предоставляет консоль MMC (Microsoft Management Console), для запуска которой в окне Выполнить (Run) набираем mmc (рис. 9.17).
В появившемся окне выбираем в меню «Консоль\Добавить или удалить оснастку …» (рис. 9.18).
В открывшемся окне можно управлять сборками — добавлять их или удалять (рис. 9.20).
Не удаляйте сборки, которые вам неизвестны, — вы можете нарушить работоспособность некоторых программ!
Вопросы и ответы
При нажатии на Сумма в примере ArbitraryMethod из Лекция 7, VS 2013 выдается ошибка:
Необработанное исключение типа «System.InvalidOperationException» в System.Windows.Forms.dll
Дополнительные сведения: Недопустимая операция в нескольких потоках: попытка доступа к элементу управления «lblResult» не из того потока, в котором он был создан.
Затем:
Необработанное исключение типа «System.InvalidOperationException» в mscorlib.dll
Дополнительные сведения: Для каждой асинхронной операции метод EndInvoke может вызываться только один раз.
Опубликовано: 08.04.2004 Исправлено: 10.12.2016 Версия текста: 2.0
Немного истории
Первым делом необходимо ознакомить читателя с историей развития технологий распределения программного кода, предшествующих появлению сборок. Познакомившись с ними, станут, понятны причины введения многих решений нашедших себя в сборках и кажущуюся с первого взгляда громоздкими и неуместными.
Первые шаги
С момента написания первых серьёзных приложений перед программистами встала проблема разделения, а также повторного использования кода. Безусловно, самым тривиальным её решением, является банальное копирование текста исходного кода из старого проекта в новый. Несмотря на кажущуюся простоту данного подхода, он оказался неприемлем при разработке более или менее серьёзных проектов. В большинстве случаев использование подобного подхода приводило к путанице, а так же нескончаемым потокам ошибок и нестыковок. Было очевидно, что проблема требовала более глубокого анализа и рассмотрения.
Процедурное программирование
Первым шагом на пути решения данной проблемы стало появление процедурного программирования. Это было сильным рывком вперёд, по сравнению с линейным стилем написания программ «сверху-вниз», присущим первым версиям языков программирования. Доказательством гениальности процедурного подхода служит то, что данная технология благополучно дожила до наших дней, став повсеместным стандартом де-факто. Без функций и процедур сейчас не обходиться ни один язык программирования, начиная от серьёзных языков для разработки приложений и заканчивая простенькими скриптами администрирования операционных систем.
Но все же, одного процедурного подхода оказалось недостаточно, поскольку он позволял разбивать программу на логические части, лишь в рамках одной языковой среды разработки. Сие означает, что к примеру «паскалист» не имел возможности использовать функции написанные на С++ или других языках, что порой было крайне необходимо. Можно сказать даже больше, в те времена программисты даже и не мечтали об удобной межъязыковой интеграции. Попытки, конечно, были, но все они были не приемлемы для повседневного использования, поскольку большинство из них были созданы в кустарных условиях самими программистами, как говориться «на коленке». Основная проблема заключалась в отсутствии общего стандарта.
А тем временем количество языков и сред разработки неудержимо увеличивалось, и программному миру было необходимо более мощное средство интеграции, чем простой процедурный подход.
Модульное программирование
Создатели средств разработки естественно понимали что одного процедурного подхода, для решения проблемы явно недостаточно. Вследствие чего во многих компиляторах появилась возможность создания модулей. То есть разбиения кода на отдельные островки, предоставляющие внешний интерфейс доступа и скрывающие реализацию кода от внешних глаз. Но данная возможность никак не могла претендовать на роль единого средства интеграции программного кода, поскольку она не была обще-стандартизированной. У каждого средства разработки была своя технология модулей, что ограничивало её использование лишь этой средой. Была необходима технология, которая позволила бы взаимодействовать коду на уровне всей операционной системы, в независимости от языка и среды разработки.
Прерывания и резидентные программы, как средство интеграции
Пионером на пути решения проблем глобальной интеграции и распределения кода, была технология программных прерываний. Данная технология позволяла создавать и вызывать общесистемные сервисы, доступные из любых программ, написанных на любых языках. Любая программа могла перекрыть одно из 255 доступных программных прерывания, предоставив, таким образом, общесистемный глобальный сервис.
Это был первый унифицированный и четко стандартизированный интерфейс, для глобального взаимодействия между программами. Он позволяет осуществлять вызовы практически из любых языков программирования, и поддерживается самой операционной системой вкупе с процессором, что делает его крайне надежным и практичным. Правда, прерывания имеют множество недостатков перечисленных ниже:
Последний пункт стал, пожалуй, основным препятствием к повсеместному использованию прерываний, в качестве основной технологии распределения программного кода. Хотя было бы крайне неверно говорить, что прерывания все же не получили распространения. Во времена MS-DOS они были, пожалуй, единственным и наиболее популярным способом предоставления общедоступных программных сервисов. Настоящая технология не забыта и сегодня, она с успехом применяется в недрах операционных систем. К примеру, в ОС Microsoft Windows 2000 все вызовы ядра проходят через программное прерывание под номером 2Eh, хотя для программистов высокого уровня, использующих обычное Windows API, это не является явным. Рассматривая функцию API ReadFile, можно с лёгкостью показать, что её вызов на определённом этапе приводит к обращению к прерыванию 2Eh с параметром 0A1h, передаваемым в регистре eax. Где прерывание 2Eh является стандартным шлюзом для подавляющего большинства API вызовов на уровень ядра, а параметр 0A1h собственно и определяет искомую функцию, в данном случае ReadFile.
ПРИМЕЧАНИЕ
В последних версиях современных операционных систем внутрисистемные вызовы предпочитают организовывать не через прерывания, а при помощи новой специализированной инструкции syscall. Которая, впрочем, по своей природе достаточно сильно напоминает прерывания, но в отличие от них имеет более высокие показатели производительности.
Таким образом, данная технология межпрограммного взаимодействия совсем не плоха, правда она не подходит для повсеместного использования в силу вышеочерченых причин.
Динамические библиотеки
Следующим шагом на пути распределения программного кода, явилось создание динамически подключаемых библиотек, наверняка знакомых читателю под широко известной аббревиатурой DLL (Dynamic Link Library). С появлением данной технологии стало возможным создание, внешних полностью самостоятельных библиотек. Обращение к коду, которых могло наглядно осуществляться из любых языков и средств разработки.
На технологии динамических библиотеках построен весь программный интерфейс верхнего уровня операционных систем от Mirosoft. Любое API, любой сервис, так или иначе, базируются на DLL. Основное преимущество данной технологии заключалось в том, что в большинстве случаев она позволяет осуществить разделение кода прозрачно для пользователя. Так, к примеру, от большинства программистов механизм вызова API функций является скрытым.
ПРИМЕЧАНИЕ
Для DOS, а так же для многих других операционных систем, так же существует поддержка технологии динамически подключаемых библиотек. Только реализация настоящего механизма будет несколько отличаться, и носить характерные особенности данной операционной системы.
Принцип работы динамических библиотек ужасно прост, если конечно не вдаваться в подробности внутренней реализации.
Сама библиотека содержит откомпилированный процессорный код, а так же ряд служебных таблиц описывающих содержащиеся в ней функции. По запросу программы или, что более вероятно, загрузчика исполняемых файлов Windows, код библиотеки копируется в её адресное пространство, становясь с этого момента доступным для прямого вызова. Данный вызов может быть осуществлен непосредственно при помощи инструкции call, jmp или прямого изменения регистра IP (здесь и далее будет рассматриваться только программы ориентированные на архитектуру x86). В большинстве же случаев вызовы происходят, как обычный вызов функции, в привычной для языка лексике, а о низкоуровневых подробностях заботиться сам компилятор.
Процесс загрузки динамической библиотеки схематично изображен на рисунке 1.
Рисунок 1
Для максимального упрощения, описания механизма работы динамических библиотек, здесь ничего не было сказано, о переадресации функций, таблицах импорта и экспорта, проецировании кода, пересчёта смещений, а так же о многом другом, но в общих чертах все осталось верно. Для получения более полной информации о работе динамических библиотек, стоит обратиться к прекрасной книге Джефри Рихтера (Jeffrey Richter): «Programming Application for Microsoft Windows» или же напрямую к документации Microsoft Platform SDK.
Динамические библиотеки на время их разработки был прекрасным средством распределения кода и межъязыковой интеграции. Правда, при их использовании возникали незначительные проблемы с выбором формата вызова функций, поскольку разные языки использовали разные форматы вызовов функций.
ПРИМЕЧАНИЕ
Форматом вызова функции называется соглашение о способе передачи её параметров и возвращении результата её работы. Всего существует семь. форматов вызовов: stdcall, cdecl, fastcall, thiscall, PASCAL, FORTRAN, SYSCALL. Причем последние три считаются безнадёжно устаревшими и более не поддерживаются.
В итоге стандартом де-факто стал stdcall, который поддерживается всеми современными компиляторами, и используется в подавляющем большинстве динамических библиотек представляющих глобальные API сервисы.
Для подключения функций из динамической библиотеки используется её символьное имя, что является гораздо более наглядным, чем вызов прерывания. К тому же вызов функции может происходить в привычном для языка синтаксисе.
Правда, в отличие от родных функций языка, при вызове динамических функций не проверяется информация о типах, а так же корректность передаваемых параметров, поскольку данная информация не предоставляется самими библиотеками. Вследствие чего, при их неосторожном использовании, не исключено возникновение конфликтных ситуаций, а так же неправильной работы программ. Правда, такое происходит довольно редко, поскольку в основном динамические библиотеки подключаются статически на стадии сборки программ. А в этом процессе участвуют специальные библиотеки компоновки (в частности lib файлы), при помощи которых компилятор может проконтролировать соответствие типов функций и их параметров на стадии компиляции. Что, существенно снижает риск некорректного использования функций из динамических библиотек. Но необходимо все же необходимо обратить внимание на то, что на стадии исполнения никаких проверок не происходит.
Ад динамических библиотек
Помимо возможных неприятностей с вызовами, динамические библиотеки принесли в жизнь программистов куда более серьёзную проблему, которую окрестили адом динамических библиотек (DLL Hell). Для того чтобы пояснить суть настоящей проблемы, необходимо в общих чертах обрисовать механизм подключения динамических библиотек. Если читатель чувствует себя осведомлённым в данной области он может смело пропустить следующие пояснения.
Способы подключения динамических библиотек
Всего существует два способа подключения динамических библиотек: статический и динамический. Наиболее распространён статический тип подключения. Он подразумевает связывание с библиотекой во время сборки (компиляции) приложения. Делается это при помощи добавления записи в таблицу импорта исполняемого файла. Данная запись представляет собой имя файла библиотеки, а так же список используемых из неё функций. Перед началом исполнения программы загрузчик Windows проверяет записи в таблице импорта и автоматически подгружает необходимые библиотеки, а так же при помощи специального механизма связывает указанные фукнкции со ссылками внутри исполняемого файла. Таким образом, загрузка библиотек происходит автоматически, без явного участия кода программы.
Наряду со статическим типом подключения динамических библиотек, существует возможность динамической загрузки библиотек. То есть код программы имеет возможность самостоятельной загрузки библиотек, прямо по ходу исполнения. Данная операция осуществляется при помощи функции
,расположенной в kernel32.dll, которая в свою очередь должна быть подключена к программе статически.
ПРИМЕЧАНИЕ
В принципе программа может и не подключать kernel32.dll. Все равно он автоматически подгружается в адресное пространство приложения, поскольку используется загрузчиком исполняемых файлов Windows. Но в этом случае для того, чтобы использовать функции данной библиотеки, придётся самостоятельно просканировать определённые области адресного пространства. Данная технология в обычных программах не используется, её в основном берут на вооружение создатели вирусов.
После загрузки библиотеки в адресное пространство процесса, необходимо получить адрес необходимой функции. Наиболее простой способ сделать это – воспользоваться функцией
Передав ей в качестве первого параметра описатель библиотеки загруженной при помощи функции LoadLibrary, а в качестве второго имя необходимой нам функции или её же порядковый номер. В результате мы получим адрес данной функции, внутри адресного пространства нашего процесса (ведь библиотека с функциями уже загружена внутрь). После чего мы можем осуществлять вызов данной функции любыми доступными нам средствами, начиная от обычного вызова функции в стиле нашего языка и заканчивая прямым вызовом команды call по данному адресу.
Если на секунду взглянуть на оба способа подключения библиотек со стороны, то становиться ясно, что они в принципе идентичны, как бы это заявление и не казалось странным. Поскольку в обоих случаях, для загрузки библиотеки, используется одна и та же функция LoadLibrary. Только в первом случае её вызывает загрузчик исполняемых файлов Windows, а во втором сама программа.
Теперь, когда мы полностью знакомы с механизмом загрузки динамических библиотек, можно перейти к детальному рассмотрению проблемы окрещенной адом динамических библиотек (Dll Hell).
Как нетрудно заметить, для загрузки динамической библиотеки используется лишь имя файла, в которой она расположена. Ранее Microsoft рекомендовала хранить все общие динамические библиотеки в едином хранилище – каталоге system для систем класса 9x и каталоге system32 для систем класса NT.
ПРИМЕЧАНИЕ
Данная рекомендация была продиктована, желанием сэкономить место на жестких дисках пользователей. Большинство программ используют один и те же широко распространенные библиотеки, а их размещение в общем централизированном хранилище позволяло избежать дублирования одинаковых файлов на жестких дисках пользователей, что в свою очередь освобождало бесценные байты для других программ.
А так как данное хранилище является общедоступным, то нередко возникала ситуация когда библиотеки замещались совершенно другими, но с идентичными файловыми именами. Данная ситуация наверняка знакома уважаемому читателю. Выражается она обычно в том, что при попытке запуска приложения, на экране появляется следующее диалоговое окно.
Рисунок 2
Оно сообщает пользователю о том, что в библиотеке Some.dll не была найдена функция hello. При этом прошу обратить внимание на то, что сама библиотека была успешно найдена и загружена. В случае если загрузчик исполняемых файлов обнаружит несоответствие библиотек и выдаст подобное сообщение об ошибке, то можно считать, что вы обошлись малой кровью. Все может быть намного хуже. К примеру, если будет загружена та библиотека которую вроде бы хотелось, но только другой версии. А поскольку в библиотеке нет никакой информации о прототипах функций, и проверить правильность передаваемых в функцию параметров нет никакой возможности, то скорее всего при вызове её функций возникнет исключительная ситуация.
ПРИМЕЧАНИЕ
Прототипом функции называется информации о количестве и типе передаваемых в функцию параметров, а так же о типе значения возвращаемой функцией.
А если уж это действительно произойдет, то можно со сто процентной вероятностью говорить о том, что приложение аварийно завершит свою работу.
Итак, суть проблемы ада динамических библиотек заключается в подмене необходимых библиотек. Происходило это из-за отсутствия механизма устойчивой идентификации необходимых библиотек. Даже неискушенному программисту будет понятно, что идентификация библиотек лишь по имени файла не является надёжной.
Позднее Microsoft разобралась, что гораздо безопаснее хранить библиотеки совместно с приложениями, и жертвы дисковым пространством вполне оправданы перед лицом данной серьезной проблемы. Она (Microsoft), конечно же, рекомендовала хранить библиотеки совместно с приложениями. Но, к сожалению, огромное количество программистов не только не знакомо с данной рекомендацией, но и вообще ничего не слышали о проблеме ада динамических библиотек и до сих пор размещают свои творения прямо в каталоге system32 (system).
Управление версиями динамических библиотек
Осознав всю серьёзность проблемы, Microsoft разработала механизм управления версиями динамических библиотек. Сделано это было при помощи введения дополнительного ресурса VERSIONINFO, который содержал информацию о версии, а так же специального Versioning API для управления им. Функции данного, представлены ниже:
ПРИМЕЧАНИЕ
Вы сможете найти список всех устаревших функций в Platform SDK на странице Obsolete Windows Programming Elements.
На данный момент проблема ада динамических библиотека практически полностью побеждена, при помощи требований к совместному хранению динамических библиотек и исполняемых файлов непосредственно в каталогах программ, а так же при участии сервисов защиты системных файлов System File Protection и Windows File Protection. Но вероятность возникновения проблем все же остаётся и она не такая уж и маленькая, как может показаться с первого взгляда.
Компонентный подход COM
Следующим этапом стало появление компонентной объектной модели (COM – Component Object Model).Данная технология впервые была реализована в составе Windows 95.
COM уже позволял разделять программы на отдельные независимые компоненты. Компоненты в отличие от всех свои предыдущих собратьев подключались уже не по имени файла, а при помощи специального глобального идентификатора (GUID).
ПРИМЕЧАНИЕ
Каждому компоненту сопоставлялся свой уникальный идентификатор, по которому в свою очередь можно было получить полную информацию о нём в базе данных COM. В ней хранилась информация по каждому из компонентов начиная от имени файла в котором расположен сам компонент и заканчивая сетевыми настройками. Вы можете самостоятельно ознакомиться с содержанием базы COM, заглянув в раздел реестра указанный в примечании.
ПРИМЕЧАНИЕ
Основная база данных COM находится в реестре, в разделе HKEY_CLASSES_ROOT. А записи GUID идентификаторов, хранятся ниже в подключе HKEY_CLASSES_ROOT\Clsid. А так же дополнительную информацию здесь: HKCR\Interface, HKCR\TypeLib.
Для усиления политики управления версиями идентификаторы были введены не только на уровне имён самих компонент, но так же и на уровне самих функций экспортируемых ими. Правда, теперь компоненты экспортировали не отдельные функции, а целые их группы, объединённые в интерфейсы. Каждому такому интерфейсу сопоставлялся уникальный 128 битный GUID идентификатор, который исключал возможность случайного использования другого. Казалось бы, такая мощная система разделения кода и управлениями версиями была неуязвима, но как показало время она была не так уж и надёжна. Проблема пришла сразу с двух сторон. Во-первых, технология COM, все же базировалась на динамических библиотеках, в которых помещались сами компоненты. А с библиотеками зачастую происходила путаница, связанная с совпадением имён файлов в которых они расположены. Но это была еще не самая страшная проблема. Основная неприятность крылась в базе данных COM, расположенной в реестре. Работа с данной базой предполагалась напрямую, без использования специального дополнительного API, которое обязательно следовало бы ввести, чего Microsoft к сожалению не сделала. А поскольку раздел реестра, в котором храниться база COM, является общедоступным, то он после продолжительной эксплуатации Windows, в большинстве случаев приходил в нерабочее состояние. С данной ситуацией наверняка знакомы большинство из пользователей – когда после установки значительного количества программ на компьютер он приходил в не работоспособное состояние, проявляющее себя в очень странных конфликтах.
Новое решение
Что же такое сборки
Далее мы рассмотрим каждый из пунктов более подробно.
Виды сборок
Сборки разделяются на два вида: статические и динамические. Статические сборки располагаются на диске в файлах формата PE (Portable Executable) и могут содержать заранее откомпилированный IL код, а так же дополнительные ресурсы. Динамические сборки располагаются прямо в памяти и не имеют дискового файла. Хотя, они вполне могут быть сохранены на диск после исполнения. Такие сборки создаются налету (во время исполнения программы) при помощи специальных средств общей библиотеки классов (FCL), названных технологией отражения (reflection).
В данной статье мы будем обсуждать лишь статические сборки, а динамические рассматривать не будем. Но все сказанное ниже будет справедливо и для динамических сборок, за тем лишь исключением, что большинство описанных здесь операций придется проводить при помощи технологии отражения.
Статические сборки
Рисунок 3
Конечно, можно было бы создать для сборок новый формат файлов, как к примеру, поступила Sun при разработке Java.
ПРИМЕЧАНИЕ
Откомпилированные приложения Java, распространяются в файлах специального формата с расширением class. Данный формат является полностью платформенно независимым и может исполняться на любой системе под управлением виртуальной машины Java.
Теперь мы перейдем к рассмотрению самих файлов статических сборок, тех которые вложены внутрь PE файлов. Они включают в себя четыре основных элемента:
Из вышеперечисленных элементов только манифест является обязательным, остальные же элементы могут включаться по желанию.
Чаще всего сборки располагаются в одном дисковом файле. Но так же существует возможность создать многофайловые сборки. Их содержимое будет представлено в виде нескольких дисковых файлов, но, тем не менее, они будут рассматриваться средой исполнения как одна единая сборка.
Рисунок 4
Так же под крышей одной сборки можно объединить несколько файлов, в которые поместить различные элементы сборки. Пример такой сборки показан на рисунке 5.
Рисунок 5
Как видно из рисунка за пределы сборки можно выносить не только ресурсы, но даже и код, который будет представлен в виде модулей.
У многофайловых сборок есть три крайне сильных преимущества перед однофайловыми.
Манифест
Информацию о манифесте можно просмотреть используя IL листинг данной сборки (он может быть получен при помощи утилиты ildasm.exe, с параметром /out:имяфайла.il).
Он обычно располагается в самом начале кода. Так же вы можете использовать утилиту ildasm.exe в интерактивном режиме. В этом случае, для того, чтобы просмотреть манифест необходимо открыть ветку MANIFEST в главном окне утилиты.
Рисунок 6
Приведём пример манифеста.
Каждый из элементов подробно описан в таблице 1.
Директива IL
Описание
.assembly extern
Указывает внешнюю сборку необходимую для работы данной. (В нашем случае это ссылка на FCL (Framework Class Library – общую библиотеку классов), которая преимущественно располагается в сборке mscorlib)
.publickeytoken
Определяет маркер открытого ключа подключаемой сборки. Необходим для точной идентификации сборки.
.ver
Определяет необходимую версию подключаемой сборки
.culture
Указывает региональную принадлежность сборки
.assembly
Указывает имя сборки
.hash algorithm
Определяет алгоритм по которому рассчитывался маркер открытого ключа данной сборки.
.ver 0:0:0:0
Определяет версию сборки.
.public
Открытый ключ, встраиваемый в сборку
.culture
Указывает региональную принадлежность сборки
.module
Указывает имя основного модуля данной сборки.
.module extern
Указывает имя внешнего модуля входящих в данную сборку, на которые есть ссылки в коде данной сборки (модуля)
.imagebase
Указывает адрес, по которому следует загружать данную сборку а адресное пространство приложения.
.subsystem
Указывает на тип приложения. В нашем случае число 3 указывает на то, что приложение консольное.
.file alignment
Определяет выравнивание, использовавшееся компилятором при создании данного файла.
.corflags
На данное время данная директива является зарезервированной.
.mresource public
Указывает на то, что к сборке будет подключен общедоступный ресурс с заданным именем.
.file
Определяет имя файла в котором располагается ресурс.
.file
Подключает внешний модуль, на который при этом может не быть явных ссылок в коде данной сборки.
.entrypoint
Указывает на то, что точка входа в приложение будет располагаться в данном модуле.
Таблица 1
Наблюдательные читатели наверняка заметили, что в таблицу не попал, один элемент манифеста: MVID. Он даже был закомментирован в тексте IL кода. Данный элемент достаточно важен и заслуживает того, чтобы рассказать о нём отдельно.
Идентификатор версии модуля MVID
MVID расшифровывается как Module Version Identifier – Идентификатор версии модуля. Это 128 битный уникальный идентификатор, который автоматически генерируется при каждом построении сборки (модуля) и автоматически добавляется в манифест. Данный идентификатор используется только во внутренних целях среды исполнения. Сами программисты с ним не сталкиваются. Именно поэтому он и был закомментирован, в манифесте. Поскольку он генерируется автоматически, то в языке IL не предусмотрено директивы для его указания.
При каждой загрузке любого модуля среда исполнения использует MVID, для проверки того, не был ли он уже загружен (MVID – уникален глобально, поскольку это обычный GUID). Если модуль оказывается уже подгруженным, то среда исполнения просто устанавливает необходимые связи с ним, избегая прямого обращения к его файлу, тем самым, существенно увеличивая скорость доступа к модулю.
Модули
ПРЕДУПРЕЖДЕНИЕ
Очень важно с самого начала осознать, что сборки это не файлы. Это не более чем абстракция в виде специальных пакетов, предназначенных для хранения и объединения файлов.
Сборки могут иметь версию, уникальную подпись при помощи криптографического ключа, а так же атрибуты безопасности, чего не могут модули и файлы ресурсов. Вследствие чего можно было бы предположить, что они (модули) не могут использоваться автономно, как самостоятельные единицы. Данное предположение по большей части верно, поскольку отдельное использование модулей возможно лишь вручную при помощи технологии отражения. В подавляющем большинстве случаев они используются в составе сборок.
Помимо обычных модулей, по умолчанию входящих в однофайловые сборки, существует возможность создавать внешние модули. Такие модули чаще всего подключают к приложению при помощи технологии многофайловых сборок. Проиллюстрируем вышесказанное примером. Первым делом мы создадим сам модуль, код которого впоследствии включим в состав нашего приложения. Листинг данного модуля представлен ниже.
Код данного модуля предельно прост и не содержит ничего лишнего. В нём вы можете обнаружить класс Mod, с единственным методом, который всего лишь информирует о своём вызове выводом приветствия на консоль. Особо хотелось бы обратить внимание читателей на компиляцию данного листинга.
ПРИМЕЧАНИЕ
Для того чтобы превратить данный листинг в модуль нам придётся воспользоваться компилятором командной строки csc, вызвав его при помощи следующей команды.
Где ключ /t:module указывает на то, что из исходного кода расположенного в фале Mod.cs необходимо создать модуль.
Теперь же нам предстоит создать многофайловую сборку, которая будет использовать данный модуль. Это намного проще и удобней чем вы можете себе представить. Для начала напишем её код.
Для того, чтобы скомпилировать данный код в сборку необходимо воспользоваться компилятором csc, которому указать на необходимость подключения ранее созданного модуля, при помощи ключа /addmodule.
В итоге должно получиться два файла: Some.exe (файл сборки) и Mod.netmodule (внешний модуль). Фактически, в только что созданном, приложении присутствует два модуля, хотя это и не является явным. Первый входит в состав файла Some.exe и является основным модулем приложения, поскольку содержит точку входа. А второй модуль, как не трудно догадаться, внешний и располагается в файле Mod.netmodule.
ПРИМЕЧАНИЕ
В результате работы исполняемого файла приложения (Some.exe) на консоль будут выведены следующие строки.
Хочу особо обратить внимание на то, что сканирование кода на предмет использования внешних модулей происходит в рамках одной функции, перед её вызовом. Сканирование всего кода приложения ни в коем случае не происходит. Соответственно обращения к разным модулям, лучше помещать в разные функции, а не разбивать условными ветвлениями внутри одной функции. Такой подход повышает вероятность того, что приложение сможет обойтись без загрузки отдельных модулей.
У модулей есть одно очень полезное и при этом не документированное свойство. Они одновременно могут входить в состав нескольких сборок. То есть можно создать модуль поместив в него общий для нескольких сборок участок кода, а затем подключить его ко всех этим сборкам (рисунок 7).
Рисунок 7
Это потрясающе удобно, поскольку, изменяя код только одного модуля, мы сразу же подвергаем изменениям все зависимые от него сборки. Скептики могут, конечно же, сказать, что, то же самое можно сделать при помощи подключения дополнительных сборок. Но использование в данных целях модулей имеет существенное преимущество перед сборками. Код помещённый в модули будет, входит в состав сборки, которая его подключает, а следовательно будет иметь общие с ней атрибуты безопасности и конфигурационные настройки. Таким образом, код модуля Some.netmodule будет автоматически иметь атрибуты и привилегии той сборки, в состав которой он входит, а в случае помещения общего кода в отдельные сборки данные параметры придётся настраивать вручную. В итоге можно заключить, что обобществление кода между приложениями при помощи модулей гораздо более удобно, чем при помощи сборок.
«Пустые» сборки
К сожалению, обычными средствами создать ни «пустую» сборку, ни модуль, который мы будем при помощи неё подключать, не удастся. Поэтому нам придётся воспользоваться низкоуровневым языком IL.
Первым делом мы создадим модуль, который подключим к пустой сборке. Он будет кардинально отличаться от обычных тем, что в нём будет располагаться точка входа нашего приложения. Да, именно так, я не оговорился! Я даже хочу специально обратить внимание на это. Точка входа в приложение будет располагаться не в основном исполняемом файле, а во внешнем модуле. Его код приведён ниже.
В результате должен получиться модуль в виде файла под именем Module.mod.
Теперь нам предстоит создать ту самую «пустую» сборку, которая будет использовать данный модуль. Код данной сборки мы напишем так же на IL, поскольку только в этом языке присутствует возможность непосредственной работы с манифестом. А поскольку данная сборка будет представлять собой, один лишь манифест, то язык IL окажется незаменим. Код «пустой» сборки представлен ниже.
Из данного исходного кода необходимо создать сборку, сделать это можно при помощи компилятора ilasm, используя следующую простую командную строку.
Дополнительных параметров, кроме имени файла указывать не надо. Результатом компиляции данной программы должен явиться файл Some.exe. Это «пустая» сборка являющаяся исполняемым файлом созданного нами приложения. В результате её запуска на консоль будет выведена строка.
Необходимо особо отметить тот факт, что в самом файле Some.exe не содержится ни байта кода. И если внешний модуль, в котором располагается код приложения, окажется недоступным, то среда исполнения сообщит об ошибке при помощи следующего диалогового окна.
Рисунок 8
Таким образом, видно, что «пустые» сборки используются в качестве связующего звена, между реальными элементами сборки. Их удобно использовать для поставки приложений в минимальной конфигурации, что очень удобно в локальных сетях с большим количеством пользователей. Вы можете поместить код и модули приложения, в сети. А приложение поставлять в виде маленькой «пустой» сборки и конфигурационного файла, который необходим для указания местоположения остальных частей приложения в сети.
Сборки со строгими именами
Сборки могут использоваться в двух режимах: персональном и совместном. Персональный режим предполагает размещение сборок в одном каталоге с приложением. Связывание с ними будет осуществляться лишь при помощи символьных имён. А поскольку сборка будет доступна только данному приложению, то конфликт имён или коллизии другого рода практически исключены.
В совместном режиме сборки помещаются в глобальное хранилище (GAC) из которого они доступны любым приложениям. Здесь уже будет недостаточно одного имени для связывания со сборкой, необходим более устойчивый алгоритм связывания, который бы гарантировал уникальность и точность связывания. С этой целью для сборок были введены строгие имена. Помимо обычного символьного имени, версии и региональной информации строгое имя требует подписание сборки открытым криптографическим ключом, что гарантирует полную уникальность её имени (идентификации).
ПРИМЕЧАНИЕ
Обратите внимание на то, что сборка содержит открытый ключ. А строгое имя маркер открытого ключа. Это два разных понятия, которые будут раскрыты далее.
Само строгое имя состоит из символьного имени сборки, версии, региональной информации, а так же маркера открытого ключа. Примеры строгих имён приведены ниже.
Обратите внимание на то, что при совпадении символьных имён, полные имена не совпадают, из-за различия регионального идентификатора, а так же маркера открытого ключа.
ПРИМЕЧАНИЕ
Маркер открытого ключа является 64 битным (8 байтным) числом, которое является хешем (контрольной суммой) открытого ключа. Маркер был введён для того, чтобы сократить запись строгого имени сборки. С маркером ключа работать гораздо удобнее, поскольку он намного меньше, чем сам открытый ключ.
Правда, у вас могут возникнуть сомнения по поводу надёжности маркера по сравнению с реальным ключом. Можете не беспокоиться, он (маркер) по умолчанию рассчитывается при помощи алгоритма SHA-1, которого с лихвой хватает для обычных приложений.
Если вы хотите подробнее ознакомиться с теорией открытых ключей, хешами, а так же другими криптографическими алгоритмами, то вам стоит ознакомиться с книгой Брюса Шнайдера – «Прикладная криптография».
В самой сборке помещен, конечно, не маркер, а сам открытый ключ. А вот ссылка на сборку со строгим именем производится по маркеру открытого ключа. Убедиться в этом можно, обратившись к манифесту любого файла использующего сборку со строгим именем. Прекрасным примером такой сборки является mscorlib – основной сборки FCL (общей библиотеки классов). Запись о связи с ней присутствует в манифесте практически любой сборки и выгладит так.
Выбор маркера, а не полного ключа для связи со сборкой продиктован требованиями уменьшения размера конечного файла сборки. Сборка может ссылаться на множество других строгих сборок, а посему, связывание при помощи полных открытых ключей было бы весьма накладно. Размер открытого ключа более одного килобайта. Следовательно, если использовать для связи со сборкой не маркер, а полный открытый ключ, то с каждой подключаемой сборкой размер файла будет увеличиваться более чем на килобайт
При загрузке сборки со строгим именем, загрузчик среды исполнения ищет её, основываясь на символьном имени, версии, региональной информации, а так же маркере открытого ключа, который обязательно должен совпадать с маркером (хешем) открытого ключа подключаемой сборки.
ПРИМЕЧАНИЕ
Опасаться того, что расчет маркеров (читай: контрольных сумм) открытых ключей при поиске необходимых сборок может замедлить процесс загрузки приложения, не стоит. Маркеры для всех сборок, располагающихся в глобальном хранилище, рассчитываются заранее.
Такой подход полностью исключает возможность использования чужой сборки, вместо необходимой. Если конечно разные создатели сборок, не будут пользоваться одной и той же парой ключей при создании сборки. Случайное создание идентичной пары ключей полностью исключено!
Помимо участия в строгом имени сборки, открытый ключ используется для её подписания. Происходит это так. При создании сборки компилятор рассчитывает контрольные суммы всех файлов входящих в сборку, затем формирует базовый файл сборки, в манифесте которого будут указаны данные значения и полный открытый ключ сборки. Поле чего считает контрольную сумму базового файла итоговой сборки, которую при помощи пары ключей (открытого и закрытого) преобразует в цифровую подпись RSA. Данная подпись помещается в специальную секцию PE файла, не участвующую при подсчёте общей контрольной суммы, что позволяет провести процесс в обратном порядке. То есть подсчитать контрольную сумму сборки, и при помощи сохранённой цифровой подписи и открытого ключа, проверить целостность файла.
Рисунок 9
Данный механизм позволяет предотвратить несанкционированное изменение сборки – её кода, или любых входящих в её состав файлов.
Создание строго именованных сборок
Первым делом нам предстоит создать пару криптографических ключей, которыми мы будем подписывать нашу сборку. Ключи создаются при помощи утилиты sn.exe, с использованием параметра –k.
После выполнения данной команды в текущем каталоге будет создан файл, в котором располагается пара криптографических ключей: открытый и закрытый. При помощи данной утилиты вы можете просмотреть открытый ключ, а так же узнать его маркер.
Если же сократить параметр до –t, то на консоль будет выведен лишь маркер открытого ключа. Прошу вас обязательно запомнить это, так как маркер созданного ключа, нам еще не раз понадобиться.
ПРЕДУПРЕЖДЕНИЕ
Помните, что закрытый ключ должен быть доступен только вам. Поскольку, попав в чужие руки, он позволит подписывать сборки от вашего имени и выдавать посторонним лицам себя, за вас.
«Хранить в прохладном и темном месте, не доступном для детей!»
Теперь, располагая парой ключей, мы можем перейти к созданию строго именованной сборки. Для этого, при помощи атрибута AssemblyKeyFile, укажем компилятору, что сборку необходимо подписать при помощи пары ключей, располагающихся в файле Keys.snk. В исходном коде применение данного атрибута будет выглядеть так:
Код нашей строго именованной сборки представлен ниже.
Скомпилировать данный файл следует при помощи следующей команды
Где параметр /target:library указывает на необходимость создания библиотеки. То есть классы данной сборки можно будет использовать из других приложений. В результате компиляции мы должны получить файл Strong.dll. Это и есть строго именованная однофайловая сборка.
Более, особых требований на использование данного атрибута не накладывается.
Теперь, когда мы создали строго именованную сборку, было бы логично научиться её использовать. Но не всё так просто. Использовать данную сборку в персональном режиме не интересно, поскольку то же самое мы делали ранее. А в совместном режиме мы её использовать пока не можем, так как не знаем, как пользоваться глобальным хранилищем сборок (GAC). Посему, придётся несколько отвлечься от линии нашего повествования и рассказать о GAC.
Глобальное хранилище сборок (GAC)
Аббревиатура GAC расшифровывается как Global Assembly Cache, что в дословном переводе на русский означает: Глобальный Кеш Сборок. Но я буду использовать термин хранилище вместо кеш. Он более точно отвечает реальному предназначению GAC, так как кеш это все-таки не постоянное, а временное место хранения чего-либо, используемое для оптимизации доступа к данным. Для примера, можем привести, дисковый или процессорный кеш.
GAC же предназначен, для постоянного хранения совместно используемых сборок. В его задачу входит обеспечение безопасностного хранения строго именованных сборок, а так же предотвращение коллизий при их использовании.
С данной задачей GAC справляется на удивление элегантно. Ничего подобного ранее не существовало. При всей красоте своей архитектуры GAC на сегодняшний день является наиболее мощным подходом к хранению совместно используемого кода.
Сам GAC, как бы это не было удивительно, представляет собой группу обычных файловых каталогов расположенных на диске компьютера. Их вы сможете найти по следующему адресу:
Если вы попытаетесь просмотреть содержимое данного каталога при помощи проводника (Explorer’a), то увидите список всех проинсталлированных строго именованных сборок. Подобный тому, что представлен на рисунке 10.
Рисунок 10
Но знайте, данный список не соответствует реальному строению GAC. Он формируется при помощи расширения оболочки проводника Windows.
Расширение оболочки для управления сборками в GAC
Список состоит из четырех колонок, которые предоставляют практически всю необходимую информацию о каждой из сборок.
Если данной информации окажется не достаточно, то вы всегда можете щелкнуть правой кнопкой мыши на интересующей вас сборке и из контекстного меню и выбрать пункт Properties.
Рисунок 11
Рисунок 12
На вкладке General, вы можете обнаружить поле CodeBase, значение которого с первого взгляда может несколько смутить. Поскольку в нём указывается путь до сборки, который может вести явно не GAC. На самом деле, данное поле указывает не текущее местоположение сборки, а то откуда производилась её установка в GAC. В случае с обычными сборками данное поле не представляет интереса. А вот если сборка перед использованием скачивалась из сети, мы сможет узнать точный URL адрес её базового местоположения.
Слева в дереве каталогов, под папкой assembly есть папка Download, в ней отображается содержимое кеша закаченных из сети сборок. Фактически данная папка ничего общего с GAC не имеет, поскольку храниться отдельно от него, в учетных записях персонально для каждого пользователя. При изучении данной папки, как раз и пригодиться поле CodeBase, закладки General. Скорее всего, на вашем компьютере папка Download будет пустовать. Удивляться этому не стоит, так как пока использование возможности хранения сборок в сети используется мало. Происходит это по двум причинам: во-первых, о данной возможности мало кто знает, во-вторых, распределённые сетевые приложения пока не пользуются большой популярностью.
На закладке Version, вы сможете узнать исчерпывающую информацию о версии продукта. Включающую: название компании, внутренне имя, региональную информацию, базовое имя файла, полное имя продукта, а так же его версию.
Расширение оболочки добавляет на панель инструментов малоприметную кнопу (рисунок 13), которая связана с пунктом меню Tools->Cashe Settings.
Рисунок 13
Выбрав пункт меню или нажав на кнопку панели инструментов, вы откроете окно, при помощи которого можно просмотреть общую статистику о глобальном хранилище сборок (рисунок 14).
Рисунок 14
В поле Cache space, отражен общий размер скачанных из сети сборок. А в нижней части окна, при помощи переключателя Download, вы можете установить максимальный размер хранилища скачиваемых сборок.
Расширение оболочки представляет собой COM компонент, который располагается в файле Shfusion.dll.
Реальное строение GAC
Изучить реальную структуру GAC можно несколькими способами:
Расширение оболочки подключается к проводнику при помощи фала Desktop.ini, располагающегося в каталоге assembly. Найдя данный файл в папке assembly, проводник автоматически вызывает расширение оболочки. Следовательно, убрав или переименовав данный файл, мы отключим расширение. Для этого следует выполнить следующие три команды.
После отключения расширения, структура каталогов GAC будет отображаться проводником в истинном виде (рисунок 15).
Рисунок 15
Если понадобиться включить расширение оболочки обратно, будет необходимо переименовать файл обратно. Правда при этом необходимо учесть, что Windows может автоматически создать файл Desktop.ini, который сначала будет необходимо удалить. Описанные операции можно выполнить при помощи следующих двух команд.
В каталоге assembly, содержаться четыре вложенных папки:
Структура папок GAC разделена на два вложенных уровня. На верхнем уровне представлены папки, отвечающие символьным именам сборок. Ниже приведено содержание каталога GAC, полученное при помощи команды dir.
При первом взгляде может возникнуть вопрос, а что происходит со сборками, имеющими одинаковые символьные имена. Не произойдёт ли конфликта, при их размещении в этих папках. Для того, чтобы ответить на данный вопрос, необходимо спуститься на один уровень ниже и заглянуть в одну из папок, перечисленных выше. Для этих целей выберем папки System и Strong (она создана мною в тестовых целях), список их содержимого приведён ниже.
На данном уровне происходит более детализированное разделение хранимых сборок. Теперь каталоги уже содержат информацию о версии, региональной принадлежности, а так же маркер открытого ключа. Имена каталогов на данном уровне формируются по следующему правилу: ВерсияСборки_РегиональныйИдентификатор_МаркерОткрытогоКлюча. Вся эта информация вкупе гарантирует уникальность конечного пути хранения сборки и исключает возможные конфликты. Внутри данных папок уже непосредственно содержаться искомые сборки.
Помимо самой сборки, в данном каталоге располагается файл __AssemblyInfo__.ini. Он содержит общую информацию о строгом имени сборки, а так же о начальном местоположении (CodeBase). Данный файл используется средой исполнения в целях оптимизации загрузки, а так же расширением оболочки для хранения информации CodeBase.
Из всего вышесказанного, можно заключить, что GAC является самоописываемой файловой структурой. Вся необходимая информация заключена, в именах папок. Дополнительных источников хранения информации, вроде реестра или каталогов Active Directory, не используется. При всей мощи хранилища GAC, его элегантная простота является революционной, ничего подобного ранее не существовало!
Инсталляция сборок в GAC
Всего существует четыре способа инсталляции сборок в GAC:
Но все они в итоге сводятся к банальному созданию необходимых в %windir%\assembly\GAC каталогов и копированию в них файлов.
Программистам Microsoft предлагает использовать утилиту gacutil.exe. Для работы с ней вам необходимо уметь пользоваться двумя основными ключами.
Для того, чтобы протестировать работу данной утилиты, инсталлируем в GAC сборку Strong.dll, созданную в предыдущем разделе. Для этого воспользуемся следующей командой.
Если все было сделано правильно, утилита должна сообщить об успехе инсталляции при помощи следующего сообщения
Отдельно хотелось бы прокомментировать работу ключа /U. Если в качестве параметра данного ключа указать только символьное имя сборки, то все сборки с таким символьным именем, не зависимо от других параметров строгого имени, будут удалены. Фактически будет удалён каталог %windir%\assembly\GAC\заданное_символьное_имя_сборки и все его содержимое. Такого режима использования ключа стоит избегать. Так как в данном каталоге могут оказаться сборки других производителей. Ведь совпадения на уровне символьных имён сборок вполне допускаются. Более предпочтительно использовать полное строгое имя сборки, такого вида
В этом случае будет удалена только необходимая сборка. И вы будете полностью застрахованы от нечаянного удаления чужих сборок.
И, наконец, мы добрались до последнего и самого интересного способа работы с GAC. Его работу мы изучим на примере инсталляции сборки Strong вручную. Первым делом создадим папку на верхнем уровне дерева каталогов GAC соответствующую символьному имени нашей сборки, то есть Strong. Сделать это можно при помощи любого стороннего файлового менеджера, или даже при помощи самого проводника отключив расширение оболочки. Но здесь для простоты и наглядности мы будет пользоваться утилитами командной строки.
Итак, для создания папки Strong, необходимо выполнить следующую команду.
Далее нам предстоит создать каталог второго уровня, который будет содержать в своём имени более детальную информацию о сборке. Для этого нам понадобиться знать её версию, региональную информацию, а так же маркер открытого ключа.
ПРИМЕЧАНИЕ
Каталогу необходимо дать имя по следующему правилу: ВерсияСборки_РегиональныйИдентификатор_МаркерОткрытогоКлюча. В нашем случае это будет выглядеть так.
Теперь необходимо скопировать в данный каталог нашу сборку.
Помимо самой сборки, во всех каталогах созданных стандартными средствами содержится файл __AssemblyInfo__.ini. Он используется только для оптимизации поиска информации. Если среда исполнения его не найдёт, то информация будет загружена напрямую из сборки. Мы данный файл создавать не будет, все будет прекрасно работать и без него.
Деинсталляция же сборки вручную производится простым удалением соответствующих каталогов и их содержимого.
Проверка целостности сборки при инсталляции в GAC
Любая сборка, инсталлируемая в GAC, обязана иметь цифровую RSA подпись, удостоверяющая её целостность. Если после последней компиляции сборки её содержимое случайно или преднамеренно изменилось, то проверка подписи сборки перед её инсталляцией не пройдёт. Это легко показать на примере. Возьмём уже созданную и соответственно подписанную сборку Strong.dll. Дизассемблируем её при помощи утилиты ildasm.exe, используя следующую команду.
Изменим её содержимое. Для простоты поменяем строку выводимую данной сборок на консоль.
После чего, скомпилируем сборку, при помощи данной команды, предварительно удалив настоящую сборку Strong.dll
В результате должна получиться сборка Strong.dll, весьма похожая на настоящую, за исключением конечно цифровой подписи RSA. Данная сборка просто не могла быть подписано, поскольку для этого требуется закрытая часть криптографического ключа, которой, как подразумевается, у нас не было.
Теперь попытаемся проинсталлировать данную сборку в GAC.
Но у нас, к счастью, ничего не выйдет. Поскольку не пройдёт проверка цифровой подписи. О чем с радостью сообщит утилита gacutil, при помощи следующих строк.
Использование строго именованных сборок
ПРЕДУПРЕЖДЕНИЕ
Здесь в качестве совместной мы будет использовать сборку Strong, созданную в одном из предыдущих разделов. Прежде чем создавать приложение, использующее совместную сборку, убедитесь в том, что она проинсталлирована в GAC. Лучше всего сделать это заранее ещё до начала создания проекта.
Во-первых, нам понадобиться приложение, к которому мы будем подключать строго именованную сборку. Создадим простое оконное приложение. Сделать это можно при помощи мастера, который доступен из меню: File->New Project.
Рисунок 16
Рисунок 17
Нам же для того, чтобы добавить связь со сборкой придётся воспользоваться кнопкой Browse, расположенной в правой части окна.
ПРИМЕЧАНИЕ
Здесь необходимо пояснить, что представляет собой добавление связи со сборкой в среде Visual Studio.NET. При разработке приложения сборка необходима на двух этапах:
После добавления ссылки на сборки, можно будет исследовать её содержимое при помощи Object Browser, который можно вызвать при помощи меню View->Other Windows->Object Browser. В дереве объектов серенькими прямоугольниками обозначены сборки, на которые установлены в проекте, среди них должна присутствовать сборка Strong (рисунок 18).
Рисунок 18
Заключительным этапом в создании нашего приложения, будет добавление кода использующего строго именованную сборку. Он будет состоять из одной строки.
Данный код следует добавить туда откуда он будет гарантированно вызван: в конструктор главной формы, в обработчик нажатия на кнопку и т.п.
ПРИМЕЧАНИЕ
Полный код приложения вы сможете найти в листинге 9.
При наборе кода, вы сможете насладиться технологией IntelliSence, подсказывающей содержание типов, прямо по ходу редактирования (рисунок 19).
Рисунок 19
На экране должно появиться диалоговое окно с надписью: “Hello World from test strong named assembly” (рисунок 20).
Рисунок 20
Код, выводящий данное диалоговое окно содержится в сборке Strong, которая располагается в GAC.
Поздравляю, вы создали первый общий сервис, расположенный в совместной сборке. Вы можете использовать его в любых своих приложениях.
Для того, чтобы убедиться в том, что данное диалоговое действительно выводиться сборкой расположенной в GAC, проведём эксперимент. Удалим сборку из GAC, после чего попытаемся запустить приложение.
В результате мы получим сообщение о том, приложение было остановлено при причине произошедшего необработанного исключения (рисунок 21).
Рисунок 21
Описание, произошедшего исключения указывает на то, что не был найдена сборка Strong. Это говорит о том, что сборка действительно подгружается из GAC.
Загрузка сборок, точно так же как и модулей, происходит по первому требованию. То есть при первом обращении сборке. Огромным плюсом такой политики подключения сборок, является существенное увеличение скорости загрузки приложения, а так же вероятная возможность работы приложения в отсутствии некоторых его компонентов.
Теперь напишем программу, использующую совместную сборку не прибегая к помощи, среды разработки. Что, впрочем, окажется не намного сложнее. Это будет консольное приложение код, которого приведён ниже.
Задача данного приложения заключается лишь в обращении к коду, расположенному в строго именованной сборке. Она реализуется единственной строкой функции Main. Откомпилировать данный исходный код в приложение, можно при помощи следующей команды
Здесь ключ /r задаёт связь с нашей строго именованной сборкой. При этом требуется, чтобы сборка находилась в распоряжении компилятора, то есть была в текущем каталоге или в одном из каталогов указанных в переменной Path. После компиляции надобность в данной сборке отпадёт и её можно будет смело удалить. На стадии компиляции сборка необходима для того, чтобы сделать правильные записи в манифесте программы о связи с данной сборкой, а так же проверить правильность используемых из неё типов.
Если заглянуть в манифест только, что созданного приложения, то там обнаружатся следующие строки, указывающие на связь со сборкой Strong.
Вкупе данная информация является строгим именем, и может быть записана в более компактном виде.
Компилятор командной строки для удобства автоматически подключает некоторые стандартные сборки. Они перечислены в файле csc.rsp, который храниться вместе с компилятором (csc.exe) в основном каталоге среды исполнения (%windir%\Microsoft.NET\xxxx). Содержимое данного файла представлено ниже.
Если в программе будет использован код одной из данных сборок, то она будет автоматически подключена в манифест. Если нет, то не будет. Вы можете добавить в данный файл собственные ссылки, либо подключить свою версию файла при помощи собаки.
Помимо ссылок на сборки, в rsp файле, могут быть указаны любые параметры командной строки компилятора. К примеру rsp файл может выглядеть так
Данный файл предписывает компилятору создать библиотечную сборку, использующую Strong.dll.
Внутренний формат имён
Загрузка дополнительных внешних сборок производится непосредственно перед обращением к ним. В общих чертах данных механизм описывался выше. Его работа возможна лишь благодаря продуманному внутреннему формату имён. Каждое имя среды исполнения, содержит в себе ссылку на сборку или модуль, в котором располагается тип, на который оно ссылается. К примеру, вызов метода Test из нашей сборки
транслируется компилятором в следующую инструкцию
Политика версий для строго именованных сборок
Для начала уясним самое главное: политика версий существует, только на уровне сборок и применяется только для строго именованных сборок. Сборка это наименьший и единственный элемент, которому может быть назначена версия. Все элементы внутри сборки версий не имеют, или если посмотреть с другой стороны имею версию родительской сборки.
ПРЕДУПРЕЖДЕНИЕ
Политика версий применяется только для строго именованных сборок.
Таким образом, политика версий действует только на сборки, а на другие программные элементы (классы, интерфейсы, и т.д.) влияния не оказывает.
Конечно, можно создать собственную искусственную политику версий на уровне типов, но в большинстве, случаев это не будет оправдано.
Информация о версии
Информация о версии сборок состоит из четырёх чисел. Первые два называются основной частью версии, а вторые дополнительной. Плюс каждое из них имеет собственное имя, перечислим их в соответствии с порядком следования в версии: Major, Minor, Build и Revision (рисунок 22).
Рисунок 22
Такое количество чисел в версии приложения может показаться странным. Действительно, для создания обычных программ оно ни к чему. Но при разработке больших корпоративных продуктов, включающих множество библиотек и приложений, подобное строение информации о версии не только целесообразно, а попросту необходимо. Так как в подобных проектах конфликты возникают не только на уровне различных версий приложений, но даже на уровне промежуточных ревизий и построений.
Информация о версии приложения разбита на две части вовсе не случайно. При поиске необходимой сборки, политика требует точного совпадения лишь основной версии сборки. А дополнительная версия используется для поиска наиболее свежей сборки. То есть будет загружена сборка с наибольшими номерами построения и ревизии.
Фактически, основная часть информации о версии используется для разграничения версий, а дополнительная для динамического обновления уже существующих версий. Дополнительная часть версии весьма удобна для создания сервис паков и заплат безопасности.
Такая политика требует обратной совместимости только от библиотек, с одинаковыми основными версиями. Сие означает, что, поменяв основную часть информации о версии, будь то Major или Minor число, вы больше не обязаны поддерживать старые типы и другие программные элементы. Новые сборки попросту не будут рассматриваться средой исполнения в виде кандидатов на загрузку, из более ранних версий приложений.
Работаем с информацией о версии
Для работы с информацией о версии на уровне исходного кода предназначен атрибут
Использование данного атрибута отличается от обычных и требует специального синтаксиса. В языке C# это выглядит так.
В качестве единственного параметра данного атрибута вы должны в виде строки указать версию сборки, для которой указывается данный атрибут.
Версию для данного атрибута можно указывать не полностью. Существует возможность
частичного задания версии, путем замены дополнительной части информации версии звёздочками. В этом случае номер построения будет равен количеству дней прошедших с января 2000 года, а номер ревизии будет равен количеству секунд, прошедших с полуночи, делённых на два. Такая, казалось бы, странная схема формирования информации о версии, обеспечивает уникальность версии для каждого построения приложения.
Так же существует возможность неполного задания информации о версии, при которой некоторые из чисел могут вообще не указываться и будут дополняться автоматически. Подытожим все способы задания версии в таблице 2.
Версия в исходном коде
Реальная версия
1
1.0.0.0
1.1
1.1.0.0
1.1.*
1.1.[количество дней прошедших с января 2000].[количество секунд прошедших с полуночи делённое на два]
1.1.1
1.1.1.0
1.1.1.*
1.1.1. [количество секунд прошедших с полуночи делённое на два]
1.1.1.1
1.1.1.1
Таблица 2
За преобразование строки версии заданной не в полном формате, отвечает сам компилятор. Вполне возможно, что при использовании компилятора стороннего производителя, данные правила выполняться не будут. Перед тем как использовать данную возможность необходимо, изучить документацию, прилагаемую к компилятору.
Вначале я решил, что информация о версии всё-таки формируется не компилятором, а самим атрибутом AssemblyVersionAttribute. Но, заглянув в код атрибута, я понял, что это не так.
Как видите, здесь даже не пахнет вычислениями даты и времени, которые обязательно должны были быть.
Дополнительная информация о версии
Помимо сухой числовой информации о версии существует возможность задать дополнительную информацию, в виде понятной каждому строки. Для этого предназначен атрибут System.Reflection.AssemblyInformationalVersion. Его применение на языке C# выглядит так.
Информацию, заданную при помощи данного атрибута можно просмотреть на вкладке Version, в диалоге свойств файла, выбрав пункт Product Version.
Рисунок 23
Данная информация очень полезна, если требуется наглядно показать различие версий приложений. К примеру, таким способом можно указать на различие между Education Edition и Professional Edition.
Технология прямого запуска
В рамках политики управления версиями существует один малозаметный нюанс, который тем не менее может вызвать очень неприятные проблемы. Речь пойдет о совместном использовании разных версий одинаковых сборок.
Различные части одного приложения, могут запросить две «одинаковые» сборки разных версий. Поскольку они могут быть запрошены косвенно, через зависимые сборки или модули, то само приложение может ничего не знать об этой ситуации. На удивление, проблем с использованием и подгрузкой самих сборок возникнуть не должно. Среда исполнения автоматически загрузит необходимые сборки, прозрачно для самой программы.
Рисунок 24
Поясним рисунок. Обратить внимание стоит, прежде всего, на сборку yyy.dll, её две разные версии одновременно используются приложением. Одна запрашивается непосредственно самой программой, а вторая косвенно через сборку x.dll. Обе эти сборки используются одновременно и не знают о существовании друг друга, несмотря на то, что обе загружены в одном адресом пространстве.
Хотя технология прямого запуска и предполагает изоляцию различных версий сборок друг от друга, тем не менее, здесь кроется одна очень неприятная и страшная проблема. При использовании (даже неявно) двух сборок различных версий, весьма вероятны конфликты при использовании объектов операционной системы, для которых возможен совместный доступ. Простейшим примером таких объектов являются обычные файлы. Доступ к ним производится при помощи их символьных имён, соответственно если две версии одной и той же библиотеки попытаются одновременно использовать один и тот же файл, то коллизии, скорее всего, избежать не удастся. Либо не будут работать обе библиотеки, или же одна из них, здесь все будет зависеть от способа доступа к файлу. Файлы, конечно же не являются единственными проблемными объектами, на самом деле их множество. Перечислим некоторый из них: файлы, именованный файлы проецируемые в память, сокеты, порты, события, семафоры, ветки реестра, а так же огромное множество объектов, которые перечислять не имеет смысла. Поскольку многие косвенно взаимодействуют с другими объектами, которые в свою очередь могут оказаться проблемными. Для того, чтобы точно знать все подводные камни, необходимо очень хорошо разбираться в устройстве операционной системы, а так же во внутренней реализации FCL. Разработчики FCL, кончено же старались огородить пользователей от этих проблем, автоматизировав по возможности работу со всеми именованными и совместными объектами. Но полностью этого сделать принципиально не возможно. Посему при создании совместных сборок, которые будут, использоваться несколькими приложениями, необходимо быть предельно осторожным.
ПРЕДУПРЕЖДЕНИЕ
Проблемы могут возникнуть не только при использовании различных версий сборок в границах одного приложения, но даже и для одной версии сборки, но уже в рамках всех системы. Многие объекты операционной системы могут использоваться совместно на уровне всей системы. Простейшим примером таких объектов являются файлы.
В качестве совета можно порекомендовать сделать имена всех совместных объектов уникальными. Осуществить это можно несколькими способами, либо динамически генерировать имена, по ходу исполнения программы, используя динамическую генерацию GUID (CoCreateGuid), либо включать в имена версию сборки или даже её полное строгое имя, обязательно включая маркер открытого ключа.
ПРИМЕЧАНИЕ
Таблица импорта – это специальная секция внутри PE, файла в которой указаны библиотеки, необходимые для работы данного приложения или динамической библиотеки.
Данная заглушка отличается от предыдущей лишь тем, что вызывает функцию _CorDllMain, которая, впрочем, так же располагается в библиотеке mscoree.dll.
ПРИМЕЧАНИЕ
У многих может возникнуть вопрос почему, я этот код называю заглушкой, а не функцией. Делаю, я это намерено, поскольку данный код не имеет основного признака функции, он не имеет пролога, а так же не возвращает управление (На уровне Ассемблера это делается при помощи инструкции ret).
Динамические библиотеки на основе сборок
Код будущей библиотеки представлен ниже.
Код библиотеки содержит единственную функцию, доступ к которой будет возможен извне.
ПРИМЕЧАНИЕ
Для компиляции данного исходного кода в конечную библиотеку нам понадобиться дополнительный файл с определениями экспортируемых функций. Это Some.def и он представлен ниже.
Для компиляции исходного кода воспользуемся двумя следующими командами, поочередно вызывающими компилятор и линковщик.
Первая скомпилирует наш исходный код в объектный файл, а вторая соберет из него динамическую библиотеку. В результате должна получиться динамическая библиотека, в таблице экспорта которой указана единственная функция – Test.
Теперь нам предстоит написать программу, использующую данную сборку. Её задача будет заключаться в загрузке библиотеки и обращении к экспортируемой из нее функции. Исходный код программы приведён ниже.
Для компиляции необходимо воспользоваться следующей командой.
В результате работы созданной программы на консоли появятся две строки
Обе они выводятся одной и той же функцией, расположенной в динамической библиотеке. Первый раз она вызывается при помощи стандартных средств языка, а второй напрямую используя ассемблерную инструкцию call.
При поверхностном осмотре ничего удивительного в работе данного приложения, нет. Оно всего лишь выводит на консоль две строки. Но, давайте заглянём ему под капот. Для этого дизассемблируем библиотеку Some.dll, функция Test которой и выводит на консоль искомую строку. Код данной функции, полученный путём дизассемблирования, представлен ниже.
Если быть до конца честным, то мы, конечно же, не вызывали данную функцию напрямую. На самом деле мы обращались к некой заглушке, которая была указана в таблице экспорта библиотеки. Но если посмотреть на неё при помощи машинного дизассемблера, то ситуация становиться еще запутанной. Вот её код.
Интересно, не правда ли? Вызов в никуда, вот как я это обычно называю.
Локализация приложений при помощи сборок
Среда исполнения имеет в своем арсенале мощные, а главное полностью автоматизированные средства локализации приложений. Основным средством локализации являются сателлитные сборки (satellite assembly). Такие сборки содержат регионально адаптированные ресурсы: строки, изображения и другую регионально зависимую информацию. Microsoft настоятельно не рекомендует располагать в сателлитных сборках код, они предназначены лишь для хранения ресурсов.
В локализованных приложениях предполагается использовать двух уровневую модель, разделяя код и интерфейсную часть. Код размещается в отдельных монолитных сборках, которые будут едины для всех региональных версий приложения. А вся информация, связанная с интерфейсом пользователя и которая нуждается в локализации, помещается в сателлитные сборки. Которые будут автоматически подключены средой исполнения, в соответствии с региональными стандартами. В данном разделе мы создадим приложение локализованное для русского и английского языка, основанное на сателлитный сборках. Но перед этим необходимо объяснить теоретические основы.
ПРИМЕЧАНИЕ
Справедливо может возникнуть вопрос, для чего кроме языка задавать еще и регион? Региональная информация, так построенная из-за существования многочисленных наречий одних и тех же языков. Порой люди, разговаривающие на разных наречиях одного и того же языка, не могут даже понять друг друга. Впрочем, идентификатор региона является опциональным и его можно не задавать.
Перечислять все региональные идентификаторы не имеет смысла, поскольку их более двухсот. Но наиболее интересные из них представлены в таблице 3.
Пугаться присутствия числовых идентификаторов во втором столбце не стоит, поскольку работать с ними рядовым программистам не придётся, они используются лишь во внутренних целях среды исполнения.
Теперь, когда мы знакомы с региональными идентификаторами, создадим локализованное приложение. Оно будет крайне примитивно, его основной целью будет показать возможности локализации при помощи сателлитных сборок. Код самого приложения представлен ниже.
Теперь нам необходимо создать две версии строковых ресурсов. Первую регионально нейтральную, с которой приложение будет работать по умолчанию, а вторую русскую. Первая будет размещаться в каталоге приложения, в файле MyStrings.txt. Данный файл в нашем случае будет состоять всего из одной строки.
Первый параметр определяет идентификатор строки, а второй саму строку. Прежде чем использовать данный файл строк, его необходимо скомпилировать в файл ресурсов. Сделать это можно при помощи следующей команды.
Теперь можно скомпилировать наше основное приложение, встроив в его исполняемый файл только что созданные ресурсы.
Прежде чем запускать и тестировать приложение необходимо создать для него русские ресурсы. Они обязательно должны располагаться в подкаталоге ru нашего приложения. Это обязательное требование среды исполнения, поскольку именно там она будет искать сателлитную dll, соответствующую русскому языку. Если говорить более обобщенно, то ресурсы должны располагаться в подкаталоге, имя которого совпадает с региональным идентификатором искомого языка.
Итак в каталоге ru, необходимо создать файл MyStrings.ru.txt. Как видите, в его имя так же должен входить региональный идентификатор. К данному файлу помимо этого предъявляется еще одно требование, он обязательно должен быть в формате Unicode.
Идентификатор строки остался все тот же, но зато строка уже локализована. Из данного файла, точно так же как из предыдущего необходимо создать файл ресурсов при помощи утилиты resgen.
А вот из него уже предстоит создать сателлитную сборку. Сделать это можно при помощи утилиты al.
Теперь наше приложение готово к запуску и тестированию. Результатом его работы должны стать две следующие строки, выведенные на консоль.
Строение локализованного приложения можно схематично изобразить так (рисунок 25).
Рисунок 25
Кроме саттелитных сборок локализацию можно проводить непосредственно через файлы ресурсов. Но данная тема выходит за рамки статьи, и поэтому мы её рассматривать не будем.
В заключение данной темы необходимо упомянуть о возможности задания региональной принадлежности программно, при помощи атрибута AssemblyCulture. Приведём пример.
Данный атрибут позволяет задать региональную принадлежность сборки с кодом. Но поскольку Microsoft не рекомендует располагать в регионально зависимых сборках код, то данным атрибутом следует пользоваться только в крайних случаях.
Конфигурирование политики загрузки сборок
То есть, если приложение располагается в файле Some.exe, то его конфигурационный файл должен называться Some.exe.config. Для наглядности приведём пример простого конфигурационного файла.
Элементы, находящиеся на данной схеме левее других, должны быть вложены в своих правых соседей. К примеру, тег должен быть вложен в тег и т.д.
Далее мы подробно рассмотрим каждый из перечисленных тегов.
Данный тег имеет следующий прототип
Тег предназначен для разработчиков. В случае если его параметр developerInstallation установлен в true, то среда исполнения помимо обычных каталогов попытается искать сборки в соответствии с путями, указанными переменной среды окружения DEVPATH.
Сам данный тег является пустым, и служит лишь для объединения вложенных подтегов.
Рассмотрим по порядку все его подтеги.
Данный тег позволяет указать дополнительный список подкаталогов, в которых будет производиться поиск сборок используемых в персональном режиме.
ПРИМЕЧАНИЕ
Сборками, используемыми в персональном режиме, называются те, которые располагаются в одном с приложением каталоге или в одном из подкаталогов.
Для примера, возьмем одно из созданных нами ранее приложений, которое использует сборку. Пусть это будет сборка Strong.dll, которая располагается в одном с приложением каталоге. А теперь переместим данную сборку, в предварительно созданный, подкаталог asm (Имя подкаталога, безусловно, выбрано абсолютно случайно). И попробуем запустить приложение. Естественно попытка запуска с треском провалится и мы увидим на экране диалоговое окно сообщающее о произошедшем во время запуска исключении.
Рисунок 26
Обратите внимание на тип произошедшего исключения, он указан в верхней части окна (System.IO.FileNotFoundException). Для того, чтобы полностью разобраться в чем дело, посмотрим детальную информацию об исключении.
ПРИМЕЧАНИЕ
Для того, чтобы просмотреть дополнительную информацию об исключении, необходимо нажать на кнопку Yes. После чего ознакомиться с ней в центральной части появившегося окна.
Рисунок 27
ПРИМЕЧАНИЕ
Далее можно смело нажимать на кнопку Break, которая приостановить исполнение приложения.
Поскольку мы использовали приложение консольного типа, то вся информация об исключении будет автоматически выведена на консоль. Пример лога сбоя представлен ниже.
Если обратиться к последним строкам данного лога то можно заметить, что среда исполнения пыталась загрузить сборку из дополнительных подкаталогов приложения, а так же из файлов с другими именами. Данный процесс называется зондированием: от английского probing. (He путать с зомбированием!).
Во время зондирования среда исполнения учитывает значение параметра privatePath, тега probing. Следовательно, нам необходимо лишь указать каталог asm, в данном параметре. Конфигурационный файл будет выглядеть так:
После создания данного файла в каталоге нашего приложения, загрузчик среды исполнения без труда найдет необходимую сборку и запустит наше приложение.
Данный тег позволяет ассоциировать сокращенное имя сборки с полным строгим именем, для удобства использования в программе. Параметры тега имеют следующий смысл
К примеру, сделаем ассоциацию для сборки Strong.
Теперь везде в нашем приложении можно использовать сокращенное имя Strong, вместо полного, строгого. После введения связи, вызов
будет рассмотрен средой исполнения как.
Параметры имеют следующий смысл:
Данный тег позволяет принудительно изменить политику управления версиями необходимой сборки. Вы указываете старую версию сборки и новую, которая будет подгружаться вместо неё. К примеру, так:
Теперь если приложение запросит сборку Strong версии 1.0.0.0, то ничего не зная об этом, получит сбоку версии 2.0.0.0.
Этот тег является наиболее интересным из описываемых здесь. Его прототип:
Он позволяет задавать произвольное местоположение зависимой сборки. При этом его параметры играют следующую роль:
Причем в качестве значения параметра href, может быть указан сетевой адрес. Продемонстрируем это на красочном примере.
Данный конфигурационный файл предписывает среде исполнения загружать сборку Strong, с адреса http://lexa:81/one/Strong.dll, по протоколу http, через 81й порт. Где lexa, это имя моей рабочей станции.
Для того, чтобы протестировать работу данного конфигурационного файла необходимо установить Web сервер для локального тестирования или выложить сборку на один из серверов в Интернет. У меня на рабочей станции стоят два web-сервера IIS и Apache. А для того, чтобы избежать конфликтов, они разнесены по разным портам 81 и 82 соответственно. Именно по этому я указывал 81 порт в адресе ссылки на сборку. Ниже приведены краткие рекомендации по установке и настройке данных серверов.
Настройка IIS сервера
IIS сервер входит в стандартную поставку операционных систем класса Windows NT, начиная с 2000 версии. Правда он может не входить в начальный пакет установки. Для того, чтобы его инсталлировать, вам придется воспользоваться мастером добавления дополнительных компонент Windows из панели управления (Add or Remove Programs->Add/Remove Windows Components). После установки сервера в корне диска C: появиться папка IntePub. В ней располагается каталог wwwroot – это корневая папка www-сервера, файлы которой доступны по протоколу HTTP. В ней необходимо создать папку one, в которой поместить сборку Strong.dll. После произведённых манипуляций сборка будет доступна по одному из следующих адресов.
Порт 80 можно не указывать, поскольку он по умолчанию используется при работе с протоколом http.
Так же можно воспользоваться специальным апплетом управления Web-сервером ISS (Control Panel->Administrative Tools->Internet Information Services), который позволяет производить более тонкую настройку.
Настройка Apache сервера
Apache сервер настраивается несколько сложнее чем IIS, правда, если вы будете следовать приведённым здесь рекомендациям, то проблем возникнуть не должно.
ПРИМЕЧАНИЕ
Apache сервер совершенно безвозмездно можно загрузить по следующему адресу http://apache.org/. Не забудьте, что вам нужна версия для Windows.
Данный Web сервер не имеет интерактивной среды настройки, все параметры придётся задавать при помощи специального конфигурационного скрипта. Он располагается в файле: …\ conf\httpd.conf, относительно корня сервера. Нам предстоит создать виртуальную связь папки на сервере, с каталогом на диске. Для этого необходимо добавить следующую строку.
Где первый параметр определяет имя виртуальной директории на сервере, а второй путь до реальной папки на жестком диске.
ПРИМЕЧАНИЕ
Каталог со сборкой может располагаться в любом месте жесткого диска, а не только в одном из подкаталогов Apache сервера.
Естественно в данную папку необходимо поместить сборку Strong.
Проверяем возможность автоматической загрузки сборок из сети
Теперь, после того, как настроен Web-сервер, и на нём располагается наша сборка, необходимо проверить возможность загрузки сборок по URL. Используем для этого одно из приложений использующих сборку Strong и конфигурационный файл приведенный выше. Только перед проверкой необходимо удалить сборку из каталога приложения, чтобы её загрузка гарантированно происходила из сети.
ПРИМЕЧАНИЕ
Для ссылок на локальный сервер помимо его реального имени, вроде lexa,можно использовать специально зарезервированные имена. Это localhost, а так же все IP адреса из диапазона 127.0.0.1-127.0.0.255. То есть ссылки могут выглядеть следующим образом.
Для того, чтобы задать URL для файла располагающегося на жестком диске воспользуйтесь псевдо протоколом file://. К примеру так
При первом запуске приложения сборка будет скачана из сети по указному адресу и добавлена в кеш. Он располагается по следующему адресу на вашем жестком диске
При повторных запусках сборка будет загружаться уже с жесткого диска компьютера, что уменьшит сетевой трафик. Вы можете просмотреть список скачанных сборок используя расширение оболочки, заглянув в папку %WINDIR%\assembly\dowload.
В заключение данного раздела, необходимо упомянуть, об одном неприятном моменте. При указании сетевого URL избегайте скачивания по протоколу ftp, поскольку в ранних версиях среды исполнения данная возможность попросту не работает. Причем, что странно запросы от среды исполнения, к ftp серверу исправно поступают, в чем можно убедиться исследовал его лог файлы. И даже происходит их скачивание (судя по логам), но подключение данных сборок по неведомым мне причинам не происходит.
Редактирование конфигурационных файлов при помощи встроенных средств среды исполнения
Конфигурационные файлы имеют формат XML, поэтому их редактирование при помощи обычных текстовых редакторов крайне неудобно. К тому же необходимо либо помнить все нужные теги, либо постоянно иметь под рукой документацию.
Рисунок 28
При помощи них вы можете легко настроить все описанные выше параметры. Правда есть одно но, данный аплет выполнен в виде оснастки консоли управления, которая присутствует только в системах класса NT. А посему, пользователям операционных систем вроде Windows 98 конфигурационные файлы придётся редактировать руками.
Процесс поиска сборок средой исполнения
Рассмотрим каждый из данных пунктов более подробно.
Анализ конфигурационного файла
В первую очередь после запроса приложения на загрузку сборки среда исполнения производит поиск и анализ конфигурационного файла. Если конфигурационный файл будет найден, то среда исполнения, скоординирует поведение своего загрузчика в соответствии с указанными в нём параметрами.
Ранее мы рассматривали, как эти параметры могут повлиять на загрузку сборок.
Проверка ранее загруженных сборок
Перед тем, как производит поиск сборки на диске или в сети, среда исполнения проверит, а не была ли сборка уже загружена в среду. Данный процесс основан на механизме сравнения MVID, который позволяет точно идентифицировать сборку и её модули.
Если сборка уже загружена, то среда исполнения просто устанавливает на неё соответствующие внутренние ссылки. Если нет, то среда исполнения приступает к следующему этапу поиска.
Поиск в GAC
Если сборка строго именованная, то среда исполнения сначала пытается отыскать её в GAC. Она обращается к менеджеру глобального хранилища сборок, расположенному в недрах динамической библиотеки fusion.dll. А он уже в свою очередь производит физический поиск сборки. Если это не привело к успеху, то загрузчик среды исполнения пытается отыскать сборку в каталоге приложения. Данный процесс подробно описан в следующем разделе.
Поиск файлов сборок в каталоге приложения
Сначала поиск сборок будет производиться в лоб, в основном каталоге приложения по реальному имени сборки. Если она не будет найдена, то загрузчик среды исполнения запустит процесс зондирования. Дабы не усложнять процесс описания этого механизма, приведём отчет неудачной попытки поиска сборки Strong.dll
Достаточно интересным моментом здесь является то, что среда исполнения варьирует расширение файла от DLL до ЕXE.
Просмотр логов загрузки сборок и исключений при помощи утилиты Fuslogvw.exe
Рисунок 29
ПРИМЕЧАНИЕ
По умолчанию запись отчетов отключена, что логично. Простым пользователям они не нужны и лишь будут занимать лишнее место на диске. Для того, чтобы включить запись логов необходимо установить значение параметра
равным 1 (данный параметр имеет тип DWORD). Сделать вы это можете либо при помощи всем нам до боли знакомой утилиты regedit или автоматически, прибегнув к помощи следующего reg файла.
После внесения необходимых изменений в реестр, станет возможным просмотр логов при помощи данной утилиты.
Для того, чтобы в списке главного окна утилиты отображались логи сбоев, необходимо установить галочку напротив переключателя Log Failures.
Ниже приведен пример лога сбоя. Для удобства, все комментарии вставлены прямо в его тело, в стиле C подобных языков (// или /**/).
Более подробно данный лог рассматриваться не будет, поскольку вся необходимая информация указана в комментариях, расположенных в его теле.