Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C# часть 2.doc
Скачиваний:
0
Добавлен:
01.03.2025
Размер:
1.3 Mб
Скачать

Глава 18. Работа со сборками

  • Обзор сборок

  • Данные в декларации

  • Преимущества сборок

  • Упаковка сборки

  • Развертывание сборки

  • Управление версиями сборки

  • Создание сборок

  • Создание сборок из нескольких модулей

  • Совместно используемые сборки

  • Работа с глобальным кэшем сборок

  • Просмотр кэша

  • Управление версиями сборок

  • QFE и политика управления версиями по умолчанию

  • Создание файла конфигурации для безопасного режима

В этой главе описаны наиболее важные преимущества сборок (assemblies), включая упаковку компонентов .NET и управление их версиями. Вы также узнаете, как создавать однофайловые и многофайловые сборки с помощью утилиты Assembly Generation (al.exe), создавать совместно используемые сборки, применяя утилиту Strong Name (sn.exe), просматривать глобальный кэш сборки с помощью Assembly Cache Viewer (shfu-sion.dll) и как утилита Global Assembly Cache (gacutil.exe) помогает манипулировать кэшем сборки. В завершение мы рассмотрим несколько примеров и увидим, как обстоят дела с управлением версиями и как сборки наряду с политикой управления версиями .NET избавляют от "ужасов DLL".

  Обзор сборок

В главе 16 сборки описаны как физические файлы, состоящие из нескольких РЕ-файлов (portable executable), генерируемых компилятором .NET. В контексте той главы это определение было приемлемым, но на самом деле сборки сложнее. Вот более полное определение: сборка является упаковкой, включающей декларацию (manifest), один или несколько модулей и (не обязательно) один или несколько ресурсов. Сборки позволяют семантически группировать функциональные единицы в единый файл для решения задач развертывания, контроля версий и сопровождения.

Все РЕ-файлы, работающие в исполняющей среде .NET, входят в ту или иную сборку. Компилируя приложение компилятором С#, вы на самом деле создаете сборку. Вы можете и не отдавать себе в этом отчета, если вы намеренно не пытались разместить несколько модулей в единой сборке или задействовать преимущества характерных для сборок функций, таких как управление версиями. Однако важно понимать, что каждый раз, компонуя ЕХЕ или DLL (с помощью переключателя /t:librаrу), вы создаете сборку с декларацией, которая предоставляет описание сборки для исполняющей среды .NET. Кроме того, вы можете создать модуль (применив переключатель /tmodule), который на самом деле является DLL (с расширением .netmodule) без декларации. Иначе говоря, хотя логически это все равно DLL, она не принадлежит к сборке и должна быть добавлена к сборке с помощью переключателя /odd-module при компиляции приложения или утилиты Assembly Generation. Ниже вы увидите, как это делается (см. раздел "Создание сборок").

  Данные в декларации

Декларацию сборки можно хранить двумя способами. Если вы скомпилировали автономное приложение или DLL, декларация будет встроена в результирующий РЕ. Это называется однофайловой сборкой (single-file assembly). Можно сгенерировать и многофайловую сборку (multifile assembly), у которой декларация существует в виде отдельной сущности в составе сборки или прикрепляется к одному из модулей сборки.

Определение сборки в немалой степени зависит от того, как вы ее используете. С точки зрения клиента, сборка — это совокупность именованных модулей определенных версий, экспортируемых типов и (не обязательно) ресурсов. С точки зрения создателя сборки, это средство упаковки связанных модулей, типов и ресурсов, при этом экспортируется только то, что клиент может использовать. Отсюда следует, что именно декларация определяет соответствие между деталями реализации сборки и тем, что предназначено для использования клиентом. В декларации хранятся:

  • имя сборки текстовое представление имени сборки;

  • информация о версии эта строка содержит четыре части: старший (major) и младший (minor) номер версии, номер ревизии (revision) и компоновки (build);

  • совместно используемое имя (не обязательно) и подписанный хэш сборки эта информация имеет отношение к развертыванию сборок (см. раздел "Развертывание сборок");

  • файлы список всех файлов в составе сборки;

  • ссылки на внешние сборки это список всех внешних сборок, на которые есть прямые ссылки в декларации сборки;

  • типы это список всех типов, содержащихся в сборке с указанием модуля, содержащего тип; именно эти данные использованы в примере отражения из главы 16 (он проходит по всем типам сборки);

  • права доступа список явно запрещенных прав доступа к сборке;

  • пользовательские атрибуты о создании пользовательских атрибутов см. главу 8; как и в случае типов, пользовательские атрибуты хранятся в декларации сборки для ускорения доступа при отражении;

  • информация о продукте сюда входит название компании (Company), товарный знак (Trademark), название продукта (Product) и сведения об авторских правах (Copyright).

  Преимущества сборок

Использование сборок приносит разработчику массу преимуществ, включая возможности упаковки, развертывания и управления версиями.

  Упаковка сборки

Одним из преимуществ упаковки нескольких модулей в единый физический файл — повышение производительности. Когда вы создаете и развертываете приложение, применяя многофайловые сборки, исполняющей среде .NET требуется загрузить только нужные модули. В результате уменьшается размер рабочего пространства приложения.

  Развертывание сборки

Наименьшей единицей развертывания в .NET является сборка. Вы можете создать модуль .netmodule с помощью переключателя /t:module, но для его развертывания его нужно включить в состав сборки. Кроме того, хотя так и тянет сказать, что сборки являются средствами развертывания приложений, формально это не верно. Более точно рассматривать сборки в .NET как форму развертывания классов (во многом похожую на DLL в Win32), где единое приложение может состоять из многих сборок.

Поскольку сборки являются самоописывающимися, проще всего их развернуть, скопировав в нужную папку. Когда же вы попытаетесь запустить приложение, содержащееся в сборке, декларация проинформирует .NET о модулях, которые хранятся в сборке. Кроме того, сборка содержит ссылки на все внешние сборки, нужные приложению.

Чаще всего для развертывания применяют закрытые сборки (private assemblies), т. е. сборки, которые копируются в папки, не используемые совместно. Как задать закрытую сборку? Это установлено по умолчанию и происходит автоматически. Если же вы захотите явным образом сделать сборку совместно используемой (shared assembly), читайте ниже раздел "Создание совместно используемых сборок".

  Управление версиями сборки

Еще одно крупное преимущество сборок — встроенное управление версиями, знаменующее собой конец "ада DLL". Речь идет о ситуации, когда одно приложение затирает DLL, нужную другому, обычно записывая поверх нее более раннюю версию этой же DLL, выводя из строя первое приложение. Хотя формат файлов ресурсов Win32 допускает наличие ресурса, определяющего версию, ОС не контролирует версии. Ответственность за это целиком и полностью несут прикладные программисты. Для решения этой проблемы в декларацию включена информация о версии сборки, а также список сборок, на которые ссылается данная сборка, с указанием их версий. Такая архитектура позволяет исполняющей среде .NET гарантировать поддержку политик управления версиями и продолжение функционирования приложения даже после установки в систему более новых, несовместимых версий совместно используемых DLL. Управление версиями описано в соответствующем разделе.

  Создание сборок

Если вы создаете DLL с переключателем /t:library, то не сможете добавить ее к другой сборке. Дело в том, что компилятор автоматически генерирует декларацию для этой DLL, которая поэтому сама является сборкой. Чтобы увидеть это в действии, рассмотрим пример. У нас есть DLL (ModulelServer.cs), у которой есть фиктивный тип Module!Server.

11 Module1Server.cs

// компоновка со следующими переключателями командной строки:

// esc /t:library Module1Server.cs

public class ModulelServer

{

}

Клиентский код в дальнейшем ссылается на эту DLL (Module 1-Client.cs):

// Module1ClientApp.cs

// компоновка со следующими переключателями командной строки:

// esc Module1ClientApp.cs /г:Module1Server.dll

using System;

using System.Diagnostics;

using System.Reflection;

class ModulelClientApp {

public static void Main() {

Assembly DLLAssembly = Assembly.6etAssembly(typeof (ModulelServer));

Console.WriteLine("Module1Server.dll Assembly Information");

Console.WriteLine("t" + DLLAssembly);

Process p = Process.GetCurrentProcessO; string AssemblyName = p.ProcessName + ".exe"; Assembly ThisAssembly = Assembly.LoadFrom(AssemblyName); Console.WriteLine("Module1Client.exe Assembly Information"); Console.WriteLine("t" + ThisAssembly); } }

А теперь допустим, что вы скомпоновали эти два модуля, используя переключатели:

esc /t:library ModulelServer.cs

esc ModulelClientApp.cs /r:Module1Server.dll

Если запустить программу, мы увидим информацию, доказывающую, что ЕХЕ и DLL существуют в своих собственных отдельных сборках:

Module1Server.dll Assembly Information

ModulelServer, Version=0.0.0.0, Culture=neutral,

PublicKeyToken=null Module1Client.dll Assembly Information

ModulelClient, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null

На самом деле, если бы вы изменили модификатор доступа класса ModulelServer с public на internal, клиентский код перестал бы компилироваться, так как по определению модификатор доступа internal указывает, что модифицируемый тип доступен для другого кода только из той же сборки.

  Создание сборок из нескольких модулей

Поместить оба модуля из нашего примера в одну сборку можно двумя способами. Во-первых, можно изменить переключатели, которые используются с компилятором:

// Module2Server.cs

// компоновка со следующими переключателями командной строки:

// esc /t:module Module2Server.cs

internal class Module2Server

{

}

Обратите внимание, что теперь мы можем указать модификатор доступа internal, при использовании которого класс доступен лишь коду в пределах этой сборки.

// Hodule2ClientApp.es

// компоновка со следующими переключателями командной строки:

// esc /addmodule:Module2Server.netmodule Module2ClientApp.cs

using System;

using System.Diagnostics;

using System.Reflection;

class Module2ClientApp {

public static void MainQ {

Assembly DLLAssembly =

Assembly.GetAssembly(typeof(Module2Server)); Console.WriteLine("Module1Server.dll Assembly Information"); Console.WriteLine("t" + DLLAssembly);

Process p = Process.GetCurrentProcessO; string AssemblyName = p.ProcessName + ".exe"; Assembly ThisAssembly = Assembly.LoadFrom(AssemblyName); Console.WriteLine("Module1Client.dll Assembly Information"); Console.WriteLine("t" + ThisAssembly); } }

Обратите внимание на то, как компонуются Module2Server.cs и Modu-le2Client.exe:

esc /t:module Module2Server.cs

esc /addmodule:Nodule2Server.netmodule Module2Client.cs

Прежде всего вы должны удалить переключатель /г, поскольку он служит только для ссылки на сборки, а сейчас оба модуля будут находиться в одной сборке. Далее вы должны вставить переключатель /addmodule, который указывает компилятору, какие модули добавить к создаваемой сборке.

При компоновке и запуске приложения теперь получатся следующие результаты:

Module1Server.dll Assembly Information

Module2Client, Version=0.0.0.0, Culture=neutral,

PublicKeyToken=null Module1Client.dll Assembly Information

Module2Client, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null

Сборку можно создать и с помощью Assembly Generation. Эта утилита принимает один или несколько файлов — модулей .NET (содержащими MSIL) или файлов ресурсов и образов. На выходе получается файл с декларацией сборки. Так, чтобы объединить несколько DLL, которые вы хотели бы распространять как единый файл и контролировать их версии как у единого целого, обратитесь к услугам Assembly Generation. Если ваши DLL называются A.DLL, В.DLL и С.DLL, то вы создадите композитную сборку, используя приложение al.exe следующим образом:

al /out:COMPOSITE.DLL A.DLL В.DLL С. DLL

  Совместно используемые сборки

Совместное использование сборок имеет место, когда сборка предназначена для работы нескольких приложений и важно управление версиями (об управлении версиями см. следующий раздел). Для совместно используемой сборки необходимо создать совместно используемое имя (shared name, также известное как строгое имя, strong name) с помощью утилиты Strong Name из .NET SDK. Применение совместно используемых имен дает четыре главных преимущества.

  • С помощью этого механизма в .NET генерируются глобально уникальные имена.

  • Поскольку сгенерированная пара ключей (см. ниже) включает подпись, можно выявить попытки изменений исходных данных, имевшие место после их создания.

  • Строгие имена гарантируют, что сторонние разработчики не смогут выпускать другие версии созданной вами сборки. Этому препятствуют подписи — у сторонних разработчиков нет вашего закрытого ключа.

  • Когда .NET загружает сборку, исполняющая среда может проверитьпришла ли эта сборка из известного вызывающей стороне источника.

Первым шагом на пути создания строгого имени является применение утилиты Strong Name для создания файла ключа для сборки. Для этого укажите переключатель -k и имя выходного файла, в котором будет содержаться ключ. Здесь мы сделаем файл ключа InsideCSharp.key:

sn -k InsideCSharp.key

После выполнения этой команды вы получите подтверждающее сообщение:

Key pair written to InsideCSharp.key

Теперь добавим к исходному файлу атрибут assembly:AssemblyKeyFile. Здесь я создал другой простой набор файлов, чтобы проиллюстрировать, как это делается:

// Module3Server.cs

// компоновка со следующими переключателями командной строки:

// esc /t:module Module3Server.cs

internal class ModuleSServer

{

}

// Module3ClientApp.cs

// компоновка со следующими переключателями командной строки:

// esc /addmodule:Module3Server.netmodule Module3ClientApp.cs

using System;

using System.Diagnostics;

using System.Reflection;

[assembly:AssemblyKeyFile("InsideCSharp.key")]

class ModuleSClientApp <

public static void MainQ {

Assembly DLLAssembly =

Assembly. GetAssembly(typeof(Module3Server)); Console.WriteLine("Module1Server.dll Assembly Information"); Console.WriteLine("t" + DLLAssembly);

Process p = Process.GetCurrentProcessQ;

string AssemblyName = p.ProcessName + ".exe";

Assembly ThisAssembly = Assembly.LoadFrom(AssemblyName);

Console.WriteLine("Module1Client.dll Assembly Information"); Console.WriteLine("t" + ThisAssembly); } }

Как видите, конструктор атрибута assembly-.AssemblyKey File принимает имя файла ключа, сгенерированного утилитой Strong Name, и позволяет задавать пару ключей, которая будет использована для создания строгого имени сборки. Важно понимать, что это атрибут уровня сборки. Поэтому он может быть помещен в любой из ее файлов и не прикреплен к какому-либо классу. Однако принято помещать этот атрибут сразу после операторов using перед определениями классов.

Запустив свое приложение, обратите внимание на значение Public-Key Token сборки. В двух предыдущих примерах оно было равно null, так как эти сборки считались закрытыми. Но теперь сборка определена как совместно используемая, и поэтому с ней ассоциирован маркер открытого ключа.

Module3Server.dll Assembly Information

ModuleSClient, Version=0.0.0.0, Culture=neutral,

PublicKeyToken=6ed7cefOc0065911 Module3Client.dll Assembly Information

ModuleSClient, Version=0.0.0.0, Culture=neutral,

PublicKeyToken=6ed7cefOc0065911

Объект Assembly, экземпляр которого мы создали для этой демонстрационной сборки, указывает, что она является совместно используемой. Но как узнать, какие сборки в системе под управлением .NET совместно используемые? Ответ — в глобальном кэше сборок.

  Работа с глобальным кэшем сборок

В .NET есть кэш кода под названием глобальный кэш сборок (global assembly cache). Он выполняет три главных функции.

  • Он используется для хранения кода, загруженного из Интернета или других серверов (как http, так и файловых серверов). Загруженный для конкретного приложения код хранится в закрытой части кэша, что предотвращает доступ к нему других приложений.

  • Это хранилище данных компонентов, совместно используемое несколькими приложениями .NET. Сборки, установленные в кэш утилитой Global Assembly Cache, хранятся в глобальной части кэша и доступны всем приложениям на машине.

  • Меня часто спрашивают: "Где хранится скомпилированный код, такой как мой код на С#, который компилируется лишь при первом исполнении?" Теперь вы знаете ответ: версии сборок со встроенным прекомпшшрованным кодом хранятся в кэше.

  Просмотр кэша

Взглянем на кэш, чтобы увидеть установленные в данный момент совместно используемые сборки. Из Microsoft Explorer откройте папку c:winnt assembly. Для просмотра информации о сборках .NET представляет утилиту Assembly Cache Viewer (shfusion.dll). Она позволяет просматривать такую информацию о сборке, как номер версии, маркер открытого ключа и даже была ли сборка прекомпилирована.

Еще одно средство просмотра кэша — утилита Global Assembly Cache — позволяет решать некоторые задачи, задавая в командной строке такие (взаимоисключающие) ключи.

  • - i Этот флаг устанавливает сборку в глобальный кэш сборки. Например:

gacutil -i HelloWorld.DLL

Скоро вы увидите как добавить сборку Module3Client в кэш с помощью этого ключа.

  • - u Этот флаг удаляет сборку и любую информацию о версиях из глобального кэша сборки. Если вы не укажете информацию о версии, будут удалены все сборки с заданным именем. Поэтому первый приведенный здесь пример удаляет все сборки с именем HelloWorld независимо от номера версии, а второй удаляет только указанную версию:

gacutil -u HelloWorld

gacutil -u HelloWorld, ver=1,0,0,0

  • - / Этот флаг выводит список содержимого глобального кэша сборки, включая им сборки, номер версии, расположение и совместно используемое имя.

Итак, вы создали файл открытого ключа и назначили его сборке. Теперь добавим эту сборку в кэш. Для этого наберите в командной строке:

gacutil -i Module3ClientApp.exe

Если все идет нормально, вы должны получить подтверждение: Assembly successfully added to the cache

ПРИМЕЧАНИЕ В некоторых ранних бета-выпусках .NET я обратил внимание на одну проблему: при просмотре папки c:winntassembly shfusion.dll не работала. Причиной этого было то, что shfusion.dll не регистрировалась должным образом. Если это случилось в вашей системе, откройте командную строку и выполните команду "regsvr32 shfusion.dll" из папки c:winntMicrosoft.netframeworkvJCHf , где XXX — это номер версии .NET Framework, с которой вы работаете. Очевидно, имя папки изменится перед началом поставок .NET, поскольку я работал с бета-выпуском. Тогда найдите файл shfusion.dll, и используйте эту папку. Здесь я использовал папку, представляющую мою текущую версию .NET: c:winntmicrosoft.netframework vl.0.2615>regsvr32 shfusion.dll.

А сейчас вы можете вызвать команду gacutil -/ для просмотра сборок в кэше и поиска ModuleSClient. Для этого можно использовать утилиту Assembly Cache Viewer, что мы и сделаем. Открыв кэш из Windows Explorer (C:WinntAssembly или C:WindowsAssembly), вы увидите в списке сборку ModuleSClient. Щелкните ее правой кнопкой и выберите Properties, что позволит узнать значение открытого ключа, номер версии и местоположение сборки на жестком диске. Ваш открытый ключ будет отличаться от моего, но он должен совпадать со значением, которое выводится при исполнении приложения ModuleSClientApp.

  Управление версиями сборок

Декларация сборки содержит номер версии и список всех сборок (и связанную с ними информацию о версиях), на которые ссылается эта сборка. Номера версий подразделяются на четыре сегмента и принимают вид:

<старшая версияхмладшая версия><компоновка><ревизия>

В период выполнения это работает так: на основе информации о номере версии .NET решает, какую версию данной сборки использовать с приложением. Как вы вскоре увидите, поведение по умолчанию — оно называется политикой управления версиями — заключается в следующем: после установки приложения .NET будет автоматически использовать самую последнюю версию сборок, на которые ссылается это приложение, если номера их старших и младших версий совпадают. Это поведение по умолчанию позволяют изменить файлы конфигурации.

Управление версиями относится только к совместно используемым сборкам (для закрытых сборок оно не требуется) и является, пожалуй, наиболее важным фактором, определяющим решение о создании совместно используемых сборок. Поэтому рассмотрим примеры, иллюстрирующие, как все это действует и как работать с версиями сборки.

Мой Код представляет собой упрощенный вариант примера управления версиями, поставляемого с .NET SDK. В него не входит материал Windows Forms, поскольку я хочу сосредоточиться на управлении версиями и действиях, осуществляемых для этого исполняющей средой.

У меня есть два исполняемых файла, представляющих два пакета бухгалтерского учета, — Personal и Business. Оба приложения обращаются к общей совместно используемой сборке Account. Класс Account может лишь сообщать свою версию, позволяя убедиться, что наши приложения обращаются именно к предназначенной для них версии класса Account. В этой связи в пример входят несколько версий класса Account, что поможет вам уяснить, как работает управление версиями при использовании политики по умолчанию и как с помощью XML устанавливается связь между приложением и конкретной версией сборки.

Для начала создайте папку Accounting, а в ней — пару ключей, которые будут использованы всеми версиями класса Account. Для этого в командной строке папки Accounting наберите:

sn /k account.key

Создайте в папке Accounting еще одну папку — Personal, а в ней — файл Personal.cs:

// AccountingPersonalPersonal.cs using System;

class PersonalAccounting {

public static void Main() {

Console.WriteLine

("PersonalAccounting calling Account.PrintVersion"); Account. PrintVersionO; } -}

В папке Personal создайте папку Account 1000, где будет располагаться первая версия класса Account нашего примера. Затем в папке Account 1000 создайте файл Account.cs:

// AccountingPersonalAccount1000Account.cs

using System;

using System.Reflection;

[assembly:AssemblyKeyFile("..\. .\Account.key")] [assembly:AssemblyVersion("1.0.0.0")] public class Account <

public static void PrintVersionO

{

Console.WriteLine

("This is version 1.0.0.0 of the Account class"); >

}

Как видите, я использую атрибуты AssemblyeKeyFile и Assembly Version, чтобы указать компилятору С# ранее созданную пару ключей и явно задать версию класса Account. Теперь скомпонуйте DLL Account:

esc /t:library account.cs

Созданный класс Account нужно добавить к глобальному кэшу сборки: gacutil -i Account.dll

Если хотите, можете проверить, что сборка Account находится в кэше сборки. Теперь перейдите в папку Personal и скомпонуйте приложение, например, так:

esc Personal.cs /r:Account1000Account.dll

При запуске этого приложения получится следующий результат:

PersonalAccounting calling Account.PrintVersion This is version 1.0.0.0 of the Account class

До сих пор мы не сделали ничего нового. Но посмотрим, что произойдет при установке еще одного приложения, использующего более новую версию класса Account.

Создайте в папке Accounting папку Business, а в ней — папку Ассо-untlOOl для представления новой версии класса Account. Этот класс находится в файле Account.cs и выглядит почти идентично предыдущей версии.

// AccountingBusinessAccount10Q1Account,cs

using System;

using System.Reflection;

[assembly:AssemblyKeyFile("..\..\Account.key")] [assembly:AssemblyVersion("1.0.0.1")] public class Account

{

public static void PrintVersionQ {

Console.WriteLine

("This is version 1.0.0.1 of the Account class"); } }

Как и прежде, скомпонуйте эту версию класса Account командами: esc /t:library Account, cs gacutil -i Account.dll

На этом этапе вы должны видеть в глобальном кэше сборки две версии класса Account. Теперь создайте (в папке AccountingBusiness) файл Business, cs:

// AccountingBusinessBusiness.cs using System;

class PersonalAccounting {

public static void Main() {

Console.WriteLine

("BusinessAccounting calling Account.PrintVersion"); Account. PrintVersionO; } }

Скомпонуйте приложение Business командой: esc business.cs /r:Account1001Account.dll

Запуск этого приложения даст следующие результаты. Это подтверждает тот факт, что приложение Business использует версию 1.0.0.1 сборки Account.

BusinessAccounting calling Account.PrintVersion This is version 1.0.0.1 of the Account class

А теперь запустите приложение Personal и посмотрите, что происходит! PersonalAccounting calling Account.PrintVersion This is version 1.0.0.1 of the Account class

Оба приложения, Personal и Business, обращаются к последней версии сборки Account*. Почему? Так все и должно быть при использовании подхода QFE (Quick Fix Engineering) и политики управления версиями .NET по умолчанию.

  QFE и политика управления версиями по умолчанию

Обновления Quick Fix Engineering, или текущие исправления, — это внеплановые исправления, рассылаемые при возникновении серьезных проблем. Поскольку текущие исправления, как правило, не модифицируют интерфейс кода, вероятность неблагоприятного влияния на клиентский код минимальна. Поэтому политика управления версиями по умолчанию подразумевает автоматическую связь всего клиентского кода с новой, "исправленной" версией, если только у приложения нет файла конфигурации, явно связывающего приложение с конкретной версией сборки. Новая версия сборки считается QFE, если меняется только часть номера версии, являющаяся номером ревизии (revision).

  Создание файла конфигурации для безопасного режима

Большую часть времени такая политика управления версиями по умолчанию может быть замечательной, ну а если потребуется, чтобы приложение Personal работало только с поставляемой с ним версией сборки? Здесь в дело вступают настроечные XML-файлы. У них те же имена, что и у файлов приложений, и находятся они в той же папке. При исполнении приложения файл конфигурации считывается, после чего .NET через содержащиеся в нем тэги XML указывает, какую версию сборки использовать.

Чтобы задать постоянное использование для приложения поставляемой с ним версии сборки, укажите "безопасный" ("safe") режим как желаемый режим связывания для приложения. В обиходе это иногда называется "перевести приложение в безопасный режим". Чтобы проверить это, создайте файл PersonalAccounting.cfg в папке Accounting/Personal и занесите в него следующие строки. Обратите внимание на тэг <AppBindingMode >.

<?xml version ="1.0"?> ,

<Configuration>

<BindingMode>

<AppBindingMode Mode="safe"/>

</BindingMode>

</Configuration>

Запустив приложение Personal, вы увидите такую выходную информацию:

PersonalAccounting calling Account.PrintVersion

В C# сокеты реализованы с помощью класса Socket. Давайте попробуем с помощью этого класса написать простенький снифер. Для начала я просто написал создал абстрактный класс BaseSocket, опишу только функции которые заслуживают внимания.

CreateAndBindSocket(string ip) - создаёт сокет, а затем биндит его на определённый IP

Shutdown() - прекращает все операции по приёму и посылке данных, после закрывает сокет.

SetSockoption() - устанавливает опцию включать IP заголовок в данные.

socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.HeaderIncluded, 1);

и переводит сокет в режим приёма всех пакетов. socket.IOControl(SIO_RCVALL, IN, OUT); IOControl - это аналог API функции WSAIoctl.

SIO_RCVALL равен 0x98000001 он указывает принимать все пакеты, на самом деле, дело обстоит иначе, если к примеру мы имеем на одной сетевой два ip из одной подсетки типа 10.100.101.130 и 10.100.101.160, то пакеты будут приниматься на эти два ip. Тем более учтите это всё работает только на < b>(на милениуме не пробовал). И если бы будете посылать пинги на свою машину типа ping 10.100.101.130(это ваш ip) или просматривать страницу по этому адресу и т.д. Пакеты не будет отсылаться и следовательно не будут ловится. Идём далее, IN и OUT это входные параметры типа byte[], по идее в Platform SDK это должны быть как булевы переменные BOOL, поэтому я определил их длины равную 4(значение должно быть установлено в TRUE) поэтому я передаю в массиве 1.

Далее всё просто ловлю пакет через функцию BeginReceive Это функция асинхронного приёма Имеет следующий вид

public IAsyncResult BeginReceive

(

byte[] buffer,

int offset,

int size,

SocketFlags socketFlags,

AsyncCallback callback,

object state

);

Первый параметр массив байтов куда принимать данные, затем смещение от начала этого массива(может мы захотим принимать не в начало), затем размер, параметр socketFlags флаги для приёма, типа не роутить пакеты при посылке и всё такое прочие я установил его в None, callback это функция которая вызывается по идее когда начинается приём дынных, а затем state - в этот параметр мы можем предать всё что угодно, он передаётся в качестве единственного параметра функции callback. AsyncCallback делегат имеет следующий вид

public delegate void AsyncCallback(IAsyncResult ar);

Судя по тому, что написано в SDK мы должны были бы вызывать из этой функции EndReceive, чтобы завершить операцию чтения данных, но так как эта функция имеет следующий вид

public int EndReceive(IAsyncResult asyncResult);

И параметр asyncResult, является результатом который мы получаем после вызова BeginReceive, то нормально вызвать из callback, мы эту функцию не можем. Конечно, могли бы, но это чревато последствиями. Скажем если функция callback, выполнялась потом начался выполняться другой процесс, после во второй вызывается наше приложение callback ожидает, а мы что-то сделали с результатом вызова BeginReceive, в итоге мы не получим, то что хотели. Далее я использую следующую структуру

[StructLayout(LayoutKind.Explicit)]

public struct IpHeader

{

[FieldOffset(0)] public byte ip_verlen; //версия ip и длина заголовка по 4 бита

[FieldOffset(1)] public byte ip_tos; //тип сервиса

[FieldOffset(2)] public ushort ip_totallength; //длина общая длина

[FieldOffset(4)] public ushort ip_id; //уникальный идентификатор

[FieldOffset(6)] public ushort ip_offset; //сперва 3 бита флаги затем 13 бит смещение

[FieldOffset(8)] public byte ip_ttl; //ttl

[FieldOffset(9)] public byte ip_protocol; //тип протокола tcp, udp, icmp и т.п.

[FieldOffset(10)] public ushort ip_checksum; //контрольная сумма

[FieldOffset(12)] public long ip_srcaddr; //адрес от кого

[FieldOffset(16)] public long ip_destaddr; //адрес кому

}

Это заголовок IP пакета, не буду подробно описывать структуру заголовка, можете если хотите почитать RFC791 или если туго с английским страничку по адресу:

http://www.citforum.ru/internet/tifamily/ipspec.shtml

А про атрибут StructLayout я уже писал. Далее приняв пакет мне нужно вытащить IP заголовок, я бы мог это сделать с помощью класса Marshal, но там муторно слишком получается, или создать класс типа:

[StructLayout(LayoutKind.Explicit)]

class Rec

{

[FieldOffset(0)] public IpHeader header;

[FieldOffset(0)] public byte []receive_bytes;

}

Тоже не захотелось. Я решил это сделать с помощью unsafe code. Для начала я зафиксировал принятые данные, чтобы GC не переместил их в памяти. Мало ли что ему в голову стукнет.

fixed(byte *fixed_buf = buf)

я в принципе хотел сразу же сделать

fixed(IpHeader*header = buf)

Но оказалось, что так делать нельзя, нужно сперва получить указатель на byte *. Далее всё просто вытащил длину, заголовка IP заголовка, тип протокола и т.д. Учтите кроме IP заголовка есть ещё UDP, TCP, ICMP и т.д. заголовки поэтому у меня длина данных без IP заголовка, это длина данных + длина заголовка конкретного заголовка. Пример длина всего пакета 40 байт, это пакет TCP примерно 20 байт из них заголовок IP пакета, 8 длина заголовка TCP и уже остальное данные. В своей программе я вытаскивал только порты для TCP и UDP. ICMP и IGMP пакеты я не разбирал, лень.

Простой пример работы с массивами в C#

using System;

public class ArrayMembers

{

public static void Main(string[] args)

{

//Skip 1 line

Console.WriteLine("

");

//Выполните итерации элементов масива используя foreach

foreach(string s in args)

{

Console.WriteLine(s);

}

//пропустить 2 линии

Console.WriteLine("

");

//инициализация элементов массива строк

string[] strNames = {"Joe","Mary","Bill","Fred"};

//Выполните итерации элементов масива strNames

for(int i = 0;i < strNames.Length;i++)

{

Console.WriteLine("strNames[{0}] = {1}",i,strNames[i]);

}

}

}

Простой пример cоздания и использования классов на C#

 

using System;

public class myCalc

{

private int result;

//constructor without a parameter defaults to 0

public myCalc()

{

result = 0;

}

//constructor with parameter

public myCalc(int x)

{

result = x;

}

//destructor

~myCalc()

{

result = 0;

}

//methods

public void displayResult()

{

Console.WriteLine("Result = {0}", result);

}

public void add(int x)

{

result = result + x;

}

public void subtract(int x)

{

result = result - x;

}

}

class myTest

{

public static void Main()

{

myCalc mC = new myCalc(90);

mC.displayResult();

mC.add(45);

mC.displayResult();

mC.subtract(35);

mC.displayResult();

mC.subtract(110);

mC.displayResult();

}

}

///--------------------------------------------

Вывод на экран:

******

Result = 90

Result = 135

Result = 100

Result = -10

Наглядный пример работы с датой в C#:

using System;

class DOB{

private DateTime dtDob;

private DateTime dtNow;

private DateTime dtAge;

private int intDay;

private int intMonth;

private int intYear;

private int intHour;

private int intMinute;

private TimeSpan tsAge;

private int intAgeYear;

private int intAgeMonths;

private int intAgeDays;

private int intAgeHours;

private int intAgeMinutes;

public static void Main (String[] args) {

DOB objDob=new DOB();

objDob.getDob();

objDob.createDateObjects();

Console.WriteLine("Your Age in Years :" + objDob.getAgeInYears());

Console.WriteLine("Your Age in Months :" + objDob.getAgeInMonths());

Console.WriteLine("Your Age in Days :" + objDob.getAgeInDays());

Console.WriteLine("Your Age in Hours : " + objDob.getAgeInHours());

Console.WriteLine("Your Age in Minutes : " + objDob.getAgeInMinutes());

Console.WriteLine("Your Accurate Age is : " + objDob.getAgeInYears() + " Years " +

objDob.getMonthDiff() + " months " + objDob.getDayDiff() + " days");

}

/* get the date */

private void getDob() {

try {

Console.Write("Enter the Day u were born : " );

intDay=Console.ReadLine().ToInt32();

Console.Write("Month : ");

intMonth=Console.ReadLine().ToInt32();;

Console.Write("Year(yyyy) : ");

intYear=Console.ReadLine().ToInt32();

Console.Write("Hour(0-23) : ");

intHour=Console.ReadLine().ToInt32();

Console.Write("Minute(0-59) : ");

intMinute=Console.ReadLine().ToInt32();

}

catch (Exception e) {

Console.WriteLine(e.StackTrace);

Environment.Exit(0);

}

}

/* create the date objects */

private void createDateObjects() {

dtDob=new DateTime(intYear,intMonth,intDay,intHour,intMinute,0);

dtNow=DateTime.Now;

if (DateTime.Compare(dtNow,dtDob)==1)

tsAge=dtNow.Subtract(dtDob);

else {

Console.WriteLine("Future dates cannot be entered.");

Environment.Exit(0);

}

dtAge=new DateTime(tsAge.Ticks);

Console.WriteLine("Your date of birth :" + dtDob.Format("F",null));

}

/* calculates the age in Years */

private int getAgeInYears() {

intAgeYear=dtAge.Year-1;

return intAgeYear;

}

/* calculates the age in months */

private int getAgeInMonths() {

intAgeMonths=intAgeYear*12;

intAgeMonths=intAgeMonths+(dtAge.Month-1);

return intAgeMonths;

}

/* calculates the age in days */

private int getAgeInDays() {

if (dtDob.Year==dtNow.Year) {

intAgeDays=dtNow.DayOfYear-dtDob.DayOfYear;

}

else {

if(DateTime.IsLeapYear(dtDob.Year))

intAgeDays=366-dtDob.DayOfYear;

else

intAgeDays=365-dtDob.DayOfYear;

for (int i=dtDob.Year+1;i < dtNow.Year;i++) {

if (DateTime.IsLeapYear(i))

intAgeDays+=366;

else

intAgeDays+=365;

}

intAgeDays+=dtNow.DayOfYear;

}

return intAgeDays;

}

/* calculates the age in Hours */

private int getAgeInHours() {

intAgeHours=getAgeInDays() * 24;

intAgeHours=intAgeHours+(dtNow.Hour-dtDob.Hour);

return intAgeHours;

}

/* calculates the age in Minutes */

private int getAgeInMinutes() {

intAgeMinutes=getAgeInHours() * 60;

intAgeMinutes=intAgeMinutes+(dtNow.Minute-dtDob.Minute);

return intAgeMinutes;

}

/* calculates the month part of the accurate Age */

private int getMonthDiff() {

return getAgeInMonths()%12;

}

/* calculates the day part of the accurate Age */

private int getDayDiff() {

int intDayTemp1=getAgeInDays();

int intDayTemp2;

int intTempYear;

int intTempMonth;

int intTempDay=intDay;

if (dtNow.Year!=dtDob.Year) {

if (1==dtNow.Month) {

intTempYear=dtNow.Year-1;

intTempMonth=12;

}

else {

if(dtNow.Day < intTempDay)

intTempMonth=dtNow.Month-1;

else {

intTempMonth=dtNow.Month;

intTempDay=1;

}

intTempYear=dtNow.Year;

}

}

else {

if (1==dtNow.Month || dtDob.Month==dtNow.Month)

return getAgeInDays();

else {

if(dtNow.Day < intTempDay)

intTempMonth=dtNow.Month-1;

else {

intTempMonth=dtNow.Month;

intTempDay=1;

}

}

intTempYear=intYear;

}

dtNow=new DateTime(intTempYear,intTempMonth,intDay);

intDayTemp2=getAgeInDays();

return intDayTemp1-intDayTemp2;

}

}

Пример отработки индексации на C#. Очень простой и интуитивно понятный.

using System;

class IndexExample

{

string Message;

public static void Main()

{

IndexExample obj=new IndexExample("Welcome");

/* This will access the String variable Message

using array like notation

*/

for(int i=0;i < obj.Length;i++)

{

Console.WriteLine(obj[i]);

}

obj[obj.Length-1]="e to C#";

Console.WriteLine(obj.Message);

}

public IndexExample(string s)

{

Message=s;

}

public string this[int i]

{

get

{

if(i >= 0 && i < Message.Length)

{

return Message.Substring(i,1);

}

else

{

return "";

}

}

set

{

if(i >= 0 && i < Message.Length)

{

Message=Message.Substring(0,i) + value + Message.Substring(i+1);

}

}

}

public int Length

{

get

{

if(Message!=null)

{

return Message.Length;

}

else

return 0;

}

}

}

Библиотеки

Будут нужны:

using System.Data

using System.Data.OleDb

using System.Globalization

Присоединение к базе

//Attributes

public const string DB_CONN_STRING =

"Driver={Microsoft Access Driver (*.mdb)}; "+

"DBQ=D:CSTestDbReadWriteSimpleTest.mdb";

Виды SQL-команд

Есть четыре вида операций:

  • чтение данных (запрос возвращает данные)

  • обновление данных (запрос возвращает количество затронутых записей)

  • добавление данных (запрос возвращает количество затронутых записей)

  • удаление данных (запрос возвращает количество затронутых записей)

Чтение данных

Производится с помощью класса OleDbDataReader

Шаги, которые нужны:

Создать соединение, открыть его

OleDbConnection conn =

new OlebConnection(DB_CONN_STRING);

conn.Open();

Выполнить SQL-запрос к базе:

OleDbDataReader dr;

OleDbCommand cmd =

new OleDbCommand( "SELECT * FROM Person", conn );

dr = cmd.ExecuteReader();

Считать все записи

while (dr.Read())

{

System.Console.WriteLine( dr["FirstName"] );

}

закрыть соединение

finally

{

// Close the connection

if (conn.State == DBObjectState.Open)

{

conn.Close();

}

}

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]