Мы продемонстрируем детали реализации диалогового окна на примере создания диалога для изменения информации о гостинице в упрощенной версии нашего примера Код системы запуска нашего примера имеется в папке HotelAdminNStepl, которая находится в главной папке этой главы Окончательная версия программы находится в папке HotelAdmin\Step3 Можно запустить первую версию примера, а можно сразу запустить решение Step 3 и посмотреть, как должно выглядеть диалоговое окно в окончательной версии В главной форме выберите гостиницу, щелкнув на соответствующем элементе списка гостиниц Затем щелкните на кнопке Change (Изменить ) В результате появится диалоговое окно Change Hotel Information (Изменение информации о гостинице), показанное на Рисунок 6.24 Обратите внимание на то, что поля City (Город) и Hotel Name (Название гостиницы) недоступны Эти поля доступны только для чтения, и их значения изменить нельзя Пользователь может изменить только значения полей Rooms (Количество номеров) и Rate (Стоимость)
Создание модального диалога
В первой части нашей демонстрации мы научим вас создавать модальное диалоговое окно Мы покажем, как устанавливать свойства диалога и как возвращать результаты после щелчка на кнопке (Ж или Cancel (Отмена)
1. Скомпонуйте и запустите стартовую систему приложения Кнопки Add (Добавить ) и Delete (Удалить) работают, но для кнопки Change (Изменить ) имеется только заглушка, которая по щелчку на этой кнопке отображает пустую форму Это обычная форма Ее размер можно изменять, у нее есть системное меню, кнопки свертывания Minimize (Свернуть) и развертывания Maximize (Развернуть)
2. Откройте файл ChangeHotelDialog. cs в режиме Design (Конструктор) В окне Properties (Свойства) установите значение свойства FormBorderStyle равным FixedDialog
3. Установите значение свойств ControlBox, MinimizeBox и MaximizeBox равным False (Ложь) Сейчас можно скомпоновать и запустить приложение Теперь размер диалогового окна будет фиксированным, у него не будет системного меню, а в правом верхнем углу окна не будет кнопки "X", с помощью которой можно закрыть окно.
4. Теперь необходимо определить надписи и текстовые поля, содержащие информацию о гостинице Кроме того, необходимо добавить кнопки ОК и Cancel (Отмена) Когда вы будете добавлять эти управляющие элементы, вы можете еще раз попрактиковаться в работе с панелью инструментов Toolbox (Инструментальная панель) Вы можете выбрать и другой подход скопировать эти свойства из файла NewHotelDialog.cs и затем вставить их, для этого нужно открыть оба файла в режиме Design (Конструктор)
5. Если вы использовали копирование и вставку, то у управляющих элементов свойства Name (Имя) и Text (Текст) уже определены правильно. В противном случае установите значения в соответствии с табл. 6.2.
6. Значение свойства Readonly для txtCity и txtHotelName установите равным true (истина).
7. Измените размер формы так, чтобы на ней помещались все добавленные управляющие элементы.
8. Установите значение свойства DialogResult кнопки ОК равным ОК. Точно так же установите значение этого свойства кнопки Cancel (Отмена) равным Cancel (Отмена). Сохраните изменения, сделанные в файле ChangeHotelDialog. cs.
9. В файле MainAdminForm.cs временно добавьте к обработчику cmdChange_Click код, который отвечает за отображение в текстовом поле Messages (Сообщения) строк "ОК" и "Cancel" ("Отмена") в зависимости оттого, как был закрыт диалог: с помощью кнопки ОК или Cancel (Отмена). Обратите внимание на то, что диалоговое окно отображается с помощью метода ShowDialog, а не метода Show (Показать), который используется для обычных форм. В качестве результата метод ShowDialog возвращает перечисление типа DialogResult.
private void cmdChange_Click(object sender,
System.EventArgs e)
{
ChangeHotelDialog dig = new ChangeHotelDialog();
DialogResult status = dig.ShowDialog(); // состояние
if (status == DialogResult.OK)
// если (состояние == DialogResult. OK)
{
txtMessages.Text = "OK"; // Текст
}
else
{
txtMessages.Text = "Cancel";
// txtMessages. Текст = "Отмена";
}
}
Таблица 6.2. Значения свойств текстовых полей и кнопок диалога ChangeHotelDialog.cs
Имя (Name) | Текст |
txtCity |
(не заполнено) |
txtHotelName | (не заполнено) |
txtlMumberRooms | (не заполнено) |
txtRate | (не заполнено) |
cmdOk | ОК |
cmdCancel | Cancel (Отмена) |
10. Скомпонуйте и запустите пример. Теперь диалоговое окно уже можно открыть с помощью меню, а закрыть— с помощью любой из кнопок ОК или Cancel (Отмена), причем на экран будет выведено соответствующее сообщение. Можно проверить, что диалоговое окно является модальным, пощелкав мышью где-нибудь еще в приложении. Программа сейчас находится на шаге 2 разработки.
Передача информации между родительской формой и диалогом
Во второй части нашей демонстрации будет показано, как передавать информацию из родительской формы диалогу и как получать информацию от диалога. Для этих целей в классах .NET Framework нет встроенного механизма, но все-таки существует некоторый шаблон проектирования, которому можно следовать. В классе диалога для каждого сообщения (порции информации), которое может передаваться между родительской формой и диалогом, нужно определить некоторое свойство.
В нашем примере мы сделаем свойства City (Город) и HotelName (Название гостиницы) доступными только для записи, а свойства Rate (Стоимость) и NumberRooms — доступными для чтения и записи.
1. Для того чтобы реализовать указанные свойства, добавьте к классу Chan-geHotelDialog в файле ChangeHotelDialog. cs приведенный ниже код.
public string City
// общедоступный строковый - Город
{
set
{
txtCity.Text = value; // txtCity. Текст = значение;
}
}
public string HotelName // общедоступная строка HotelName
{
set
{
txtHotelName.Text = value; // txtHotelName. Текст = значение;
}
}
public int NumberRooms
{
get
{
return Convert.ToInt32 (txtNumberRooms.Text);
}
set
{
}
}
public decimal Rate // общедоступная десятичная Цена
{
get
{
return Convert.ToDecimal(txtRate.Text);
}
set
{
txtRate.Text = value.ToString(); // Текст = значение
}
}
2. Теперь, чтобы установить эти свойства перед вызовом диалогового окна, и для того, чтобы использовать эти свойства перед закрытием диалогового окна с помощью кнопки ОК., добавьте код в главную форму MainAdminForm.cs. Удалите или закомментируйте временно вставленный ранее код, который отображает строки "ОК." или "Cancel" ("Отмена") в поле Messages (Сообщения).
private void cmdChange_Click(object sender,
System.EventArgs e)
{
ChangeHotelDialog dig = new ChangeHotelDialog();
if (currHotel.HotelName != "")
{
dig.City = currHotel.City; // Город
dig.HotelName = currHotel.HotelName;
dig.NumberRooms = currHotel.NumberRooms; d
ig.Rate = currHotel.Rate;
}
else
{
MessageBox.Show("Please select a hotel",
// "Пожалуйста, выберите гостиницу ",
"Hotel Broker Administration",
MessageBoxButtons.OK,
MessageBoxIcon.Exclamation // Восклицательный знак
) ;
return;
}
DialogResult status = dig.ShowDialog(); // состояние
if (status == DialogResult.OK)
// если (состояние == DialogResult. OK)
{
string comment = hotelBroker.ChangeRooms( // строковый
// комментарий
currHotel.City, // Город
currHotel.HotelName,
dig.NumberRooms,
dig.Rate);
if (comment == "OK")
// если (комментарий == "OK")
{
ShowHotelList(hotelBroker.GetHotels(;) ;
txtMessages.Text = "Hotel " + currHotel.HotelName
// txtMessages. Текст = "Гостиница"
// + currHotel. HotelName
+ " has been changed";
// + " была изменена";
}
else
txtMessages.Text = comment; // Текст = комментарий
}
}
Структура currHotel содержит поля гостиницы, выбранной в данный момент в качестве элемента списка. В следующем разделе вы научитесь извлекать информацию из элемента списка и заполнять элемент списка.
3. Скомпонуйте и испытайте программу. Все функции диалога должны работать корректно. Полученный проект соответствует проекту HotelAdmin\Step3.
Лучший способ научиться создавать приложения Windows с помощью Visual Studio.NET — самостоятельно с самого начала создать небольшое приложение на С#. Для примера мы создадим Windows-приложение, которое позволит вносить деньги на счет и снимать деньги со счета в банке.
1. Создайте на СП новый проект Windows Application (Windows-приложение), как на Рисунок 6.11, и назовите его BankGui.
2. Раскройте панель инструментов Toolbox, перетянув указатель мыши на вертикальную вкладку Toolbox в левой части главного окна Visual Studio. Если вкладки нет, инструментальную панель Toolbox можно открыть из меню ViewOToolbox (Вид1^ Панель инструментов). Чтобы панель инструментов Toolbox оставалась открытой, щелкните на "канцелярской кнопке", которая находится в заголовке панели инструментов Toolbox рядом с X. Если курсор мыши навести на "канцелярскую кнопку", появится подсказка с надписью "Auto Hide" (Автоматическое свертывание).
3. Перетащите из панели инструментов Toolbox две надписи (Label), два поля (TextBox) и две кнопки (Button) на форму (Рисунок 6.12).
4. В конструкторе форм (Forms Designer) щелкните на надписи labell. Тем самым вы выделите этот управляющий элемент в окне Properties (Свойства), которое находится под Solution Explorer (Поиск решения). Окно Properties (Свойства) позволяет изменять свойства управляющих элементов. В поле свойства Text (Текст) объекта labell введите Amount (Сумма). После того, как вы ввели значение, нажмите возврат каретки. Вы увидите, что текст появится на форме. На Рисунок 6.13 показано окно Properties (Свойства) после изменения свойства Text (Текст) первой надписи.
5. Точно так же измените текст надписи 1аЬе12 на Balance (Баланс).
6. Введите значения свойств полей и кнопок в соответствии с табл. 6.1.
7. С помощью маркеров размера, которые находятся посредине каждой стороны формы, измените ее размер. При желании, перетащите управляющие элементы на выбранные места, и измените их размер. Если внешний вид формы вас удовлетворяет, сохраните изменения, сделанные в проекте. Ваша форма должна выглядеть примерно так, как на Рисунок 6.14.
8. Добавьте обработчики событий кнопок, дважды щелкнув на каждой кнопке.
9. Добавьте необходимый код к коду, сгенерированному мастером:
Таблица 6.1. Значения свойств полей (Textbox) и кнопок (Button)
Имя свойства | Текст |
txtAmount | (не заполняется) |
txtBalance | (не заполняется) |
cmdDeposit | Deposit (Вклад) |
cmdWithdraw | Withdraw (Снять) |
public class Forml : System.Windows.Forms.Form
// общедоступный класс Forml:Система.Windows.Формы.Форма
{
public Forml()
{
//
// Требуется для поддержки Windows Form Designer
//
InitializeComponent();
//
// TODO: Добавьте любой код конструктора после
// вызова InitializeComponent
//
txtAmount.Text = "25";
// Текст txtBalance.Text = "100";
// Текст
}
/// <summary>
/// The main entry point for the application.
/// Основная точка входа для приложения.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Forml{));
}
private void cmdDeposit_Click(object sender,
System.EventArgs e)
{
int amount = Convert.Tolnt32(txtAmount.Text);
int balance = Convert.Tolnt32(txtBalance.Text); // баланс
balance += amount;
// баланс + = количество;
txtBalance.Text = Convert.ToString(balance); // Текст
}
private void cmdWithdraw_Click(object sender,
System.EventArgs e)
{
int amount = Convert.Tolnt32(txtAmount.Text);
int balance = Convert.Tolnt32(txtBalance.Text); // баланс
balance = amount;
txtBalance.Text = Convert.ToString(balance); // Текст
}
10. Откомпилируйте и выполните приложение. Оно должно вести себя как стандартное приложение Windows. Вы должны без проблем вносить деньги на счет и снимать деньги со счета. На Рисунок 6.15 показано выполняющееся приложение BankGui.
В данный момент проект реализован на С#. И хотя этого, как правило, не делают, мы перенесем этот проект в C++ для того, чтобы показать, как это нужно делать. Сначала с помощью шаблона Managed C++ Empty Project (Пустой проект на управляемом C++) создадим новый проект C++, который назовем BankGuiPort.
Теперь создадим исходный файл Forml.cpp в проекте BankGuiPort и перенесем (с помощью команд сору (копировать) и paste (вставить)) код С# из исходного файла Forml. cs проекта BankGui.
Перенесите все строки кода из файла Forml.cs проекта BankGui в файл Forml.cpp проекта BankGuiPort. При таком переносе кода могут возникнуть проблемы и непредвиденные ситуации. Эти проблемы не будут рассмотрены в нашей книге и вам придется ознакомиться с ними самостоятельно, если вы и в дальнейшем захотите выполнять подобный перенос кода. Итак, откройте оба проекта — BankGui на С# и BankGuiPort на C++ — в двух копиях Visual Studio.NET и визуально сравните исходные файлы Forml.cs и Forml.cpp, чтобы получить представление о подробностях переноса кода.
//Form1.cpp
#using <mscorlib.dll>
#using <System.dll>
#using <System.Drawing.dll>
#using <Systern.Windows.Forms.dll>
using namespace System;
// использование пространства имен Система;
namespace BankGui
// пространство имен BankGui
{
_gc class Forml : public System::Windows::Forms::Form
// класс сборщика мусора Forml: общедоступная Система::
// Windows:: Формы:: Форма
{
private: // частный
System::Windows: Forms::Label *label1;
System::Windows: Forms::Label *labe!2;
System::Windows: Forms::TextBox *txtAmount;
System::Windows: Forms::TextBox *txtBalance;
System::Windows: Forms::Button *cmdDeposit; // Кнопка
System::Windows: Forms::Button *cmdWithdraw; // Кнопка
System::ComponentModel::Container *components; // Контейнер
public:
Form1 ()
{
components =0; // компоненты
InitializeComponent();
txtAmount->Text = "25"; // Текст
txtBalance->Text = "100"; // Текст
}
private: // частный
void InitializeComponent()
{
cmdWithdraw = new System: :Windows::Forms::Button;
// Кнопка
cmdDeposit = new System::Windows::Forms::Button;
// Кнопка
txtBalance = new System::Windows::Forms::TextBox;
txtAmount = new System::Windows::Forms::TextBox;
labell = new System::Windows::Forms::Label; // Надпись
Iabel2 = new System: : Windows :: Forms :-.Label; // Надпись
SuspendLayout();
//
// cmdWithdraw
//
cmdWithdraw->Location = // Местоположение
* _nogc new System::Drawing::Point(152, 144);
// Точка
cmdWithdraw->Name = "cmdWithdraw"; // Имя
cmdWithdraw->TabIndex = 2;
cmdWithdraw->Text = "Withdraw"; // Текст = "Снять"
cmdWithdraw->Click += // Щелчок
new System::EventHandler(this, cmdWithdraw_Click);
//
// Form1
//
AutoScaleBaseSize =
* _nogc new System::Drawing::Size(5, 13);
// Размер
ClientSize =
* _nogc new System::Drawing::Size(280, 189);
// Размер
System: :Windows::Forms::Control* pltems[] = {
cmdDeposit,
txtAmount,
label1,
label2,
txtBalance,
cmdWithdraw};
Controls->AddRange(pltems);
"Name = "Forml"; // Имя
Text = "Forml"; // Текст
Load += new System::EventHandler(this, Forml_Load);
ResumeLayout(false); // ложь } void Forml_Load(
Object *sender, System::EventArgs *e)
{
}
void cmdWithdraw_Click(
Object *sender, System::EventArgs *e)
{
int amount = Convert::ToInt32(txtAmount->Text);
// преобразование текста
int balance = Convert::ToInt32(txtBalance->Text);
// преобразование текста
balance -= amount;
// -количество
txtBalance->Text = Convert::ToString(balance);
// преобразование в текст }
public:
[STAThread] static void Main()
{
System::Windows::Forms::Application::Run(new Forml);
// Приложение:: Выполнить (новая Forml);
}
};
}
Использование диалоговых окон облегчает процесс взаимодействия пользователя с приложением Windows Диалоговое окно — это набор управляющих элементов, с помощью которых упрощается процесс ввода данных В предыдущем примере было описано, как создать простое диалоговое окно, которое позволяло бы пользователю на вопрос дать ответ "Да" или "Нет" Для создания таких диалоговых окон используется класс MessageBox (Окно сообщения) Более сложные диалоговые окна создаются на основе форм.
Мы проиллюстрируем создание диалоговых окон на примере графического пользовательского интерфейса для бюро путешествий Acme (Acme Travel Agency) Код примера, как обычно, находится в папке CaseStuay для этой главы Давайте рассмотрим простой диалог, с помощью которого можно добавить гостиницу в список гостиниц. Скомпонуйте и запустите пример В главной форме щелкните на кнопке Add... (Добавить ) После этого появится диалоговое окно New Hotel (Новая гостиница), которое показано на Рисунок 6.21.
Теперь пользователь может вводить данные. Щелчок на кнопке ОК. приведет к тому, что информация будет запомнена Щелчок на кнопке Cancel (Отмена) приведет к тому, что данные будут проигнорированы. Это диалоговое окно, так же как и окно сообщения, является модальным Если модальное диалоговое окно открыто, то пользователь может взаимодействовать с приложением только через это окно, — ни с какими иными средствами приложения он взаимодействовать не может. Если вы попытаетесь сделать что-нибудь на главной форме, когда открыто диалоговое окно New Hotel (Новая гостиница), то услышите гудок. Существуют также и немодальные диалоговые окна, которые позволяют взаимодействовать с иными средствами приложения даже тогда, когда немодальное диалоговое окно открыто.
Если рассматривать диалоговое окно как форму, то обычно у него есть свои специфические характеристики. Как правило, у диалоговых окон нет системного меню, нет кнопок свертывания Minimize (Свернуть) и развертывания Maximize (Развернуть), причем размер окна фиксирован Вы можете наблюдать эти свойства на примере диалога New Hotel (Новая гостиница).
Для того чтобы продолжить испытание программы, введите данные о новой гостинице и щелкните на кнопке ОК. Программа вернет вас в главную форму, где новая гостиница будет отображена в списке гостиниц (Рисунок 6 22). На главной форме реализованы также и другие управляющие элементы графического интерфейса пользователя, например, окно, в котором отображается список гостиниц, а также многострочное текстовое поле для отображения текста, который не помещается в одну строку.
1. Скомпонуйте и выполните программы (стартовые системы) на С# и C++, находящиеся в папках VsForm\Stepl и VsFormPortXStepl, и убедитесь, что они работают одинаково Это полностью статические приложения, — они просто отображают строку приветствия в фиксированной позиции
2. Откройте форму проекта VsForm\Stepl в окне конструктора (Design window) и щелкните на кнопке Events (События) в окне Properties (Свойства)
3. Найдите событие MouseDown (Кнопка мыши нажата), как на Рисунок 6.18.
4. В окне Properties (Свойства) дважды щелкните на событии MouseDown (Кнопка мыши нажата) Автоматически будет сгенерирован код, который зарегистрирует делегата для события и образует скелет метода, связанного с делегатом
private void InitializeComponent ()
{
this.MouseDown =
new System.WinForms.MouseEventHandler
(this.Forml_MouseDown);
}
protected void Forml_MouseDown (object sender,
System.WinForms.MouseEventArgs e)
{
}
Диалоговые окна очень хорошо описаны в документации по комплексу инструментальных средств разработки программ .NET Framework SDK Информацию по диалоговым окнам следует искать в подразделе "Dialog Boxes in Windows Forms" ("Диалоговые окна в Формах Windows") раздела "Introduction to Windows Forms" ("Введение в Формы Windows") Следует заметить, что во всех языках .NET принципы работы диалоговых окон одни и те же Это является серьезным отличием программирования на NET от традиционного программирования на Visual Basic и программирования с применением библиотеки базовых классов Microsoft (Microsoft Foundation Classes, MFC), где принципы создания диалоговых окон совершенно разные На Рисунок 6.23. показано, где искать документацию по диалоговым окнам
Документацию, касающуюся событий и их обработки, можно найти в справочнике по NET Framework ( NET Framework Reference) На рис 6 6 показаны предопредепенные события, связанные с классом Form (Форма)
Windows Forms (Формы Windows) — это та часть каркаса .NET Framework, которая поддерживает создание приложений со стандартным графическим пользовательским интерфейсом (GUI) на платформе Windows. Среди классов Windows Forms (Формы Windows) есть обширный набор классов для создания сложных графических пользовательских интерфейсов. Эти классы можно использовать в приложениях, написанных на любом языке .NET
Как правило, ваше приложение будет содержать главное окно, которое реализовано с помощью некоторого класса MyForm, производного от класса Form (Форма). На рис 6.1 изображено, какое место ваш класс MyForm занимает в иерархии классов Windows Forms (Формы Windows).
3. Откройте панель инструментов Toolbox, если она до сих пор еще не открыта (щелкните на ярлыке панели инструментов Toolbox в вертикальной линейке) и перетащите управляющий элемент MainMenu (Главное меню) на форму приложения.
4. Для создания выпадающего меню File (Файл) с пунктом Exit (Выход), введите File (Файл) и Exit (Выход), как на Рисунок 6.19.
5. В окне Properties (Свойства) измените названия этих пунктов меню на menuFile и menuExit.
6. Дважды щелкните на Exit (Выход), чтобы добавить код в обработчик события File => Exit (Файл => Выход).
7. Добавьте в обработчик код, закрывающий приложение.
protected void menuExit_Click (object sender,
System.EventArgs e)
{
Application.Exit(); // Приложение.Выход
}
8. Скомпонуйте и выполните приложение. Меню должно полностью работать. Полученный проект соответствует проекту, который находится в папке VsForm\Step3.
И снова, вместо того, чтобы переносить каждую строчку кода, созданного на С#, из файла Forml.cpp проекта VsForm\step3 в проект на C++, просто сделайте копию проекта VsFormPort\Step2, созданного ранее с помощью переноса кода. Потом перенесите те несколько строчек кода, которые связаны с новыми функциями меню, из VsForm\Step3.
// VSForm - Step3
_gc class Forml : public System::Windows::Forms::Form
{
private: // частный
float x, у; // с плавающей точкой
Brush *pStdBrush; // Кисть
System: -.Windows: : Forms : :MainMenu *mainMenul;
System::Windows::Forms::MenuItem *menuFile;
System::Windows::Forms::MenuItem *menuExit;
private: // частный
void InitializeComponent()
{
menuFile =
new System: -.Windows: : Forms: :MenuItem() ; menuExit =
new System::Windows::Forms::MenuItem(); mainMenul =
new System::Windows::Forms::MainMenu() ;
//
// menuFile
//
menuFile->Index =0; // Индекс
System::Windows::Forms::MenuItem *pltems[] =
{menuExit};
menuFile->MenuItems->AddRange(pltems) ;
menuFile->Text = "File"; // menuFile-> Текст = "Файл";
//
// menuExit
//
menuExit->Index = 0;
// Индекс menuExit->Text = "Exit";
// menuExit-> Текст = "Выход";
menuExit->Click += // Щелчок
new System::EventHandler
(this, menuExit_Click);
//
// mainMenul
// System::Windows::Forms::MenuItem *pMenus[] =
{menuFile};
mainMenul->Menu!tems->AddRange(pMenus);
//
// Forml
// AutoScaleBaseSize =
* _nogc new System::Drawing::Size(5, 13);
// Размер ClientsTze =
* _nogc new System::Drawing::Size(248, 181);
// Размер Menu = mainMenul;
}
private: // частный
void menuExit_Click(
Object *sender, System::EventArgs *pe)
{
Application::Exit(); // Приложение:: Выход
}
};
//SimpleForm.срр - Шаг 4
_gc class Forml : public Form
// класс сборщика мусора Forml: общедоступная Форма
{
private: // частный
void InitializeComponent()
{
pMainMenul = new MainMenu ();
pMenuFile = new Menultem ();
pMenuExit = new Menultem ();
// mainMenul
Menultem* pMainMenulItems[] = {pMenuFile};
pMainMenul->get_MenuItems()
->AddRange(pMainMenulItems); // Меню File
pMenuFile->set_Index(0);
Menultem* pMainFileltems[] = {pMenuExit};
pMenuFile->get_MenuItems()
->AddRange(pMainFileltems);
pMenuFile->set_Text("File"); // Файл
// Меню Exit
pMenuExit->set_Index{0);
pMenuExit->set_Text("Exit"); // Выход
pMenuExit->Click += new System::EventHandler // Щелчок (this, MenuExit_Click);
Menu = pMainMenul; // Меню
MouseDown += new MouseEventHandler
(this, Forml_MouseDown);
KeyPress += new KeyPressEventHandler
(this, Forml_KeyPress); }
float x, у; // с плавающей точкой
Brush *pStdBrush; // Кисть
StringBuilder *pStr;
Menultem *pMenuExit;
Menultem *pMenuFile;
MainMenu *pMainMenul;
public:
private: // частный
void MenuExit_Cliok(
Object *pSender, EventArgs *pea)
{
Application::Exit(); // Приложение:: Выход
}
В методе initializeComponent создается иерархическая структура меню, представленная экземпляром класса MainMenu (Главное меню). Меню состоит из объектов Menultem, каждый из которых является отдельной командой меню Каждый объект Menultem является командой приложения или командой родительского меню для других пунктов подменю В нашем приложении мы связываем объект MainMenu (Главное меню) с объектом Form (Форма), присваивая свойству Menu (Меню) объекта Form (Форма) значение MainMenu (Главное меню)
Когда в этой главе мы позже обсудим конструктор форм (Forms Designer), вы увидите, что меню можно создать и так нужно просто перетянуть элемент управления MainMenu (Главное меню) с панели инструментов на форму. Конструктор форм (Forms Designer) позаботится о генерации нужного шаблонного кода.
1. Чтобы установить координаты строки приветствия, добавьте код в обработчик события мыши (нажатие кнопки мыши) Не забудьте после этого вызвать метод Invalidate (Считать недействительным)
protected void Forml_MouseDown (object sender,
System WinForms.MouseEventArgs e)
{
x = e X;
у = e Y;
Invalidate() ;
}
2. Скомпонуйте и выполните проект Теперь по щелчку мыши (любой кнопкой) приветствие должно перемещаться Проект сейчас находится на шаге 2 разработки и соответствует проекту, хранящемуся в папке VsForm\Step2
Вместо того, чтобы переносить каждую строчку кода, созданного на СП, в файл Forml cpp проекта VsForm\Step2, просто сделайте копию проекта VsFormPortXStepl, который уже получен с помощью переноса кода Потом перенесите несколько строчек кода, связанных с событием MouseDown (Кнопка мыши нажата) из VsForm\Step2
void InitializeComponent()
{
MouseDown +=
new System::Windows::Forms:-MouseEventHandler
(this, Forml_MouseDown);
}
void Forml_MouseDown (Object *sender,
System::Windows::Forms::MouseEventArgs *e)
{
x = (float)e->X; // с плавающей точкой
у = (float)e->Y; // с плавающей точкой
Invalidate();
}
Как и в случае других событий Windows Forms (Формы Windows), с событием связывается его делегат Щелчок на пункте меню приводит к выполнению соответствующей команды
void InitializeComponent()
{
pMenuExit->Click += new System::EventHandler // Щелчок
(this, MenuExit_Click);
}
void MenuExit_Click(
Object *pSender, EventArgs *pea)
{
Application::Exit(); // Приложение::Выход
}
Все пользователи Windows-приложений хорошо знакомы с меню, которые представляют собой простой механизм выбора команд. В языках .NET меню реализуется в самой программе. Иными словами, для меню файл ресурсов не нужен.
При запуске программы HotelAdmin в процессе инициализации конструктор формы MainAdminForm осуществляет начальную загрузку списка элементов listHotels, — в него загружается список гостиниц.
public MainAdminForm()
{
//
// Требуется для поддержки конструктора форм Windows
// (Windows Form Designer)
//
InitializeComponent();
//
// TODO: Добавьте любой код конструктора после
// вызова InitializeComponent
//
hotelBroker = new HotelBroker();
ShowHotelList(hotelBroker.GetHotels()
};
}
Метод ShowHotelList отображает в списке элементов список массивов, в которых хранится информация о гостиницах. Чтобы получить список массивов, вызывается метод HotelBroker . GetHotels. Ниже приведен метод ShowHotelList.
private void ShowHotelList(ArrayList array) // массив
{
listHotels.Iterns.Clear();
if (array == null)
// если (массив == пустой указатель)
{
return; }
foreach(HotelListltem hotel in array) // гостиница в массиве
{
string city = hotel.City.Trim();
// строковый город = гостиница.Город.Вырезка();
string name = hotel.HotelName.Trim();
// строковое название = гостиница.HotelName.Вырезка();
string rooms = hotel.NumberRooms.ToString(); // гостиница
string rate = hotel.Rate.ToString();
string str = city + "," + name + ","
// строка str = город +,"" + название +,""
+ rooms + "," + rate;
listHotels.Items.Add(str); // Добавить элементы
}
}
Управляющий элемент ListBox (Список элементов) содержит свойство Items (Элементы), которое поддерживает коллекцию объектных ссылок. Сначала мы вызываем метод Items.Clear (Элементы.Очистить), чтобы очистить список элементов от тех элементов, которые отображаются в нем в момент вызова метода. Потом мы с помощью цикла перебираем гостиницы в списке массивов и создаем строку, которая состоит из полей структуры гостиницы, разделенных запятыми. Чтобы добавить эту строку в список элементов, вызывается метод Items .Add (Элементы.Добавить).
Графический пользовательский интерфейс (GUI) управляется событиями приложение выполняет действия в ответ на события, вызванные пользователем, например, на щелчок кнопкой мыши или выбор пункта меню Каждая форма или элемент управления имеет заранее определенный набор событий Например, у каждой формы есть код, обрабатывающий событие MouseDown (Кнопка мыши нажата)
В Windows Forms (Формы Windows) применяется модель обработки событий NET, в которой делегаты используются для того, чтобы связать события с обрабатывающими их методами В классах Windows Forms (Формы Windows) используются групповые делегаты Групповой делегат содержит список связанных с ним методов Когда в приложении происходит событие, управляющий элемент возбуждает событие, вызвав делегат для этого события Потом делегат вызывает связанные с ним методы
Для того чтобы добанить де iciar к событию, в C++ используется перегруженный оператор += Мы добавляем метод Forml_MouseDowr к событию MouseDown (Кнопка мыши нажата)
MouseDown += new MouseEventHandler
(this, Forml_MoaseDown),
Вскоре мы увидим этот код в программе
Для работы с проектами Windows Forms (Формы Windows) в Visual Studio очень важно научиться переключаться между окном конструктора (Design window), где вы работаете с управляющими этементами на форме, и окном кода (Code window), где вы работаете с кодом программы Мы можем показать это на примере двух окон проекта VsForm на С#, код стартовой системы этого проекта находится в папке VsForm\Stepl главной папки данной главы Версия этого проекта, перенесенная из С# на C++, находится в папке VsFormPortXStepl Это первая версия проекта стартовой системы, которая отображает одну и ту же строку приветствия Проекты, отвечающие разным стадиям разработки, последовательно пронумерованы, и каждой версии проекта на С# (они содержатся в папке VsForm), созданной с помощью конструктора форм (Forms Designer), соответствует перенесенная версия проекта на C++, которая содержится в папке VsFormPort
Если дважды щелкнуть на файле Forml cs проекта VsForml\Stepl в окне Solution Explorer (Поиск решения), то файл будет открыт в окне конструктора (Design window), как на рис 6 16
Для того чтобы появилось окно кода (Code window), щелкните на кнопке View Code (Просмотреть код), находящейся на инструментальной панепи Таким образом вы откроете исходный код, и вверху главной области окна вы увидите горизонтально расположенные ярлыки, с помощью которых можно выбирать нужные окна В данный момент для этой формы открыты оба окна, — и окно конструктора (Design window), и окно кода (Code window) Можно без труда вернуться в окно конструктора (Design window), щелкнув на кнопке View Designer (Открыть окно Design), находящейся на инструментальной панели На рис 6 17 вы можете увидеть внешний вид окна кода (Code window)
Приложение SimpleForm (Простая форма) — скелет стандартного приложения Windows. Вот код приложения SimpleForm (Простая форма), созданный на шаге 0:
//SimpleForm.срр - Шаг 0
// Эта версия отображает простую форму (simple form)
fusing <mscorlib.dll>
fusing <System.dll>
fusing <System.Drawing.dll> // Система
fusing <System.Windows.Forms.dll> // Система
using namespace System;
// использование пространства имен Система;
using namespace System::Windows::Forms;
// использование пространства имен Система::Windows::Формы;
_gc class Forml : public Form
// класс сборщика мусора Forml: общедоступная Форма
{
public:
Forml()
{
Size = // Размер
*_nogc new System::Drawing::Size(300,200); // Размер
Text = "Simple Form - Step 0";
// Текст = "Простая Форма - Шаг О";
}
static void Main()
{
Application::Run(new Forml);
// Приложение:: Выполнить (новая Forml);
}
};
int _stdcall WinMain(
long hlnstance, // дескриптор текущего экземпляра
long hPrevInstance, // дескриптор предыдущего экземпляра
long IpCmdLine, // командная строка
int nCmdShow // состояние отображения )
{
Forml::Main();
return 0;
}
Класс Forml является производным от System:: Windows:: Forms:: Form (Cистема::Windows::Формы::Форма). В классе System::Windows::Forms::Application (Система::Windows::Формы::Приложение) есть статические методы для управления приложением, например Run (Выполнить) и Exit (Выход). Метод WinMain создает новую форму и запускает ее в качестве главного окна приложения.
Обратите внимание, что в примерах этой главы, написанных на C++, вместо имени функции main (главная) в качестве точки входа используется WinMain. В принципе можно в функции main (главная) в рамках консольного приложения реализовать все возможности графического интерфейса пользователя. Но при этом подходе придется создать бездействующее консольное окно, что в приложениях с графическим пользовательским интерфейсом совсем ни к чему. Если же использовать WinMain вместо main (главная), то в программе не создаются консольные окна, а сразу создается главное окно.
Конструктор формы инициализирует форму. Значение в поле Size (Размер) определяет размер формы в пикселях. Поле Text (Текст) определяет заголовок, который отображается в области заголовка окна новой формы.
Ключевым классом Windows Forms (Формы Windows) является базовый класс Form (Форма). Этот класс содержит обширный набор функций, которые наследуются разрабатываемыми нами классами форм, производными от класса Form (Форма).
Чтобы создать приложение, нужно выполнить из командной строки командный файл build.bat. А чтобы запустить командный файл, откройте окно DOS, перейдите в папку SimpleFormXStep(), и введите в командной строке build (компоновка). Помните, что . перед этим необходимо правильно установить значения переменных среды. Для этого достаточно выполнить Visual Studio.NET Command Prompt.
cl /CLR SimpleForm.cpp
По умолчанию будет откомпилирован исполняемый файл Windows. В исходном коде приложения находятся директивы fusing, в которых указаны используемые библиотеки .NET: System.dll,System.Drawing.dllи System.Windows.Forms.dll.
После того, как вы откомпилировали приложение с помощью командного файла, можете запустить его, введя в командной строке SimpleForm (Простая форма). Вы также можете запустить приложение в проводнике Windows, дважды щелкнув на файле SimpleForm.exe. На Рисунок 6.2 изображен внешний вид этого простого приложения. И хотя приложение SimpleForm (Простая форма) совсем примитивное, в нем заложено множество возможностей, унаследованных созданным нами классом, который является производным от класса Form (Форма). Окно приложения можно перетаскивать по экрану, изменять его размер, свертывать, развертывать, в нем можно открывать системное меню (щелкнув кнопкой мыши в верхнем левом углу окна) и т.д.
Сообщения о работе окна
В Visual Studio.NET есть инструментальное средство под названием Spy++ (Шпион++). Эта программа "шпионит" за окнами, чтобы иметь представление о том, что происходит внутри окна. Чтобы в Visual Studio запустить Spy++ (Шпион++), нужно воспользоваться меню Tools (Сервис). Запустите версию приложения SimpxeForm.exe, полученную на шаге 0, а затем запустите Spy++ (Шпион++). Выберите Spy (Шпион) Find Window (Найти окно) — появится диалоговое окно Find Window (Найти окно). В этом диалоговом окне установите переключатель Messages (Сообщения), как на Рисунок 6.3
Левой кнопкой мыши перетащите инструмент Finder Tool (Средство поиска) (в диалоговом окне Find window (Найти окно) этот инструмент отображается в виде специальной пиктограммы — перекрестия) на окно приложения SimpleForm (Простая форма), а потом щелкните на кнопке ОК. Теперь в окно программы-шпиона Spy++ будут выводиться сообщения, информирующие обо всех взаимодействиях с окном. Окно программы-шпиона Spy++ показано на Рисунок 6.4.
Чтобы обрабатывать события, приложения Windows должны иметь специальную структуру Операционная система Windows в ответ на действие пользователя, например щелчок кнопкой мыши, выбор меню или ввод символов с клавиатуры, посылает приложению сообщение Приложения Windows должны иметь такую структуру, которая позволяет реагировать на эти сообщения
Удобство создания Windows-программ с помощью классов NET Framework состоит в том, что программировать можно на очень высоком уровне абстракции На шаге 0 вы уже убедились, насколько просто создать приложение В последующих разделах мы будем добавлять в приложение новые основные свойства графических пользовательских интерфейсов, и таким образом проиллюстрируем основы создания графических пользовательских интерфейсов с помощью классов Windows Forms (Формы Windows)
В приложении, созданном на первом шаге, будет показано, как отобразить на форме текст На Рисунок 6.5. можно увидеть, как выглядит это приложение при выполнении
Вывод данных в программах Windows сильно отличается от вывода данных в аналогичных консольных приложениях, где для этого используется метод Console: :WriteLine Вычерчивание результата в окне часто называют закрашиванием или закраской Закрашивание выполняется в ответ на сообщение "раскрасить", WM_PAINT Такой способ закрашивания по требованию гарантирует, что если окно будет накрыто каким-либо другим окном, а затем открыто снова, то содержимое окна будет отображено корректно
Еще одно отличие выполнения вывода в Windows-программах от выполнения вывода в консольных приложениях состоит в том, что необходимо определить все детали Например, нужно указать координаты области рисования, "кисть", которой будет выполняться рисование, шрифт текста, и так далее Вот код приложения, созданного на шаге 1
//SimpleForm.cpp - Шаг 1
// Эта версия отображает приветствие
fusing <mscorlib.dll>
#using <System.dll>
#using <System.Drawing.dll>
#using <System.Windows.Forms.dll>
using namespace System;
// использование пространства имен Система;
using namespace System::Windows:.Forms;
// использование пространства имен Система.:Windows::Формы;
using namespace System::Drawing;
// использование пространства имен Система::Рисование;
_gc class Forml : public Form
// класс сборщика мусора Forml: общедоступная Форма
{
private: // частный
float x, у; // с плавающей точкой Brush *pSto<Brush; // Кисть public:
Forml() {
Size = // Размер
*_nogc new System::Drawing::Size(300,200);
// Размер
Text = "Simple Form - Step 1";
// Текст = "Простая Форма - Шаг 1";
x = у = 10; pStdBrush = new SolidBrush(Color::Black);
// Красить::
// Черным } protected: // защищенный
virtual void OnPaint(PaintEventArgs * ppea)
{
ppea->get_Graphics()->Graphics::DrawString // Графика
("Hello, Window Forms", Font,
// "Привет, Формы Window ", Шрифт,
pStdBrush, x, у);
}
public:
static void Main() {
Application.:Ran(new Forml);
// Приложение:: Выполнить (новая Форма),
}
};
int _stdcall WinMain(
long hlnstance, // дескриптор текущего экземпляра
long hPrevInstance, // дескриптор предыдущего экземпляра
long IpCmdLine, // командная строка
int nCmdShow // состояние отображения )
{
Forml::Main();
return 0;
}
Для того чтобы рисовать с помощью Windows Forms (Формы Windows), нужно переопределить виртуальный метод OnPaint Класс PaintEventArgs содержит объект Graphics в качестве свойства, доступного только для чтения Класс Graphics, который принадлежит пространству имен System: -Drawing (Система Рисунок), содержит методы рисования
Параметры метода Drawstring
В качестве стандартной кисти используется черная кисть SolidBrush
На шаге 2 мы внесем изменения в приложение, чтобы при щелчке любой кнопкой мыши строка с приветствием перемещалась на место щелчка На Рисунок 6.7 можно увидеть, что после щелчка кнопкой мыши строка действительно переместилась
//SimpleForm.cpp - Шаг 2
// SimpleForm.срр - 2
// Эта версия отображает приветствие, которое может быть перемещено
// щелчком кнопки мыши
fusing <mscorlib.dll>
fusing <System.dll>
fusing <System.Drawing dll>
fusing <System.Windows.Forms dll>
using namespace System;
// использование пространства имен Система;
using namespace System::Windows. Forms;
// использование пространства имен Система::Windows::Формы;
using namespace System::Drawing;
// использование пространства имен Система:: Рисунок;
_go class Forml : public Form
// класс сборщика мусора Forml: общедоступная Форма
{
private: // частный
void InitializeComponent()
{
MouseDown += new MouseEventHandler // новый обработчик
// события
(this, Forml_MouseDown);
}
float x, у; // с плавающей точкой
Brush *pStdBrush; // Кисть public:
Forml()
{
InitializeComponent(); Size = // Размер
*_nogc new System::Drawing::Size(300,200); // Размер
Text = "Simple Form - Step 2"; // Текст = "Простая Форма - Шаг 2"; x = у = 10;
pStdBrush = new SolidBrush(Color::Black) ; // Красить::Черным
}
protected- // защищенный void Forml_MouseDown
(Object *pSender, MouseEventArgs *pmea)
{
x = pmea->X; у = pmea->Y;
Invalidate(); I
}
Во время инициализации программа связывает метод Forml_MouseDown с событием MouseDown (Кнопка мыши нажата) Этот метод устанавливает координаты текста, х и у, равными координатам точки, в которой находился указатель в момент щелчка Для того чтобы понять роль метода Invalidate (Считать недействительным), закомментируйте его и снова скомпонуйте код Щелкните мышью, чтобы перенести текст Что произойдет в результате9 Приветствие останется на том же месте Потом накройте окно приложения каким-либо другим окном, а потом снова откройте его Теперь вы увидите, что строка перемещена
Метод Invalidate (Считать недействительным) определен в базовом классе Control (Элемент управления) Существует несколько переопределенных версий этого метода Каждая из них заставляет считать недействительной определенную область управляющего элемента и посылает элементу управления сообщение о перерисовывании Метод, который не содержит параметров, заставляет считать недействительным весь управляющий элемент Для того чтобы максимально уменьшить объем перерисовывания, в более сложном приложении можно ограничиться тем, что недействительным будет считаться прямоугольник
На шаге 2М создания нашего приложения мы реализуем два разных обработчика события MouseDown (Кнопка мыши нажата). Второй обработчик по щелчку кнопкой мыши просто отображает окно сообщения.
//SimpleForm.срр - Шаг 2М
// Эта версия имеет два обработчика событий для MouseDown
_gc class Forml : public Form
// класс сборщика мусора Forml: общедоступная Форма
{
private: // частный
void InitializeComponent()
{
MouseDown += new MouseEventHandler
(this, Forml__MouseDown) ;
MouseDown += new MouseEventHandler
(this, ShowClick);
}
void Forml_MouseDown
(Object *pSender, MouseEventArgs *pmea)
{
x = pmea->X;
у = pmea->Y;
Invalidate(); }
void ShowClick (Object *pSender, MouseEventArgs *pmea)
{
MessageBox::Show("Mouse clicked!!'");
// "Мышь щелкнула!!! }
На шаге 3 мы введем в наш пример обработку еще одного события, а именно, события KeyPress (Нажатие клавиши), а также покажем, как в событии MouseDown (Кнопка мыши нажата) различать, какая кнопка была нажата, левая или правая.
Обработка событий, вызванных правой и левой кнопкой мыши
Для того чтобы определить, какая кнопка мыши была нажата, нужно использовать свойство Button (Кнопка) параметра MouseEventArgs. Правую кнопку мыши будем использовать для удаления строки с приветствием, которая хранится в элементе данных str класса StringBuilder.
void Forml_MouseDown
(Object *pSender, MouseEventArgs *pmea)
{
if (pmea->Button == МоиseButtons::Left) // если левая кнопка
{
x = pmea->X;
у = pmea->Y;
}
else if (pmea->Button == MouseButtons::Right) // если правая
// кнопка
{
pStr = new StringBuilder();
}
Invalidate() ;
}
Событие Keypress (Нажатие клавиши)
На шаге 3 мы научимся обрабатывать событие KeyPress (Нажатие клавиши). Каждый раз, когда пользователь нажмет клавишу, в конец строки приветствия будет добавлен соответствующий символ. Обратите внимание, что вместо класса String (Строка) используется класс StringBuilder, который более эффективен в этой ситуации. Объект String (Строка) — стационарный (неизменяемый), то есть, для того, чтобы реализовать добавление символов в конец строки, нужно постоянно удалять одни объекты String (Строка) и создавать другие.
StringBuilder *pStr;
void Forml_KeyPress
(Object *pSender, KeyPressEventArgs *pmea)
{
pStr—>Append(pmea->KeyChar) ; // Добавляем в конец
Invalidate() ;
}
Также, как и на шаге 2, необходимо вызвать метод Invalidate (Считать недействительным), для того, чтобы принудительно перерисовать окно приложения после сделанных изменений. На Рисунок 6.8 показано окно приложения SimpleForm (Простая форма), после удаления исходного текста и ввода нового.
На шаге 4 мы добавим в наше приложение Simple Form простое меню Для того чтобы выйти из программы, пользователь должен выбрать File => Exit (Файл => Выход), как на Рисунок 6.9.
На шаге 5 создания приложения SimpleForm (Простая форма) мы используем управляющий элемент TextBox (Поле) для отображения строки с приветствием. В более ранних версиях приложения строку можно было переместить щелчком левой кнопки мыши и удалить щелчком правой кнопки мыши. Можно было также ввести свою собственную строку с приветствием. Теперь, применив управляющий элемент TextBox (Поле), вы получите полноценные возможности редактирования. Управляющий элемент TextBox (Поле) позволяет в любом месте строки вставить символы, вырезать и вставить текст (с помощью комбинаций клавиш Ctrl+X и Ctrl+V соответственно) и так далее. Все возможности редактирования поддерживаются управляющим элементом TextBox (Поле). На Рисунок 6.10 изображено окно приложения после того, как текст приветствия был перемещен, и мы ввели некий собственный текст.
Это новая версия программы. Обратите внимание на то, что она значительно проще предыдущей, хотя и имеет гораздо более богатые функциональные возможности. Нет больше необходимости использовать переменные экземпляра для координат и текста строки приветствия (теперь эта информация хранится в управляющем элементе pTxtGreeting типа TextBox (Поле)). Не нужен больше метод OnPaint, так как управляющий элемент TextBox (Поле) знает, как нарисовать себя. Можно также избавиться от кисти. Теперь не нужно обрабатывать событие KeyPress (Нажатие клавиши), потому что оно автоматически обрабатывается управляющим элементом TextBox (Поле), притом весьма аккуратно.
//SimpleForm.cpp - Шаг 5
_gc class Forml : public Form
// класс сборщика мусора Forml: общедоступная Форма
{
private: // частный
void InitializeComponent()
{
// текст приветствия (text greeting) pTxtGreeting = new TextBox; pTxtGreeting->Location = // Местоположение
* _nogc new Point(10, 10); // новая точка pTxtGreeting->Size = // Размер
(*_nogc new struct Size(150, 20)); // новый Размер pTxtGreeting->Text = "Hello, Windows Forms"; // Текст = "Привет, Формы Windows";
Controls->Add(pTxtGreeting); // Добавить
}
float x, у; // с плавающей точкой
Brush *pStdBrush; // Кисть
Menultem *pMenuExit;
Menultem *pMenuFile;
MainMenu *pMainMenul;
TextBox *pTxtGreeting;
protected: // защищенный void Forml_MouseDown
(Object *pSender, MouseEventArgs *pmea) {
if (pmea->Button == MouseButtons::Left)
// если кнопка левая
{
pTxtGreeting->Location = // Местоположение
*_nogc new Point(pmea->X, pmea->Y); // новая точка }
else if (pmea->Button == MouseButtons::Right) // если кнопка правая {
pTxtGreeting->Text = ""; // Текст } }
};
Управляющий элемент TextBox (Поле) удобен в использовании. В инициализирующей части программы мы создаем объект TextBox (Поле) и определяем значения его свойств Location (Местоположение), Size (Размер) и Text (Текст). Мы добавляем новый управляющий элемент к коллекции управляющих элементов Controls (Управляющие элементы) этой формы. В обработчике событий мыши мы перемещаем управляющий элемент, изменив значение свойства Location (Местоположение). С помощью свойства Text (Текст) управляющего элемента TextBox (Поле) можно удалить строку с приветствием.
Событие MouseDown (Кнопка мыши нажата) является одним из предопределенных событий класса Control (Элемент управления), от которого порожден класс Form (Форма)
public: _event MouseEventHandler* MouseDown;
А вот и объявление обработчика этого события, MouseEventHandler
public _gc _delegate void MouseEventHandler(
Object* sender, // отправитель
MouseEventArgs* e
) ;
В качестве параметра обработчик событий получает объект класса Мои seEventArgs (производный от класса EventArgs) Свойства этого объекта доступны только для чтения и содержат информацию, связанную с данным событием
Button (Кнопка) определяет, какая кнопка была нажата, Clicks (Щелчки) определяет, сколько раз была нажата и отпущена кнопка, Свойство Delta (Дельта) является счетчиком оборотов колесика мыши; X и Y — координаты точки, в которой находился указатель в момент нажатия кнопки мышиКсожалению, конструктор форм (Forms Designer) не поддерживается в C++ Тем не менее, вы можете использовать конструктор форм (Forms Designer) в С#, и потом перенести полученный с помощью С# код графического интерфейса пользователя в программу на C++ Для переноса графического интерфейса пользователя в программу на C++ необходимы дополнительные усилия, и, в большинстве случаев, такой перенос особой пользы не дает Как правило, C++ не используют для разработки пользовательских графических интерфейсов, вместо этого применяется подход смешения языков, в котором для создания почьзовательского интерфейса используется С#, а в других аспектах разработки проекта из разных соображений используется C++ Поскольку вам, скорее всего, придется часто создавать графический интерфейс пользователя для многих приложений, в этой паве мы преследуем две цели Во-первых, мы хотим научить вас совместно использовать код на С# и C++ Даже если вы в основном программируете на C++, стоит ознакомиться с С# и конструктором форм (Forms Designer), — у вас появится возможность использовать те мощные инструментальные программные средства, которые не поддерживаются в C++ Во-вторых, мы приведем в пример один из немногих случаев, когда перенос кода графического интерфейса пользователя из С# в программу на C++ является целесообразным В главе представлено несколько пар примеров кода графических пользовательских интерфейсов до и после переноса
Ключевым средством взаимодействия пользователя с компьютером является графический пользовательский интерфейс (Graphical User Interface, GUI) Из этой главы вы узнаете, как создавать графический пользовательский интерфейс с помощью классов Windows Forms (Формы Windows), которые находятся в NET Framework На практике программирование Windows-приложений предполагает экстенсивное использование различных инструментальных средств и мастеров, которые намного упрощают этот процесс Однако все указанные средства автоматизации заслоняют то, что лежит в основе создания графического пользовательского интерфейса Поэтому сначала мы рассмотрим основы создания графических пользовательских интерфейсов Иными словами, мы научимся создавать простые приложения Windows с самого начала, пользуясь только комплексом инструментальных средств разработки программ NET Framework SDK Это значит, что вначале мы будем создавать простые приложения Windows без применения каких-либо специальных сервисных программ Будут рассмотрены основы рисования с помощью Windows Forms (Формы Windows) с применением шрифтов и кистей, а также необходимые обработчики событий Мы объясним принципы обработки событий в Windows Forms (Формы Windows) и реализуем обработчики событий мыши. С помощью Windows Forms (Формы Windows) мы также реализуем меню и соответствующие обработчики событий. Кроме того, мы рассмотрим управляющие элементы, а после этого изучим среду Visual Studio.NET, посредством которой можно без труда создать простой графический пользовательский интерфейс на С#. С помощью конструктора форм (Forms Designer) добавим в форму управляющие элементы, создадим меню, добавим обработчики событий и другие полезные функциональные возможности. При желании полученный в результате проект на С# можно потом перенести на C++. В заключение будут рассмотрены диалоговые окна и такой элемент управления, как список.
Для ознакомления с классами Windows Forms (Формы Windows) полезно будет создать простое приложение SimpleForm (Простая форма) в несколько шагов. Ни на одном из этих шагов мы не будем использовать средства проектирования Visual Studio. Используя интерфейс командной строки, необходимо запустить командный файл build. bat.
В программе, которую мы только что рассматривали, объект pMainMenul является управляющим элементом. Данный объект — указатель на экземпляр класса MainMenu (Главное меню). Управляющий элемент — это объект на форме, который придает форме новые функциональные возможности. Управляющий элемент автоматически выполняет много заданий "по поручению" формы. Использование управляющих элементов упрощает программирование, так как программисту не надо беспокоиться о рисовании, о том, следует ли считать представление формы недействительным, не нужно думать о графических элементах и так далее. Для того чтобы реализовать простое меню с самого начала, нам пришлось написать довольно много кода. Управляющие элементы, которые реализуются с помощью объектов, содержат богатый, вполне пригодный для повторного использования код.
В .NET Framework поддерживается несколько управляющих элементов, с помощью которых можно отобразить списки элементов. Эти управляющие элементы позволяют пользователю выбрать элемент списка; обычно для выбора элемента требуется щелкнуть на нем. В этом разделе мы рассмотрим управляющий элемент ListBox (Список элементов).
Пример программы находится в папке HotelAdmm\Step3. Главная форма в файле MainAdminForm. cs содержит список элементов listHotels, в котором хранится список гостиниц. Каждая гостиница представлена в виде строки, а значения атрибутов гостиницы разделены запятыми.
И хотя вполне реально создать приложение Windows Forms (Формы Windows), используя в командной строке только комплекс инструментальных средств разработки программ .NET Framework SDK, на практике подобную работу намного проще выполнить с помощью Visual Studio.NET. К сожалению, в Visual Studio.NET нет средств для генерирования проекта пусковой системы на управляемом C++ на основе Form (Форма), и управляемый C++ не поддерживает конструктор форм (Forms Designer). Однако для начала можно создать проект Windows-приложения на С# (Windows Application). При этом будет сгенерирован код пусковой системы и будут установлены ссылки на необходимые библиотеки .NET. Затем можно в конструкторе форм (Forms Designer) перетащить управляющие элементы с инструментальной панели на вашу форму. Конструктор форм (Forms Designer) вставит необходимый шаблон кода на С#, который поддерживает функционирование этих управляющих элементов в форме. В окне Properties (Свойства) несложно определить свойства управляющего элемента в процессе проектирования. Можно, конечно, определить эти свойства и во время запуска приложения, как мы это сделали для поля pTxtGreeting в предыдущем примере. После этого можно перенести код С# в программу на управляемом C++, но этого обычно не рекомендуется делать.
Чтобы в списке элементов выбрать какой-нибудь элемент, нужно щелкнуть на нем. Выбор элемента вызовет событие SelectedlndexChanged. Доступ к выбранному элементу можно получить с помощью свойств Selectedlndex и Selectedltem. Если никакой элемент не выбран, значение Selectedltem будет равно -1. Ниже приведен код обработчика события SelectedlndexChanged.
private void listHotels_Selected!ndexChanged(object sender,
System.EventArgs e) {
if (listHotels.Selectedlndex != -1)
{
string selected - (string) listHotels.Selectedltem;
// выбранная строка
char [ ] sep = new char[] {','};
// символ
string[] fields;
// строка [] поля;
fields = selected.Split(sep);
// поля = выбранное, разбить;
currHotel = new HotelListltem();
currHotel.City = fields[0];
// Город = поля [О];
currHotel.HotelName = fields[1];
// поля [1]
currHotel.NumberRooms = Convert.Tolnt32(fields[2]);
// поля [2]
currHotel.Rate = Convert.ToDecimal(fields[3]);
// поля [3]
}
else
{
currHotel.HotelName = "";
}
}
Поскольку в списке элементов элементы хранятся в виде объектных ссылок, мы выполняем приведение типа выбранного элемента к типу string (Строка). Для того чтобы выделить значения полей, разделенные запятыми, используется метод String. Split (Строка.Разбиение), затем значения сохраняются в массиве строк fields (поля). Из массива эти значения переносятся в currHotel, где они и хранятся. В предыдущем разделе мы использовали currHotel для инициализации диалоговых окон New Hotel (Новая гостиница) и Change Hotel Information (Изменение информации о гостинице).
Давайте сделаем так, что каждый раз, когда пользователь попытается закрыть приложение, приложение будет его спрашивать, действительно ли он хочет выйти Существует несколько способов закрыть окно-
щелкнуть на кнопке "X" (Закрыть) в правом верхнем углу окна; закрыть окно из системного меню в левом верхнем углу окна, закрыть окно с помощью комбинации клавиш AU+F4, выйти из приложения с помощью меню FileOExit (ФайлОВыход)Когда закрывается форма, вызывается событие Closing (Процедура завершения) Можно остановить процедуру завершения, установив в обработчике этого события свойство Cancel (Отмена). (Сначала нужно, как обычно, добавить обработчик события Closing (Процедура завершения) ) Просто напечатайте код MessageBox (Окно сообщения), приведенный ниже
protected void Forml_Closing (object sender.
System.ComponentModel.CancelEventArgs e)
{
DialogResult status = MessageBox.Show( // состояние
"Do you want to close",
// "Хотите закрыть?",
"Simple Form (VS)", MessageBoxButtons.YesNo);
if (status == DialogResult.No)
// если (состояние == DialogResult. Нет)
{
e.Cancel = true; // Отмена = истина
}
}
Для того чтобы получить нужное нам поведение приложения, обработчик меню FileOExit (ФайлОВыход) должен не выходить из приложения, а закрыть главное окно, вызвав метод С lose (Закрыть)
protected void menuExit_Click (object sender,
System.EventArgs e)
{
//Application.Exit();
// Приложение.Выход ();
Close () ;
}
Теперь проект соответствует проекту, находящемуся в папке VSForm\Step4 Запустите программу, и попытайтесь закрыть окно приложения разными способами В любом случае должно появиться диалоговое окно, показанное на Рисунок 6.20.
И, наконец, как обычно, перенесите код проекта, подготовленного на С#, в проект на C++ VSFormPort\Step4. Скомпонуйте и выполните программу на C++. Убедитесь в том, что она работает так же, как и программа на С#
// VSForm - Step4
_gc class Forml : public System:-.Windows :: Forms :: Form
{
private: // частный
void InitializeComponent()
{
Closing += // Закрытие
new CancelEventHandler(this, Forml_Closing) ;
}
void menuExit_Click(
Object *sender, System::EventArgs *pe)
{
//Application::Exit();
// Приложение:: Выход ()
; Close();
}
void Form1_Closing(
Object *sender, CancelEventArgs *e)
{
int status = MessageBox::Show( // состояние
"Do you want to close",
// "Хотите закрыть",
"Simple Form (VS)", // Простая Форма
MessageBoxButtons::YesNo);
if (status == DialogResult::No)
// если (состояние == DialogResult::No)
{
e->Cancel = true; // Отмена = истина
}
}
};
Все оставшиеся примеры в этой главе написаны исключительно на С#!
Далее в этой главе мы будем рассматривать только программы на С#, поскольку даже в программах, которые в основном написаны на C++, при разработке графических пользовательских интерфейсов, как правило, используется С#. Причина этого проста— в C++ не поддерживается конструктор форм (Forms Designer). Всегда помните о том, что вы можете сначала создать графический пользовательский интерфейс на С#, а потом, если понадобится, перенести этот код в C++. В предыдущих примерах мы показали, как это сделать.
Проект CAB (CAB project) создает САВ-файл, содержащий любое количество других файлов, которые могут использоваться для традиционных целей развертывания Файлы CAB использовались для упаковки старых компонентов, построенных на основе модели компонентных объектов Microsoft (COM), для развертывания их по Internet Кроме того, они использовались в традиционных инсталляционных программах, которые выполняли установку с компакт-дисков Теперь файлы CAB также могут использоваться для того, чтобы упаковать управляемый код, однако при развертывании в NET файл CAB может содержать только одну сборку, причем он должен иметь то же самое имя, что и содержащаяся в нем сборка, но с расширением cab Например, сборка SomeComponent.dll может содержаться в файле CAB с именем SomeComponent. cab
Частное развертывание сборки просто означает, что конечный пользователь копирует сборку в тот же самый каталог, что и клиентская программа, использующая ее. Не нужна никакая регистрация, и не требуется никакая причудливая инсталляционная программа. Кроме того, не требуется никакая очистка системного реестра, не нужна также и никакая программа деинсталляции для удаления компонента. Чтобы деинсталлировать сборку, просто удалите ее с жесткого диска.
Конечно, ни один программист из самоуважения никогда не поставит коммерческий компонент, который конечный пользователь должен вручную копировать или удалять какие-либо файлы подобным способом, даже если сделать это очень просто. Пользователи привыкли использовать формальную инсталляционную программу, так что она должна поставляться, даже если ее работа тривиальна. Однако, ручное копирование и удаление сборки — идеальный способ быстрого и безболезненного управления развертыванием реализаций при разработке, тестировании, отладке и испытании. Как вы помните, развертывание компонентов, построенных на основе модели компонентных объектов Microsoft (COM), никогда не было настолько просто. Ведь для развертывания требовался как минимум файл сценария, чтобы занести в системный реестр информацию о компоненте к клиентским программам и о среде времени выполнения модели компонентных объектов Microsoft (СОМ). С появлением сборок не нужно при инсталляции конфигурировать системный реестр, а, значит, позже, когда вы захотите отказаться от компонента, не нужно заботиться о том, чтобы тщательно стереть информацию из системного реестра.
Далее мы рассмотрим, как выполнить частное развертывание сборки для простого NET-компонента. В следующем разделе будет показано, как развернуть общедоступную сборку в глобальном кэше сборок.
Скомпоновав компонент сборки, можно создать клиентскую программу, которая вызывает общедоступные методы компонента Следующий код показывает пример такой клиентской программы, вызывающей общедоступный метод AddEmUp, приведенный ранее. Конечно, в случае необходимости, путь к сборке SimpleComponent.dll в инструкции fusing должен быть откорректирован, чтобы компилятор смог найти нужные данные. Кроме того, сборку нужно развернуть, чтобы общеязыковая среда выполнения CLR могла определить местонахождение сборки и загрузить ее во время выполнения. В этом подразделе развертывание достигается простым копированием сборки в каталог клиентской программы.
//SimpleComponentClient.срр
finclude "stdafx.h"
fusing <mscorlib.dll>
using namespace System;
// использование пространства имен Система;
fusing fusing <C:\OI\NetCpp\Chap7\SimpleComponent\Debug\
SimpleComponent.dll> using namespace SimpleComponent;
// использование пространства имен SimpleComponent;
void main()
{
SomeClass *psc = new SomeClass;
int sum = psc->AddEmUp(3, 4); // суммировать
Console::WriteLine(sum); // сумма
}
Как найти SimpleComponent?
Обратите внимание, что программа SimpleComponentClient имеет инструкцию fusing, указывающую компилятору, где найти сборку SimpleComponent, метаданные которой компилятор использует для контроля соответствия типов. Однако, если вы попробуете выполнить клиентскую программу, то возникнет исключение System. IQ.FileNotFoundException. Так получится потому, что загрузчик класса общеязыковой среды выполнения CLR неспособен найти сборку SimpleComponent. Чтобы выполнить клиент, нужно только скопировать сборку SimpleComponent в тот же самый каталог, где находится программа SimpleComponentClient.ехе.
Теперь можно рассмотреть и сравнить декларацию сборки этой клиентской программы, чтобы увидеть, как она взаимодействует с декларацией сборки SimpleComponent, приведенной ранее. Чтобы рассмотреть декларацию клиентской программы, используйте команду
Ildasm SimpleComponentClient.exe
Можно заметить, что декларация клиентской программы содержит следующую внешнюю зависимость от сборки SimpleComponent.
.assembly extern SimpleComponent
{
.hash = (2A 1C 2D D7 CA 9E 7E D5 08 5B DO 75 23 D3 50 76
5E 28 EA 31 )
.ver 1:0:584:39032
}
Из этого следует, что клиентская программа ожидает использовать сборку SimpleComponent с номером версии 1:0:584:39032. Однако, поскольку сборка развернута частным образом, на самом деле, когда клиент загружает эту сборку, принятая по умолчанию в общеязыковой среде выполнения CLR политика проверки значения версии проверку номера не выполняет. Просто ответственность за то, что в данном конкретном каталоге развернута нужная версия, возлагается на администратора или инсталляционную программу. Эту заданную по умолчанию политику проверки версии можно отменить, используя файл конфигурации. Хотя в клиентской декларации имеется хэш-код компонента, он фактически не используется общеязыковой средой выполнения CLR для того, чтобы проверить подлинность двоичного кода. И снова это происходит потому, что сборка развернута частным образом. В следующем разделе мы увидим, что номер версии и хэш-код используются автоматически для проверки содержимого кода общедоступных сборок, развернутых в глобальном кэше сборок.
Чтобы развернуть сборку в глобальном кэше сборок, надо, чтобы она имела цифровую подпись. Разработчики могут разместить сборку в глобальном кэше сборок, используя утилиту Gacutil .exe (Global Assembly Cache utility), Проводник Windows (Windows Explorer) с расширением оболочки Windows посредством просмотра кэша сборок, или.
Инструмент администрирования NET (.NET Admin Tool). Инсталляция общедоступных сборок на конкретной машине конечного пользователя должна быть сделана с помощью программы установки компонентов (системы) по выбору пользователя.
Процесс цифрового подписания сборки включает генерацию криптографической пары открытого и секретного ключа, вычисление хэш-кода сборки, шифровку хэш-кода с помощью секретного ключа, и запись в сборку зашифрованного хэш-кода вместе с открытым ключом.
Зашифрованный хэш-код и открытый ключ вместе составляют полную цифровую сигнатуру (подпись). Обратите внимание, что цифровая подпись (сигнатура) записана в зарезервированную область сборки, не участвующую в вычислении хэш-кода. Когда все это сделано, сборка может быть развернута в глобальном кэше сборок (GAC). Все указанные шаги выполняются тремя простыми инструментальными средствами: утилитой Strong Name (Strong Name utility — Sn. exe), компоновщиком сборок (Assembly Linker — Al.exe) и утилитой Global Assembly Cache (Global Assembly Cache utility — Gacutil.exe). Чтобы скомпоновать, подписать в цифровой форме и развертывать общедоступную сборку, необходимо выполнить следующие шаги:
1. Разработать и скомпоновать компонент.
2. Сгенерировать пару открытого и секретного ключей.
3. Вычислить хэш-код содержимого сборки.
4. Зашифровать хэш-код, используя секретный ключ.
5. Поместить зашифрованный хэш-код в декларацию.
6. Поместить открытый ключ в декларацию.
7. Поместить сборку в глобальный кэш сборок.
Конечно, шаг 1 обычно выполняется с помощью Visual Studio.NET. Шаги 2 — 6 представляют собой цифровое подписание. Шаг 2 выполняется с помощью утилиты Strong Name— Sn.exe. Шаги 3—6 выполняются с помощью Visual Studio.NET или компоновщика сборок — утилиты Assembly Linking — Al. exe ("1" — это "эль", а не "один"). Чтобы на шаге 7 поместить сборку в глобальный кэш сборок, применяется утилита Global Assembly Cache— Gacutil.exe, Проводник Windows (Windows Explorer), или Инструмент администрирования .NET (.NET Admin Tool).
Сейчас мы опишем первый шаг— создание компонента. Для целей демонстрации мы используем пример, подобный предыдущему примеру сборки SimpleComponent, но он называется SharedComponent, и будет развернут в глобальном кэше сборок. Сначала должен быть создан новый проект SharedComponent; для этого используется шаблон Managed C++ Class Library (Управляемый C++ на основе Библиотеки классов), причем нужно добавить следующий код:
//SharedComponent.срр
#include "stdafx.h" // имеет fusing <mscorlib.dll>
#include "SharedComponent.h"
//SharedComponent.h
using namespace System;
// использование пространства имен Система;
namespace SharedComponent
// пространство имен SharedComponent
{
public _gc class SomeClass
// класс сборщика мусора SomeClass
{
public:
int AddEmUpdnt i, int j)
{
return i+j;
}
};
}
На следующем шаге нужно сгенерировать криптографическую пару. Генерацию криптографической пары можно выполнить с помощью утилиты Sn.exe. Эта утилита известна как утилита Strong Name, но иногда также называется и утилитой Shared Name, однако, этот последний термин теперь осуждается. Данный инструмент генерирует криптографически стойкое имя сборки. Вы генерируете криптографическую пару ключей (открытого и секретного) и размещаете ее в файле KeyPair.snk, как показано в следующей команде.
sn -k KeyPair.snk
Получающийся двоичный файл KeyPair. snk не предназначен для чтения пользователю. Но если вы любопытны, можете записать эти ключи в текстовый файл, в котором поля разделяются запятыми. Это делается с помощью следующей команды:
sn -о KeyPair.snk KeyPair.txt
Теперь можете рассмотреть полученный файл с помощью Блокнота (Notepad.exe); однако это не обязательно.
На следующем шаге нужно применить секретный ключ к сборке. Это может быть сделано при компиляции, что, конечно, является полезным для разработки и тестирования; однако, когда придет время промышленного выпуска сборки, нужно будет поместить "строку с названием компании, да и вообще придется использовать более формальный подход. Для защиты корпоративной цифровой подписи предприятия официальный секретный ключ держится в секрете и потому не может быть сообщен программисту. Вместо него программист может при разработке и проверке (испытании) использовать заменяющую криптографическую пару, которая применяется во время компиляции автоматически. Тогда перед выпуском сборки уполномоченный сотрудник ставит официальную корпоративную цифровую сигнатуру (подпись), используя секретный ключ, строго хранимый в тайне. Это делается после компиляции с помощью инструментального средства А1. ехе. Указанный инструмент будет описан в этом подразделе позже. Однако, чтобы во время компиляции применить цифровую сигнатуру (подпись) автоматически, вы просто используете определенный атрибут C++, как показано в следующем коде. В частности, обратите внимание, что файл KeyPair.snk, сгенерированный предварительно инструментом Sn. ехе, определен в атрибуте AssemblyKeyFileAttribute.
//Assemblylnfо.срр
#include "stdafx.h" // имеет fusing <mscorlib.dll>
using namespace System::Reflection;
// использование пространства имен Система::Отражение;
using namespace System::Runtime::CompilerServices;
// использование пространства имен
// Система::Время выполнения:: CompilerServices;
[assembly:AssemblyTitleAttribute(""}];
[assembly:AssemblyDescriptionAttribute("")];
[assembly:AssemblyConfigurationAttribute("")];
[assembly:AssemblyCompanyAttribute("")];
[assembly:AssemblyProductAttribute("")];
[assembly:AssemblyCopyrightAttribute("")];
[assembly:AssemblyTrademarkAttribute("")];
[assembly:AssemblyCultureAttribute("")];
[assembly:AssemblyVersionAttribute("1.0.*") ];
[assembly:AssemblyDelaysignAttribute(false)] ;
[assembly:AssemblyKeyFileAttribute("KeyPair.snk") ];
[assembly:AssemblyKeyNameAttribute("")];
После добавления файла KeyPair. snk к AssemblyKeyFileAttribute, сборку нужно перетранслировать. Тогда при следующем запуске Ildasm. ехе покажет полученную в результате информацию, которая была включена в декларацию сборки для динамически подключаемой библиотеки (DLL) SharedComponent. Обратите внимание, что новая запись называется .publickey. В этой записи содержится открытый ключ создателя, который находится в файле KeyPair. snk. Именно этот открытый ключ может использоваться для расшифровки профиля сообщения, чтобы найти первоначальный хэш-код. Когда сборка развернута в глобальном кэше сборок, этот расшифрованный хэш-код сравнивается с новым, вычисленным по фактическому содержимому сборки, хэш-кодом. Такое сравнение позволяет определить, законна ли сборка (т.е. идентична оригиналу), или незаконна (т.е. разрушена или подделана). Конечно, когда вы используете Sn.exe, эта утилита сгенерирует другую криптографическую пару, и открытый ключ, приведенный ниже, будет отличаться от вашего.
.assembly SharedComponent
{
.publickey = (00 24 00 00 04 80 00 00 94 00 00 00 ...
... 56 5А Bl 97 D5 FF 39 5F 42 DF OF 90 7D D4 )
.hash algorithm 0x00008004
.ver 1:0:584:42238
}
Чтобы проверить разработанную нами сборку SharedComponent, мы должны создать испытательную клиентскую программу, а затем, вместо того, чтобы копировать SharedComponent.dll в каталог клиента, мы применим утилиту Gacutil.exe (Global Assembly Cache utility), или Проводник Windows (Windows Explorer), или Инструмент администрирования .NET (.NET Admin Tool), чтобы развернуть сборку в глобальном кэше сборок. Следующий код представляет собой испытательную клиентскую программу.
//SharedComponentClient.срр
linclude "stdafx.h"
#using <mscorlib.dll>
using namespace Systeir;
// использование пространства имен Система;
#using <C:\OI\NetCpp\Chap7\SharedComponent\Debug\
SharedComponent.dll>
using namespace SharedComponent;
// использование пространства имен SharedComponent;
using namespace System;
// использование пространства имен Система;
void main()
{
SomeClass *psc = new SomeClass;
int sum = psc->AddEmUp(3, 4); // суммируем
Console::WriteLine(sum); // сумма
}
Если бы вышеупомянутая испытательная клиентская программа была выполнена до инсталляции компонента-сборки, во время выполнения было бы запущено исключение FileNotFoundException, потому что сборку не удалось бы найти. Но на сей раз мы развертываем сборку в глобальном кэше сборок с помощью утилиты Gacutil.exe (Global Assembly Cache utility) Напомним, это только один из нескольких методов.
Gacutil -1 SharedComponent.dll
Затем можно выполнить клиент, причем теперь он должен работать надлежащим образом.
После этого вы должны увидеть на консоли сообщение Assembly successfully added to the cache (Сборка успешно добавлена в кэш). В результате выполнения приведенной выше команды в каталоге \WINNT\Assembly был создан новый глобальный узел кэша сборки, имя этого узла — SharedComponent. Как видно на Рисунок 7.4, номер версии и создатель (т.е. лексема открытого ключа) сборки отображаются в Проводнике Windows (Windows Explorer)
Чтобы установить сборку в глобальный кэш сборок (GAC), можно также использовать Проводник Windows (Windows Explorer) — перетащите и опустите компонент в каталог Assembly. Кроме того, чтобы установить сборку в глобальный кэш сборок (GAC), можно использовать Инструмент администрирования .NET (.NET Admin Tool). Инструмент администрирования .NET (.NET Admin Tool) встроен в ММС и расположен в \WINNT\Microsoft.NET\Framework\vl.0.2914\mscorcfg.msc. Номер версии в каталоге будет отличен в более позднем выпуске .NET Framework. Хотя наличие третьего инструмента может показаться избыточным, эта встроенная в ММС утилита очень полезна, потому что упрощает решение многих задач На Рисунок 7.5 показано окно верхнего уровня данного инструмента. Чтобы с его помощью добавить сборку в глобальный кэш сборок (GAC), достаточно всего лишь выбрать Assembly Cache в левой области окна, щелкнуть правой кнопкой мыши, и выбрать Add (Добавить). В появившемся диалоговом окне переместитесь к нужному файлу, выберите сборку, которую хотите добавить, и щелкните на кнопке Open (Открыть).
Теперь, когда общедоступная сборка развернута в глобальном кэше сборок, клиентская программа SharedComponentClient может использовать нашу сборку. После того, как вы установили сборку в глобальном кэше сборок (GAC), вы можете переместить клиентскую программу в другой каталог. Вы можете выполнять клиента, причем нет необходимости перемещать сборку, установленную в глобальном кэше сборок (GAC).
Но что случается тогда, когда мы изменяем главную или младшую версию SharedComponent, или подписываем ее другим секретным ключом, а клиентскую программу SharedComponentClient не перекомпоновываем? Если сделать это со сборкой SimpleComponent, развернутой в частном порядке, то общеязыковая среда выполнения CLR не предпринимает никаких попыток обнаружить такое несоответствие (когда действует заданная по умолчанию политика) Однако, если сделать это с общедоступной сборкой, общеязыковая среда выполнения CLR запустит FileNotFoundException, когда обнаружит, что главная или младшая версия либо лексема открытого ключа отличается от того, что было установлено в соответствующем поле при компоновке клиента. Обратите внимание, что это исключение не будет вызвано, если реализация изменена, но главный и младший номер версии не изменены, а сборка подписана тем же самым секретным ключом.
Если сборка должна быть развернута в глобальном кэше сборок, то необходимо, чтобы она имела цифровую подпись. Цифровая подпись (сигнатура) не требуется и не особенно полезна для сборки, развернутой частным образом, так как частная сборка развертывается пользователем для того, чтобы работать со своей определенной клиентской программой и потому согласована с ней. Если даже развернутая частным образом сборка имеет цифровую подпись, общеязыковая среда выполнения CLR по умолчанию не проверяет этого, когда загружает сборку для клиентской программы. Поэтому ответственность за предотвращение неправомочной модификации или подтасовки развернутых частным образом сборок полностью возложена на администратора. С другой стороны, очень выгодно использовать общедоступно развернутые (т.е. общедоступные) сборки, так как они должны быть подписаны в цифровой форме, и потому обычно их используют многие клиенты, причем рядом могут существовать несколько версий одной сборки.
Цифровые подписи (сигнатуры) основаны на криптографических методах, в которых применяются открытые ключи. В мире криптографии применяется два основных криптографических метода — симметричные шифры (общий ключ) и асимметричные шифры (открытый ключ). Симметричные шифры совместно используют секретный ключ как для кодирования, так и для расшифровки. Стандарт шифрования данных DES (Data Encryption Standard), Triple DES (Тройной стандарт шифрования данных) и RC2 — примеры симметричных алгоритмов шифрования. Симметричные шифры могут быть очень эффективными и мощными в обеспечении секретности сообщения между двумя доверяющими друг другу сотрудничающими лицами, но они не совсем подходят для ситуаций, где трудно совместно использовать секретный ключ. По этой причине симметричные шифры считаются неподходящими для цифровых подписей (сигнатур). Именно потому цифровые подписи (сигнатуры) используются не для секретности, а лишь для идентификации и опознавания, что является более открытым делом. Если бы вы совместно использовали ваш симметричный ключ с каждым, кто потенциально хотел бы убедиться в подлинности ваших сборок, то по неосторожности могли доверить его людям, которым захотелось исполнить вашу роль. Для использования в цифровых подписях (сигнатурах) намного лучше подходят асимметричные шифры.
Асимметричные шифры, которые также называются шифрами с открытым ключом, используют криптографическую пару открытого и секретного ключа. Ключи в паре математически связаны, и генерируются они вместе; однако чрезвычайно трудно вычислить один ключ по другому. Обычно открытый ключ выдается каждому, кто хотел бы убедиться в подлинности владельца сборки С другой стороны, владелец сохраняет соответствующий секретный ключ подписи в тайне, чтобы никто не мог подделать его подпись. Метод шифрования по схеме открытого ключа RSA (RSA-кодирование) — пример системы шифрования с открытым ключом.
Шифрование с открытым ключом основано на очень интересной математической схеме, которая позволяет зашифровать обычный текст одним ключом, а расшифровать, только зная ключ, соответствующий исходному Например, когда открытый ключ используется, чтобы зашифровать первоначальные данные (называемые открытым текстом), только соответствующий секретный ключ может помочь в расшифровке этого текста. Даже ключ, который использовался для шифрования, не поможет в расшифровке зашифрованного текста! Этот сценарий полезен тогда, когда секретные сообщения посылаются только человеку, который знает секретный ключ.
Рассмотрим теперь противоположный сценарий. Человек, который знает секретный ключ, использует его для того, чтобы зашифровать открытый текст. Получающийся зашифрованный текст не является тайной, так как каждый заинтересованный может получить открытый ключ, чтобы расшифровать данный текст. Этот сценарий бесполезен для сохранения тайны, но очень эффективен для опознавательных целей. Нет необходимости шифровать исходные данные полностью, поэтому, чтобы повысить эффективность, вместо них шифруется компактный хэш-код, который с высокой вероятностью характерен для исходных данных. Если вы получаете файл, который содержит зашифрованную версию его собственного хэш-кода, и расшифровываете его с помощью соответствующего открытого ключа, то фактически вы повторно вычислите хэш-код исходных данных. И если теперь вы обнаружите, что он совпадает с хэш-кодом, который был зашифрован, можете быть совершенно уверены, что именно владелец секретного ключа приложил цифровую подпись и данные не были изменены другими лицами Если предположить, что владелец сумел сохранить тайну секретного ключа, тогда совпадение результатов вычисления доказывает, что никто не смог бы исказить файл после того, как он был подписан в цифровой форме. На Рисунок 7.3 показано, как работает цифровая подпись (сигнатура).
Цифровое подписание методом шифрования по схеме открытого ключа RSA и SHA1
Чтобы подписать сборку, производитель вычисляет с помощью алгоритма SHA1 ее хэш-код (причем байты, зарезервированные для подписи (сигнатуры), предварительно обнуляются), и затем зашифровывает значение хэш-функции с помощью секретного ключа, используя метод шифрования по схеме открытого ключа RSA (RSA-кодирование). Открытый ключ и зашифрованный хэш-код сохраняются в метаданных сборки.
Заданная по умолчанию политика управления версиями часто оказывает желательное действие, причем она действует автоматически, если не существует никакого файла конфигурации. Однако иногда требуется более тонкое управление процессом связывания с общедоступной сборкой. Как упоминалось ранее, чтобы сделать это для выполняемых клиентских программ, нужно создать файл конфигурации, содержащий XML-документ с тем же самым именем, что и у клиента, но с расширением . conf ig, и поместить его в тот же самый каталог, где находится клиентская выполняемая программа. Это может быть полезно в тех случаях, когда вы хотите отключить заданную по умолчанию автоматическую политику QFE, или если вы хотите использовать версию компонента-сборки, отличную от той, которая была указана при компиляции клиентской программы. Важно обратить внимание, что файл конфигурации приложения может затронуть поведение связывания только единственного приложения-клиента. Он не может быть применен ко всем клиентским программам, использующим данную сборку.
Для .NET файлов конфигурации тэгом верхнего уровня является <configuration> (<конфигурация>). Информация о связывании сборки находится в разделе <runtime> (<во время выполнения>). Пример файла конфигурации может выглядеть следующим образом:
<?xml version="l.0"?> <!-- версия -->
<configuration> <!-- конфигурация -->
<runtime> <!—во время выполнения -->
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.vl">
<dependentAssembly>
<assemblyldentity name="Customer"
<!-- Клиент -->
publicKeyToken="8bOe612d60bdeOca" />
<bindingRedirect oldVersion="l.0.0.0-1.1.0.0"
newVersion="l.1.0.0" />
</dependentAssemblу>
</assemblyBinding>
</runtime>
<!--во время выполнения -->
</configuration>
<!-- конфигурация -->
Правила, определяющие политику управления версиями, находятся в разделе <assemblyBinding>. XML-спецификация пространства имен необходима. Каждая сборка, политику управления версиями которой мы хотим установить, помещается в ее собственный раздел <dependentAssembly>. Элемент assemblyldentity имеет атрибуты, определяющие сборку, к которой этот раздел относится. Атрибут name (имя) обязателен; атрибуты culture (культура) и publicKeyToken являются необязательными. Атрибуты элемента bindingRedirect определяют то, что версии могут отобразить на другую версию. Атрибутом oldVersion может быть диапазон; атрибут newVersion может быть установлен только на одну версию. В вышеупомянутом примере любые ссылки на версии от 1.0.0.0 до 1.1.0.0 могут быть разрешены с помощью версии 1.1.0.0. Другими словами, версия 1.1.0.0 обратно совместима со всеми указанными версиями. Можно определить несколько элементов bindingRedirect.
Для установки файла конфигурации можно использовать Инструмент администрирования .NET (.NET Admin Tool). Чтобы запустить этот инструмент, дважды щелкните на \WINNT\Microsoft.NET\Framework\vl.0.2914\mscorcfg.msc в Проводнике Windows (Windows Explorer). Затем добавьте приложение к инструменту, щелкнув правой кнопкой мыши на Applications (Приложения) в левой области окна, и выберите Add (Добавить) из контекстного меню. Переместитесь к приложению, которое вы хотите конфигурировать.
Выберите его и щелкните на кнопке Open (Открыть). На Рисунок 7.7 показан Инструмент администрирования (Admin Tool) после того, как выполнены все указания.
Возможно, вы помните, что в предыдущей главе пример программы был реализован на С#, а не на управляемом C++, так как в нем было много кода графического интерфейса пользователя, который намного лучше реализуется на С#, чем на C++. Для целей данной главы все это вполне бы пригодилось, так как процесс развертывания одинаков для сборок, написанных на любом языке .NET, включая С# и управляемый C++. Однако проект был частично преобразован (конвертирован) из С# в управляемый C++. На данном этапе изучения примера мы разбили нашу программу администратора гостиницы (Hotel Administrator) на три сборки. В каталоге CaseStudy для этой главы имеется прикладная программа AcmeGui (файл с расширением ЕХЕ), написанная на С#, и два компонента (динамически подключаемые библиотеки (DLL)) сборки — Customer (Клиент) и Hotel (Гостиница), которые были реализованы на управляемом C++. Часть, написанная на С#, посвящена формам (т.е. функциональным возможностям графического интерфейса пользователя (GUI)). Код неграфического интерфейса пользователя, связанный с классами Customer (Клиент) и Hotel (Гостиница), а также связанные с ним классы, интерфейсы и структуры, в предыдущей версии примера программы были перемещены в новые сборки, представляющие собой динамически подключаемые библиотеки (DLL), написанные на управляемом C++.
На Рисунок 7.9 видно, как Solution Explorer (Поиск решения, Проводник решения) показывает, что проект AcmeGui имеет ссылки на сборки (динамически подключаемые библиотеки (DLL)) Customer (Клиент) и Hotel (Гостиница). Эти ссылки дают возможность компилятору найти типы Customer (Клиент) и Hotel (Гостиница), используемые в AcmeGui и затем скомпоновать приложение. Они не диктуют, куда нужно поместить динамически подключаемые библиотеки (DLL) при развертывании проекта. Обратите также внимание на ссылки на системные сборки, такие как System.dll. Посмотрев на свойства ссылок, вы узнаете, где расположена сборка.
Однако в пределах существующего решения AcmeGui довольно просто создать проект на основе шаблона Managed C++ Class Library (Управляемый C++ на основе Библиотеки классов). Щелкните правой кнопкой мыши на узле Solution AcmeGui (Решение AcmeGui) в Solution Explorer (Поиск решения, Проводник решения), затем выберите Add => New Project (Добавить => Новый проект). Выберите тип Visual C++ Project (Проект на Visual C++), потом выберите шаблон Managed Class Library (Библиотека Управляемых классов), найдите CaseStudy, и назовите проект Customer (Клиент). В Visual Studio.NET выберите Class Library (Библиотека классов) в New Project Wizard (Мастер создания проектов), затем укажите местоположение и имя, после чего щелкните на ОК. Проект Customer (Клиент) находится в папке C:\OI\NetCpp\Chap7\CaseStudy. То же самое сделайте для другой сборки — Hotel (Гостиница), которая представляет собой динамически подключаемую библиотеку (DLL). Чтобы установить в вашем проекте ссылку на каждую из сборок, которые в данном примере представляют собой динамически подключаемые библиотеки (DLL), используйте оператор ttusing. Конечно, библиотеки должны быть созданы прежде, чем на них можно будет сослаться.
На Рисунок 7.10 показан верхний уровень, который вы увидите, когда откроете сборку Customer.dll в ILDASM и выполните двойной щелчок на пространстве имен 01 .NetCpp.Acme. Вы видите точки входа для декларации MANIFEST (МАНИФЕСТ, ДЕКЛАРАЦИЯ), классов Customers (Клиенты) и Customer (Клиент), интерфейса ICustomer и типа CustomerListltem. Если щелкнуть на кнопке плюс (+), точка входа будет развернута.
Чтобы рассмотреть декларацию, дважды щелкните на узле MANIFEST (МАНИФЕСТ, ДЕКЛАРАЦИЯ), показанном на Рисунок 7.10, и в результате будет отображена информация, относящаяся к декларации (Рисунок 7.11). Некоторые числа будут другими, если вы заново создали какой-либо из примеров, или имеете более позднюю версию .NET.
Декларация содержит информацию о зависимостях и содержимом сборки. Видно, что декларация для Customer (Клиент) содержит, среди других, следующую внешнюю зависимость:
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 EO 89 )
.hash = (09 BB ВС 09 EF 6D 9B F4 F2 CC IB 55 ... EF 77 )
.ver 1:0:2411:0
}
Если вы заново создали какой-нибудь из компонентов, то, конечно, номера компоновки и пересмотра будут отличаться.
Инструкция метаданных .assembly extern mscorlib указывает, что сборка Customer (Клиент) использует (и поэтому зависит от нее) стандартную сборку mccorlib. dll, которая нужна для всего управляемого кода. Когда в сборке встречается ссылка на другую сборку, вы увидите инструкцию метаданных . assembly extern. Если открыть AcmeGui в ILADASM, то в декларации вы увидите зависимости от нескольких других сборок, включая зависимости от сборок Customer (Клиент) и Hotel (Гостиница), а также зависимости от сборки System. Drawing (Система.Рисунок).
.assembly extern Customer
{
.publickeytoken = (8В ОЕ 61 2D 60 BD EO CA )
.ver 1:0:641:33530
}
.assembly extern Hotel
{
.publickeytoken = (CF OB C2 2F 8E 2C 15 22 )
.ver 1:0:641:33536
}
.assembly extern System.Drawing
{
.publickeytoken = (BO 3F 5F 7F 11 D5 OA ЗА )
.ver 1:0:2411:0
}
Сборка System. Drawing (Система.Рисунок) — общедоступная сборка, которую можно легко найти в каталоге \WINNT\Assembly с помощью Проводника Windows (Windows Explorer). Общедоступная сборка mscorlib не развернута в кэше сборки. Microsoft сделала единственное исключение для этой сборки, потому что mscorlib так близко связана с механизмом общеязыковой среды выполнения CLR (mscorwks), что она устанавливается в соответствующем каталоге установки (\WINNT\Microsoft.NET\Framework) для текущей версии .NET.
В общедоступной сборке System. Drawing (Система.Рисунок) инструкция метаданных .publickeytoken = (ВО 3F 5F 7F 11 D5 ОА ЗА) содержит лексему открытого ключа, представляющую собой самые младшие 8 байтов хэш-кода открытого ключа, который соответствует своему секретному ключу, принадлежащему автору сборки System. Drawing (Система.Рисунок). Эта лексема открытого ключа не может фактически непосредственно использоваться для того, чтобы подтвердить подлинность автора сборки System. Drawing (Система.Рисунок). Однако первоначальный открытый ключ, указанный в декларации System. Drawing (Система.Рисунок) может использоваться, чтобы математически проверить, что для того, чтобы подписать сборку System. Drawing (Система.Рисунок) в цифровой форме на самом деле использовался соответствующий секретный ключ. Поскольку Microsoft создала System.Drawing.dll, лексема открытого ключа, приведенная выше, принадлежит, несомненно, Microsoft.
Когда во время выполнения встречается статическая или динамическая ссылка на сборку, общеязыковая среда выполнения CLR осуществляет связывание со сборкой. Статическая ссылка определяется постоянным (статическим) образом в клиентской декларации сборки еще при ее компиляции. Динамическая ссылка генерируется программой во время выполнения, например, при вызове метода System.Reflection. Assembly.Load (Система.Отражение.Сборка.Загрузка). В любом случае связывание выполняется тем же самым способом для статической или динамической ссылки на сборку со строгим именем. Кроме простого имени, версия, открытый ключ (если он есть) и культура (если она есть) также отличают общедоступную сборку. Для связывания необходимо, как минимум, простое имя.
Процесс связывания с общедоступной сборкой часто называется зондированием или исследованием, которое является не чем иным, как набором эвристических правил, используемых общеязыковой средой выполнения CLR для того, чтобы определить местонахождение сборки и загрузить ее по требованию. Эти правила применяются только к ссылке на сборку со строгим именем, а ссылки на неподписанные (т.е. без цифровой подписи) частные сборки разрешаются непосредственно, т.е. без исследования или проверки версии (мы предполагаем, что действует заданная по умолчанию политика).
Однако можно использовать сборку со строгим именем, чтобы вынудить клиента связываться с определенной версией сборки. Предположим, что вы хотите допустить соответствие для нескольких обратно совместимых сборок (т.е. чтобы несколько сборок удовлетворяли условиям загрузки сборки общеязыковой среды выполнения CLR). Вы можете использовать XML-файл конфигурации, чтобы определить правила, используемые общеязыковой средой выполнения CLR для нахождения соответствующей сборки. Инструмент Администрирования .NET (.NET Admin Tool) позволяет создавать и поддерживать такие файлы с помощью графического интерфейса пользователя (GUI).
Имя файла конфигурации представляет собой имя клиентской программы, в конец которого добавлено расширение . conf ig. Файл конфигурации затем помещается в тот же самый каталог, где находится выполняемая клиентская программа.
В дополнение к файлу конфигурации приложения, существует административный файл конфигурации Machine .config. Он находится в подкаталоге Config того каталога, где установлена среда времени выполнения .NET (\WINNT\Microsoft.NET\ FrameworkA vl. О . 2914\, где номер версии отражает текущую компоновку .NET). Политика администрирования версии определяется теми же дескрипторами XML, что используются и в файле конфигурации приложения. Однако файл конфигурации администратора отменяет любые соответствующие параметры в файле конфигурации приложения.
Проект модуля слияния упаковывает многократно используемую информацию об установке; она может храниться независимо, а затем объединяется в качестве общедоступного инсталляционного пакета с другими инсталляционными пакетами. Проект модуля слияния генерирует модуль слияния— файл .msm, который может быть объединен в файлы .msi. Это позволяет совместно использовать общие сборки, связанные (входящие в состав) файлы, значения системного реестра и функциональные возможности установки для нескольких приложений.
Чтобы запустить Merge Module Project Wizard (Мастер проектов модуля слияния), выберите File => New (Файл => Создать), затем выберите Project (Проект). В диалоговом окне New Project (Новый проект) в качестве типа проекта (Project Type) выберите Setup and Deployment Projects (Установка и Развертывание проектов, Проекты Setup и Deployment). Наконец, выберите в качестве шаблона (Template) Merge Module Project " Wizard (Мастер проектов модуля слияния), укажите имя и местоположение, а затем щелкните на ОК.
Как правило, файл MSI предназначен для использования конечным пользователем с целью установить законченное решение за один простой сеанс развертывания. Файл MSM, напротив, обычно предназначается для других разработчиков, которые хотят использовать разработанные вами компоненты в их собственных проектах установки. Эти разработчики могут объединить ваш файл MSM в их собственный файл MSI для того, чтобы развернуть ваши компоненты в их средах испытания и разработки, а также для их окончательного конечного пользователя. Конечные пользователи не должны получать какие-либо файлы MSM, так как Windows Installer (Инсталлятор Windows) не может использовать их непосредственно, а сами они не являются очень дружественными по отношению к пользователю.
Чтобы добавить существующий проект модуля слияния к проекту установки, создайте или откройте проект установки и выберите Select FileOAdd Project (Выбрать файл^Добавить проект), а затем выберите Existing Project. В диалоговом окне Add Existing Project (Добавить существующий проект) найдите местоположение нужного проекта модуля слияния, выберите связанный (т.е. входящий в состав) файл . vdp проекта развертывания, а потом щелкните на Open (Открыть).
Мы только добавили проект модуля слияния к решению. Теперь мы должны добавить его к самому проекту установки. Выберите проект установки и вызовите Add: Project Output (Добавить: Вывод проекта), а затем в появившемся диалоге выберите проект модуля слияния.
Сборка может быть составлена из нескольких модулей. Модуль — динамически подключаемая библиотека (DLL) (или файл с расширением ЕХЕ), который содержит управляемый код плюс метаданные, а также, возможно (но не обязательно'), декларацию. Однако сборка должна иметь одну и только одну декларацию, так что сборка может содержать несколько модулей, но только один модуль содержит декларацию, в которой хранится информация о содержимом всех модулей в сборке Модуль с декларацией может иметь или только декларацию, или содержать также и код, и ресурсы
Основное преимущество разбиения большой сборки на модули состоит в том, что каждый модуль содержится в отдельном файле динамически подключаемой библиотеки (DLL). Благодаря этому по сети можно загружать только требуемые модули. Это позволяет оптимизировать потребление памяти и ускорить работу (выполнение программ). Даже в локальном сценарии, чтобы повысить эффективность, общеязыковая среда выполнения CLR загружает классы на локальной машине со степенью детализации модуля Другая причина создания сборок с несколькими модулями состоит в том, что каждую часть такой сборки можно написать на своем языке NET. Чтобы скомпоновать сборку, которая содержит несколько модулей, необходимо скомпоновать каждый модуль отдельно, а затем объединить их утилитой А1. ехе.
Есть два способа создания мультимодульной сборки. Один способ состоит в том, чтобы создать все модули без деклараций, а затем создать один дополнительный модуль, который содержит только декларацию для всей сборки, но фактически не содержит никакого кода. Другая методика полагается на то, что только один модуль в сборке должен содержать и код, и декларацию для всей сборки, а все другие модули содержат только код без декларации. Мы опишем первую из этих альтернатив, так как она более симметрична и ее проще визуализировать. Вторая альтернатива здесь не описана; однако реализовать ее можно подобным способом, теми же самыми инструментальными средствами.
Сначала мы создаем два модуля без декларации, используя Visual C++ с управляемыми расширениями. Затем создаем третий модуль с декларацией, содержащей информацию о двух других модулях, используя инструмент Assembly Linker (Компоновщик сборки) — А1. ехе. Вместе эти три модуля образуют сборку. На Рисунок 7.8 показана структура полученной таким образом сборки.
Следующие исходные файлы составляют первый модуль без декларации. Этот модуль содержит общедоступный класс ClassInModuleWithoutManifestl, который предоставляет для использования общедоступный метод SomeMethod. Вы можете создать его с помощью шаблона Managed C++ Class Library (Управляемый C++ на основе Библиотеки классов).
//ModuleWithoutManifestl.h
using namespace System;
// использование пространства имен Система;
namespace ModuleWithoutManifestl
// пространство имен ModuleWithoutManifestl
{
public _gc class ClassInModuleWithoutManifestl
// класс сборщика мусора
ClassInModuleWithoutManifestl
{
public:
void SomeMethod();
};
}
//ModuleWithoutManifestl.cpp
finclude "stdafx.h"
// имеет
fusing <mscorlib.dll>
using namespace System;
// использование пространства имен Система;
tinclude "ModuleWithoutManifestl.h"
namespace ModuleWithoutManifestl
// пространство имен
ModuleWithoutManifestl
{
void CiassInModuleWithoutManifestl::SomeMethod()
{
Console::WriteLine(
"CiassInModuleWithoutManifestl::SomeMethod");
}
}
Опция компилятора /CLR:noAssembly указывает, что генерируется управляемый код, но декларация для сборки не генерируется. Опция компилятора /с подавляет автоматическое связывание, так как мы выполним его отдельной командой. Опция связывания /NOASSEMBLY также запрещает генерацию информации, относящейся к декларации. Поэтому чтобы сгенерировать первый модуль (динамически подключаемую библиотеку (DLL)) без декларации, в приглашении к вводу команды используются следующие две команды:
cl /CLR:noAssembly /с ModuleWithoutManifestl.срр
link /NOASSEMBLY /DLL ModuleWithoutManifestl.obj
Если вы предпочитаете создавать модуль в Visual Studio.NET, сначала для проекта нужно изменить опции компилятора и компоновщика, чтобы не генерировать информацию, относящуюся к декларации, и не добавлять ее к модулю. Для установки опции компилятора /CLR: noAssembly в Visual Studio.NET сделайте следующее:
1. Откройте диалоговое окно Property Pages (Страницы свойств) проекта. Чтобы сделать это, щелкните правой кнопкой мыши на узле ModuleWithoutManifestl, находящемся под узлом решения в окне Class View.
2. Откройте папку Configuration Properties (Свойства конфигурации).
3. Откройте папку C/C++.
4. Щелкните на странице General (Общая), находящейся под папкой C/C++.
5. Установите MetaData Only (/CLR: noAssembly) (Только метаданные) в качестве значения свойства Compile as managed (Компилировать как управляемый).
Объектный модуль, полученный в результате компиляции с такими параметрами, теперь нужно скомпоновать в Visual Studio.NET с опцией компоновщика /NOASSEMBLY. Эта опция компоновщика требует, чтобы вы установили также опцию компоновщика /NOENTRY. Чтобы внести эти изменения в проект, выполните следующее:
6. Откройте диалоговое окно Property Pages (Страницы свойств) проекта, если только оно не открыто.
7. Откройте папку Configuration Properties (Свойства конфигурации), если она не открыта.
8. Откройте папку Linker (Компоновщик).
9. Щелкните на Advanced page (Дополнительная страница) под папкой Linker (Компоновщик).
10. Установите Yes (/NOASSEMBLY) (Да) в качестве значения свойства Turn Off Assembly Generation (Выключить генерацию сборки).
11. Оставаясь на странице Advanced page (Дополнительная страница), установите Yes (/NOENTRY) (Да) в качестве значения свойства Resource Only DLL (Динамически подключаемая библиотека (DLL) только с ресурсами).
Теперь можно создать проект обычным способом.
Второй модуль— динамически подключаемая библиотека (DLL), названная ClassInModuleWithManifest2 .dll, — также будет создан без декларации. Следующие исходные файлы, которые очень похожи на приведенные выше исходные файлы первого модуля, составляют второй модуль без декларации. Он содержит общедоступный класс ClassInModuleWithoutManifest2, который предоставляет для использования общедоступный метод Some-OtherMethod. Этот проект также может быть создан в Visual Studio.NET с помощью шаблона Managed C++ Class Library (Управляемый C++ на основе Библиотеки классов).
//ModuleWithoutManifest2.h
using namespace System;
// использование пространства имен Система;
namespace ModuleWithoutManifest2
// пространство имен
ModuleWithoutManifest2
{
public _gc class ClassInModuleWithoutManifest2
// класс сборщика мусора ClassInModuleWithoutManifest2
{
public:
void SomeOtherMethod();
};
}
//ModuleWithoutManifest2.cpp
#include "stdafx.h" // имеет #using <mscorlib.dll>
using namespace System;
// использование пространства имен Система;
#include "ModuleWithoutManifest2.h"
namespace ModuleWithoutManifest2
// пространство имен ModuleWithoutManifest2
{
void ClassInModuleWithoutManifest2::SomeOtherMethod()
{
Console::WriteLine(
"ClassInModuleWithoutManifest2::SomeOtherMethod");
}
}
Этот модуль также не будет иметь декларации, и он создается с помощью следующих команд, которые фактически совпадают с теми, что использовались для создания первого модуля:
cl /CLFUnoAssembly /с ModuleWithoutManifest2.срр
link /NOASSEMBLY /DLL ModuleWithoutManifest2.obj
И снова можно выполнить те же самые шаги, что и для предыдущего модуля, если вы хотите создать этот модуль с помощью Visual Studio.NET. Только не забудьте установить MetaData Only (/CLR: noAssembly) (Только метаданные) в качестве значения свойства Compile as managed (Компилировать как управляемый), a Yes (/NOASSEMBLY) (Да)— в качестве значения свойства Turn Off Assembly Generation (Выключить генерацию сборки) и Yes (/NOENTRY) (Да)— в качестве значения свойства Resource Only DLL (Динамически подключаемая библиотека (DLL) только с ресурсами).
Наконец, эти два модуля должны быть помещены в ту же самую сборку. Обратите внимание, что физически они не связаны вместе. Они остаются различными файлами динамически подключаемых библиотек (DLL), но помещаются в объединенную логическую сборку. Для этого используется утилита А1. exe (Assembly Linker), которая включает информацию, содержащуюся в метаданных обоих модулей, в одну декларацию, содержащуюся в третьей динамически подключаемой библиотеке (DLL) — Combined-Assembly . dll. Чтобы сделать это, скопируйте два существующих модуля в общий каталог CombinedAssernbly, и введите следующую команду:
Аl ModuleWithoutManifestl.dll, ModuleWithoutManifest2.dll /
out:CombinedAssembly.dll
Теперь мы закончили создание мультимодульной сборки. Декларация сборки содержится в CombinedAssembly.dll. Чтобы это проверить, мы должны создать испытательную клиентскую программу. Обратите внимание, что все модули должны быть доступны для клиента. Этого можно достичь с помощью локального или общедоступного развертывания. Чтобы сделать этот пример простым, мы развернем сборку локальным образом, т.е. только скопируем три модуля (три динамически подключаемые библиотеки (DLL)) в ту же самую папку, в которой находится испытательная клиентская программа. Следующий код реализует испытательную клиентскую программу. Обратите внимание, что клиент ссылается только на одну динамически подключаемую библиотеку (DLL) сборки — на CombinedAssembly. dll, так как только она содержит декларацию. Если теперь вы создадите эту клиентскую программу и скопируете все три динамически подключаемые библиотеки (DLL), то увидите, что созданная клиентская программа успешно вызывает методы, определенные в классах, которые определены в отдельных модулях (динамически подключаемых библиотеках (DLL)). Однако ссылки есть только на одну сборку — на CombinedAssembly. dll. Это доказывает, что все три модуля работают как единая логическая сборка.
//MultiModuleAssemblyTestClient.срр
#include "stdafx.h"
#using <mscorlib.dll>
#using <C:\OI\NetCpp\Chap7\CombinedAssembly\
CombinedAssembly.dll>
using namespace ModuleWithoutManifestl;
// использование пространства имен
ModuleWithoutManifestl;
using namespace ModuleWithoutManifest2;
// использование пространства имен
ModuleWithoutManifest2;
void main()
{
ClassInModuleWithoutManifestl *pl =
new ClassInModuleWithoutManifestl;
pl->SomeMethod();
ClassInModuleWithoutManifest2 *p2 =
new ClassInModuleWithoutManifest2 ;
p2->SomeOtherMethod();
}
Мы выяснили, откуда общеязыковая среда выполнения CLR знает, какие версии сборки удовлетворят условиям разрешения ссылки. Но откуда общеязыковая среда выполнения CLR узнает, где сборка постоянно хранится на диске? Если сборка с нужной версией была загружена предварительно потому, что ранее в программе уже встретилась другая ссылка на эту сборку, то эта сборка уже используется. Если сборка имеет строгое имя, то проверяется кэш сборки; если в нем находится правильная версия, то используется найденная там сборка.
В файле конфигурации есть несколько элементов, которые можно определить для того, чтобы указать общеязыковой среде выполнения CLR, где нужно попробовать найти сборку.
Если сборка еще не найдена, выполняются динамические проверки, чтобы увидеть, была ли определена в файле конфигурации кодовая страница. В разделе <dependentAssembly> можно определить элемент <codeBase> (<кодовая страница>). Этот элемент имеет два атрибута, — version (версия) и URI (Uniform Resource Identifier — универсальный идентификатор ресурса (вариант унифицированного указателя информационного ресурса (URL))), — используемых с целью проверки сборки. Для установки этих элементов в файле конфигурации может использоваться вкладка Codebases (Кодовые страницы) диалога свойств сборки Инструмента администрирования .NET (.NET Admin Tool). Примеры этого элемента:
<codeBase version="l.1.1.1" href="http://www.abc.com/
Customer.dll" />
<codeBase version="l.1.1.2" href="flie:///c:\AcmeGui\
Customer.dll" />
Чтобы использовать элемент Codebase (Кодовая страница) вне каталога приложения или подкаталогов, требуется строгое имя. Если это условие не удовлетворяется, независимо от того, действительно ли найдена требуемая сборка, процесс связывания останавливается. Если сборка не найдена, генерируется исключение.
Если элемент Codebase (Кодовая страница) не был найден в файле конфигурации, среда времени выполнения продолжает исследовать сборку Теперь весь поиск ведется относительно каталога, в котором выполняется приложение. Такой каталог называется базой (ядром, основанием или уровнем отсчета) приложения (application base)
Сначала среда времени выполнения ищет в ядре приложения. Затем она смотрит в любых подкаталогах ядра приложения, которые имеют то же самое имя, что и сборка. Если в запросе определена культура, то среда времени выполнения ищет только подкаталог сборки в подкаталоге с названием (именем) требуемой культуры.
Наконец, в разделе assemblyBinding файла конфигурации можно определить атрибут privatePath. Этот атрибут представляет собой список подкаталогов ядра приложения. Подкаталоги в этом списке отделяются точкой с запятой, именно в них и осуществляется поиск.
<probing privatePath="\bin;\assemb" />
Атрибут privatePath можно также установить с помощью Инструмента администрирования .NET (.NET Admin Tool) на вкладке свойств приложения.
Кэш сборки — средство параллельного ("бок о бок") развертывания (инсталляции) компонентов на всей машине. Термин "бок о бок" означает, что множество версий того же самого компонента могут постоянно находиться в кэше сборок рядом друг с другом. Глобальный кэш сборок содержит общедоступные сборки, которые являются глобально доступными для всех .NET-приложений на машине. Есть также кэш загружаемых сборок, доступный для приложений типа Internet Explorer, которые автоматически загружают сборки по сети. Чтобы развернуть сборку в глобальном кэше сборок, нужно создать для нее строгое имя.
С развертыванием общедоступных сборок связана еще одна проблема. Шаги, описанные выше, годились для цифрового подписания сборки во время компиляции, так как использовался атрибут, определяющий файл ключей, который содержал открытый и секретный ключи Но часто случается так, что ни один из программистов, разрабатывающих компоненты, которые будут упакованы в сборки, не знает секретного ключа всей организации. В этом случае при разработке и испытании программисты должны использовать временный секретный ключ, но когда придет время отправить сборку клиентам, назначенный представитель компании выполняет окончательное цифровое подписание, используя официальный секретный ключ организации, хранимый в строгой тайне. Это делается при помощи инструмента Sn.exe, с использованием опции -R, где "R" означает "resign" ("подписать заново"). Как правило, предварительно с помощью опции -k утилиты Sn.exe генерируется официальная криптографическая пара. Затем, используя опцию -R утилиты Sn. ехе. ставится подпись на каждой новой сборке.
sn -k TrueKeyPair.snk
sn -R SharedComponent.dll TrueKeyPair.snk
Хотя здесь две команды показаны вместе, обычно не нужно генерировать новую криптографическую пару каждый раз, когда вы заново подписываете очередную новую сборку. После второй команды на пульте появится сообщение Assembly 'SharedComponent.dll' successfully re-signed (Сборка 'SharedComponent.dll' успешно подписана заново).
Впрочем, есть и другой метод: вы можете задержать цифровое подписание. При создании сборки открытый ключ делается доступным компилятору, чтобы он мог поместить его в поле PublicKey декларации сборки. В файле резервируется место для подписи (сигнатуры), но сама подпись (сигнатура) не генерируется. Когда в конечном счете будет сгенерирована фактическая подпись (сигнатура), она помешается в файл с помощью опции -R утилиты Strong Name (sn.exe).
Чтобы указать компилятору, что вы хотите использовать отсроченное цифровое подписание, вы указываете значение true (истина) для атрибута AssemblyDelaySignAttribute в исходном тексте. Вы также должны включить открытый ключ с помощью атрибута
AssemblyKeyFileAttribute.
Предположим, что вы сгенерировали криптографическую пару открытого и секретного ключа так, как описано выше. Тогда вы с помощью опции -р утилиты Strong Name можете получить только открытый ключ, все еще сохраняя в тайне секретный ключ.
sn -p TrueKeyPair.snk PublicKey.snk
Затем вы добавляете следующие два атрибута к вашему коду:
[assembly:AssemblyDelaySignAttribute(true)];
[assembly:AssemblyKeyFileAttribute("PublicKey.snk")];
Сборка все еще не имеет правильной подписи (сигнатуры). Вы не сможете установить ее в глобальном кэше сборок или загрузить ее из каталога приложения. Вы можете отключить проверку подписи (сигнатуры) конкретной сборки, используя опцию -Vr утилиты Strong Name.
sn -Vr SharedComponent.dll
Перед поставкой сборку надо снабдить правильной сигнатурой (подписью), чтобы она могла быть развернута как общедоступная сборка. Для этого используйте опцию -R утилиты Strong Name и запишите криптографическую пару открытого и секретного ключей.
sn -R SharedComponent.dll TrueKeyPair.snk
Шаблон Setup Project (Проект установки) создает файл msi, используемый инсталлятором Windows (Windows Installer) для настольного или распределенного приложения Проект установки (Setup Project) не предназначен для развертывания приложений, основанных на технологии WWW, так как для этой цели используется специализированный проект сетевой установки (Web setup project) Проект установки (Setup Project) генерирует программу, которая устанавливает приложение на целевую машину Вы можете создать проекты установки в пределах того же самого решения, которое содержит другие развертываемые проекты В решении с несколькими уровнями вы можете создать один проект установки для каждого проекта, который должен быть развернут на конкретной целевой вычислительной машине Например, в простом решении с тремя звеньями, вероятно, будет три проекта развертывания Два простых проекта развертывания предназначены для установки клиента и сервера Третий проект развертывания будет заботиться о более сложной бизнес-логике среднего звена Кроме того, могли бы потребоваться дополнительные проекты развертывания, если бы решение было очень сложным или в стратегию развертывания были включены модули слияния
Чтобы создать проект установки, выберите FileONew (Файл<> Создать), затем выберите Project (Проект) В диалоговом окне New Project (Новый проект) в качестве типа проекта (Project Type) выберите Setup and Deployment Projects (Проекты установки и развертывания) Наконец, выберите в качестве шаблона (Template) Setup Project (Проект установки), укажите имя и местоположение, а затем щелкните на ОК Результат показан на Рисунок 7.13. — вы видите открытые Solution Explorer (Поиск решения, Проводник решения) и File System Editor (Редактор файловой системы)
Создав начальный проект установки с помощью шаблона Setup Project (Проект установки), его можно разрабатывать далее, используя File System Editor (Редактор файловой системы). Редактор файловой системы (File System Editor) позволяет перетащить и опустить или скопировать и вставить файлы, которые будут развернуты проектом развертывания установки, и управлять их адресатами на целевой машине. Сразу после запуска редактор файловой системы (File System Editor) показывает начальный список папок-получателей (результирующих папок), в которые будут развернуты компоненты, причем к этому списку вы можете добавить ваши собственные папки.
Создав файл MSI, вы можете использовать инсталлятор Windows (Windows Installer), как показано в следующей командной строке:
Msiexec /i SomeSetup.msi
Затем запускается программа Windows Installer (Инсталлятор Windows). Эта программа отображает ряд инсталляционных диалогов. После того, как инсталлятор Windows (Windows Installer) завершит развертывание, вы можете попробовать выполнить установленное приложение, чтобы проверить, что инсталляция была завершена успешно. Если выполнить ту же самую команду, Msiexec /i SomeSetup.msi, то она обнаружит, что приложение уже существует, и предложит вам восстановить инсталляцию или деинсталлировать приложение.
При связывании с общедоступно развернутыми сборками, в отсутствие любого файла конфигурации политики управления версиями, связывание просто использует информацию в декларации сборки для того, чтобы во время выполнения определить местонахождение других сборок и загрузить их по требованию. По умолчанию используются только поля главного и младшего номера версии, причем главный и младший номера должны совпадать точно, а затем берутся наиболее поздняя компоновка и пересмотр из доступных. Именно это чаще всего и требуется, так как клиентская сборка определяет зависимости от других сборок на основании главных и младших номеров версии, которые были установлены при компиляции клиентской сборки. Поскольку неизменные главные и младшие значения традиционно указывают совместимость вниз, а измененная компоновка и пересмотр указывают обратно совместимые исправления и изменения, то часто имеет смысл именно эта заданная по умолчанию политика управления версиями. Эта заданная по умолчанию политика управления версиями называется также Автоматической политикой QFE (Automatic QFE policy). При необходимости в файле конфигурации политики управления версиями это поведение может быть отменено, например для того, чтобы номера компоновки и пересмотра совпадали точно. Как это сделать, описано в следующем подразделе.
При развертывании по сети у клиента используется Internet Explorer для того, чтобы по требованию от Web-сервера .NET автоматически загрузить сборки, упакованные как ЕХЕ-файлы, динамически подключаемые библиотеки (DLL), или файлы CAB. Чтобы управлять процессом связывания, файлы HTML могут динамически развернуть сборки, а также файлы конфигурации. При развертывании по сети затребованные сборки загружаются в глобальный кэш загрузки сборок клиента.
Тэг <object> (<объект>) используется для загрузки и установки сборок по относительному или абсолютному унифицированному указателю информационного ресурса (URL). В приведенном ниже примере используется относительный унифицированный указатель информационного ресурса (URL), поэтому сборка располагается относительно того каталога Web-сервера, в котором содержится файл HTML:
<object
id="SomeComponent"
classid="./SomeDirectory/MyComponent.dll#SomeClass">
</object>
В приведенном ниже примере используется абсолютный унифицированный указатель информационного ресурса (URL), поэтому сборка располагается на указанном Web-сервере:
<object
id="SomeComponent"
classid="http://www.acme.com/MyComponent.dll#SomeClass">
</object>
По умолчанию IE создает отдельную прикладную область для каждого встреченного им Web-узла. Прикладная область— средство .NET, напоминающее масштабируемый упрощенный процесс. Прикладная область эффективно обеспечивает изоляцию ошибки без накладных расходов, неизбежных при выполнении множества настоящих процессов. Каждая из этих прикладных областей может при необходимости иметь свой собственный файл конфигурации, чтобы управлять связыванием и защитой. Кроме того, в файле конфигурации можно определить изолированную прикладную область для индивидуальных приложений на том же самом Web-сервере. Каждый файл HTML, который определяет тот же самый файл конфигурации, будет помещен в ту же самую прикладную область.
Именно благодаря операции развертывания тяжелая работа программиста становится доступной пользователю. Сборки .NET делают развертывание намного более простым и намного более надежным, чем традиционное развертывание Windows. Частное развертывание сборки столь же просто, как копирование компонента сборки в тот же самый каталог, в котором расположена клиентская программа. А общедоступное развертывание сборки регистрирует компонент с уникальным именем (известным как строгое имя) в глобальном кэше сборок, благодаря чему сборка становится доступной для общего использования.
Эта глава начинается с обсуждения понятий сборки, которая является основной единицей развертывания при частном развертывании сборки в .NET. Затем описано и общедоступное развертывание сборки. Управление версиями и цифровое подписание сборок обсуждаются в контексте общедоступного развертывания. После этого на примере программы Hotel (Гостиница) демонстрируется инсталляция и приемы развертывания сборок. Наконец, представлены развертывание Visual Studio.NET и мастера установки. По ходу обсуждения мы иллюстрируем множество полезных инструментальных средств, которые являются частью комплекса инструментальных средств разработки программ .NET Framework SDK.
Сборки положены в основу технологии компонентов .NET. Сборка — основная единица развертывания и управления разрешениями защиты, версиями, а также повторным использованием двоичного кода. Сборка содержит двоичный выполнимый код, составленный из команд управляемого промежуточного языка 1L (Intermediate Language), a также метаданных, которые полностью описывают содержимое сборки. Сборки могут также содержать данные ресурса и быть упакованы в виде динамически подключаемой библиотеки (DLL) или ЕХЕ-файлов. Сборка может быть составлена из одного или нескольких физических файлов на диске, но это — все равно одна логическая единица развертывания. Сборки, как говорят, описывают себя сами, так как они содержат в метаданных информацию о себе, и поэтому для их установки не требуется никакой внешней информации, например, в системном реестре. Это делает .NET-компоненты намного более простыми и менее подверженными ошибкам при установке и деинсталляции, чем традиционные компоненты, построенные на основе модели компонентных объектов Microsoft (СОМ), которые имели обширные требования к информации в системном реестре. Мало того, что сборки описывают себя сами, они также содержат и хэш-код, представляющий двоичное содержимое сборки. Этот хэш-код может использоваться для того, чтобы подтвердить подлинность и обнаружить тайные изменения или искажение сборок с цифровой подписью.
Прежде чем развернуть сборку в глобальном кэше сборок, ее сначала необходимо подписать в цифровой форме. Сборка с цифровой подписью имеет криптографически сгенерированную информацию, которую общеязыковая среда выполнения CLR может использовать для проверки, чтобы выполнить важные правила зависимости при размещении и загрузке сборок. Простая проверка версии гарантирует, что клиентская программа правильно использует общедоступные сборки с учетом тех их версий, которые были определены при первоначальной компиляции и тестировании клиента. Благодаря этому можно на самом деле устранить жуткую проблему "ада динамически подключаемых библиотек (DLL)", где клиенты и компоненты, построенные на основе модели компонентных объектов Microsoft (COM), могли легко "рассинхронизироваться" друг с другом, если старая версия заменялась более новой, — это нарушало работу существующих клиентов. Сборка с цифровой подписью может также проверить, что никакой неправомочный человек не изменил общедоступное содержимое сборки после того, как была поставлена цифровая подпись. Такая проверка гарантирует не только то, что вы не сможете случайно использовать неправильную версию, но также, что вы не будете злонамеренно обмануты в результате использования компонента, который мог бы причинить серьезный вред.
Хотя часто имеется взаимнооднозначное соответствие между пространством имен и сборкой, сборка может содержать множественные пространства имен, причем одно пространство имен может использоваться несколькими сборками. Точно так же часто существует взаимнооднозначное соответствие между сборкой и файлом двоичного кода (т.е. динамически подключаемой библиотекой (DLL) или исполняемым файлом). Однако одна сборка может содержать несколько файлов двоичного кода. Помните, что сборка — единица развертывания, пространства имен поддерживают иерархическую систему именования, а динамически подключаемая библиотека (DLL), или исполняемый файл— единица упаковки функциональных возможностей в пределах файловой системы. Важно также понимать разницу между сборкой, которая является единицей развертывания, и приложением, которое является единицей конфигурации.
Сборка без подписи идентифицируется просто удобочитаемым названием (именем), а также номером версии. Сборка с цифровой подписью идентифицируется также по имени ее создателя, однозначно определяемому по криптографической паре (ключей). При необходимости сборка может также включать код культуры для того, чтобы поддерживать специфические для данной культуры наборы символов и форматы строк.
Утилита lldasm.exe может использоваться для просмотра содержимого сборки, чтобы лучше понять, как работают управление версиями, цифровое подписание и развертывание. Сначала нам понадобится сборка для экспериментирования. Здесь поможет компонент SimpleComponent, созданный с помощью Visual Studio.NET. Для его создания мы в диалоге New Project (Новый проект) выбрали шаблон managed C++ Class Library (Управляемый C++ на основе Библиотеки классов). Название проекта — SimpleComponent, а в следующих исходных файлах приведена его реализация, которая только немного изменяется в зависимости от кода, сгенерированного мастером. Вспомните, что важно объявить метод AddEmUp общедоступным, чтобы он был видим для кода вне данной сборки.
//Assemblylnfо.cpp
#include "stdafx.h" // имеет #using <mscorlib.dll>
using namespace System::Reflection;
// использование пространства имен Система::Отражение;
using namespace System::Runtime::CompilerServices;
// использование пространства имен
// Система::Время выполнения::CompilerServices;
[_assembly::AssemblyTitleAttribute("")];
[_assembly: : AssemblyDescriptionAttnbute ( "" ) ] ;
[_assembly::AssemblyConfigurationAttribute("")];
[_assembly: : AssernblyCornpanyAttribute ( "") ] ;
[_assembly::AssemblyProductAttribute("")];
[_assembly::AssemblyCopyrightAttribute("")];
[_assembly::AssemblyTrademarkAttribute("") ] ;
[_assembly::AssemblyCultureAttribute("")];
[_assembly::AssemblyVersionAttribute("1.0.*")];
[_assembly::AssemblyDelaySignAttribute(false) ] ;
[_assembly::AssemblyKeуFileAttribute("")];
t_assembly::AssemblyKeyNameAttribute("")];
//SimpleComponent.cpp
#include "stdafx.h" // имеет #using <mscorlib.dll>
#include "SimpleComponent.h"
// SimpleComponent.h
using namespace System;
// использование пространства имен Система;
namespace SimpleComponent
// пространство имен SimpleComponent
{
public _gc class SomeClass
// класс сборщика мусора SomeClass
{
public: // должен быть общедоступен, чтобы сборка могла
// предоставлять его для использования
int AddEmUp(int i, int j)
{
return i+j;
}
};
}
Как только вы создали сборку компонентов .NET, содержащую вышеупомянутый код и скомпоновали ее, вы можете с помощью Ildasm.exe просмотреть ее содержимое, включая декларацию. Декларация содержит следующую информацию о сборке:
сборка идентифицируется по имени, версии, культуре, и, при необходимости, по цифровой сигнатуре (подписи); список файлов, которые составляют содержимое сборки; список других сборок, от которых зависит данная сборка; список разрешений, требуемых сборкой для ее выполнения.Все это можно увидеть с помощью утилиты Ildasm Чтобы ее запустить, выполните следующую команду Результат выполнения показан на Рисунок 7.1.
Ildasm SimpleComponent.dll
Чтобы увидеть декларацию сборки SimpleComponent, выполните двойной щелчок на узле MANIFEST (МАНИФЕСТ, ДЕКЛАРАЦИЯ), который виден на Рисунок 7.1. Информация из манифеста показана на Рисунок 7.2.
Декларация содержит информацию о зависимостях и содержимом сборки Видно, что декларация сборки SimpleComponent содержит, среди других, следующую внешнюю зависимость
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 EO 89 )
.hash = (09 BB ВС 09 EF 6D 9B F4 F2 CC IB 55 76 A7 02 91
22 88 EF 77 )
.ver 1:0:2411:0
}
Инструкция метаданных .assembly extern mscorlib указывает, что сборка SimpleComponent использует (и поэтому зависит от нее) стандартную сборку mccorlib.dll, необходимую для всего управляемого кода Сборка mscorlib — общедоступная сборка, которую можно найти в каталоге \WINNT\Assembly с помощью Проводника Windows (Windows Explorer) Эта зависимость появляется в метаданных SimpleComponent.dll благодаря оператору fusing <mscorlib.dll> в первоначальном исходном тексте (Stdafxh) Если бы другой оператор #using добавлялся для другой сборки, например fusing <System WinForms.dll>, то декларация содержала бы также соответствующую инструкцию зависимости .assembly extern System. WinForms
Инструкция метаданных .publickeytoken = (В7 7А 5С 56 19 34 ЕО 89 ) указывает общедоступную лексему (маркер) открытого ключа, являющуюся хэш-кодом открытого ключа, который ставится в соответствие своему секретному ключу, принадлежащему автору сборки mscorlib Эта лексема открытого ключа на самом деле не может использоваться непосредственно, чтобы подтвердить подлинность автора mscorlib Однако первоначальный открытый ключ, указанный в декларации mscorlib может использоваться для того, чтобы математически проверить, что секретный ключ на самом деле совпадает с тем, который действительно применялся при цифровом подписании сборки mscorlib Поскольку mscorlib.dll создала Microsoft, лексема открытого ключа, приведенная выше, принадлежит Microsoft Конечно, соответствующий секретный ключ — тщательно охраняемая корпоративная тайна, и, как полагает большинство экспертов в области защиты, такой секретный ключ практически очень трудно определить по открытому ключу Однако нет никакой гарантии, что некий математический гений не найдет когда-нибудь хитроумный способ делать это проще.
Как мы вскоре увидим, инструкция . publickeytoken присутствует в декларации клиентской сборки только в случае, когда сборка, на которую есть ссылка, имеет цифровую подпись (На самом деле все сборки, предназначенные для общедоступного развертывания, должны иметь цифровую подпись) Microsoft подписала в цифровой форме стандартные сборки NET, такие KaKmscorlib.dll и System.WinForms.dll принадлежащими ей секретными ключами Именно поэтому лексема открытого ключа для многих общедоступных сборок, содержащихся в каталоге \WlNNT\Assembly, имеет то же самое повторяющееся значение Создаваемые другими производителями сборки с цифровой подписью подписаны их собственными, отличными от приведенного выше, секретными ключами, и они будут иметь отличную от приведенной выше лексему открытого ключа в декларациях их клиентской сборки Позже вы научитесь создавать ваши собственные криптографические пары секретного и открытого ключа и сможете подписывать собственные сборки цифровой подписью для их развертывания через глобальный кэш сборок.
Декларация .publickeytoken
Чтобы сэкономить память, декларация .publickeytoken содержит только самые младшие 8 байтов кэш-кода открытого ключа производителя (он состоит из 128 байтов), вычисленного с помощью алгоритма SHA1. Однако, несмотря на это, она все же может использоваться для довольно надежной проверки А вот декларация .publickey содержит полный открытый ключ. Конечно, она занимает больше места, но именно поэтому злодеям труднее найти секретный ключ, который соответствует полному открытому ключу.
Важно обратить внимание, что, хотя цифровой ключ уникален, он сам по себе не может идентифицировать фактического автора конкретного модуля Однако разработчик сборки может использовать утилиту signcode, чтобы добавить цифровое свидетельство, которое может идентифицировать издателя сборки А если зарегистрировать цифровое свидетельство у Certificate Authority (Полномочного свидетеля), например у VenSign, то пользователи смогут установить надежность источника.
Инструкция метаданных .hash = (09 ВВ ВС 09 ... 77 ) обеспечивает фиксированный размер представления хэш-кода двоичного содержимого mscor lib. dll Если бы содержимое изменилось, в результате изменился бы и этот хэш-код Несмотря на то, что хэш-код имеет компактное представление, он с высокой вероятностью характеризует сборку Поэтому вычисленный на основе первоначальных данных, он может использоваться для многих целей, включая обнаружение ошибок и проверку Хэш-код для сборки mscorlib, показанной выше, с высокой вероятностью характеризует двоичные данные сборки mscorlib Это означает, что, если бы содержимое mscor I ib. dll было изменено случайно, преднамеренно, или даже злонамеренно, то, с астрономически высокой вероятностью, новый хэш-код не совпадал бы со старым, и изменение было бы обнаружено по хэш-коду Как описано далее в разделе по цифровому подписанию сборок, секретный ключ используется для того, чтобы гарантировать, что только уполномоченный человек может зашифровать хэш-код, и это используется для подтверждения (проверки) подлинности всей сборки
Инструкция метаданных .ver 1:0:2411:0 указывает версию сборки mscorlib Формат спецификации этой версии— Major .Minor :Build:Revision (Главный Младший Компоновка Пересмотр) Через какое-то время, когда будут выпущены новые версии этой сборки, существующие клиенты, которые были скомпонованы так, чтобы использовать данную версию, продолжат использовать именно данную версию, по крайней мере те версии, у которых совпадают значения главного и младшего номера версий Более новые клиентские программы, конечно, смогут обратиться к более новым версиям этой сборки, поскольку именно для них станут доступными новые версии Старые и новые версии могут быть развернуты буквально рядом посредством кэша глобальных сборок, и быть одновременно доступны старым и новым клиентским программам Обратите внимание, что версия 1:0:2411:0, появляющаяся в клиентской декларации, принадлежит текущей версии сборки mscorlib и не связана с атрибутом версии 1.0.*, указанным в исходном тексте SimpleComponent Вскоре мы более подробно рассмотрим четыре поля, которые составляют номер версии, а также управление версиями сборки
До сих пор мы сосредотачивались на зависимостях, которые определены в декларации сборки SimpleComponent Теперь давайте подробнее рассмотрим информацию в декларации, описывающую компонент SimpleComponent, содержащийся в сборке. Обратите внимание, что эта сборка не имеет цифровой подписи, и поэтому не содержит информации о ее создателе (т е , открытый ключ она в себе не содержит)
.assembly SimpleComponent
{
.hash algorithm 0x00008004
.ver 1:0:584:39032
}
Директива .assembly
Директива . assembly объявляет декларацию и определяет, какой сборке принадлежит текущий модуль. В данном примере директива . assembly определяет SimpleComponent (в качестве имени сборки. Именно это имя (вместе с номером версии и, возможно, открытым ключом), а не имя динамически подключаемой библиотеки (DLL) или (.исполняемого файла, используется во время выполнения для определения принадлежности сборки. Обратите также внимание, что, если сборка подписана, то в |директиве .assembly будет определен параметр .publickey. Директива .assembly декже указывает, добавлялись ли какие-либо пользовательские атрибуты к метаданным. Инструкция метаданных .assembly SimpleComponent указывает, что имя ысборки — SimpleComponent. Имейте в виду, что это — не имя класса компонента в рЬборке, а само имя сборки.
Алгоритмы хэширования
Алгоритм хэширования— математическая функция, которая берет первоначальные Ввходные данные произвольной длины и генерирует хэш-код, также известный как щрофиль сообщения, который представляет собой двоичный результат установленной |длины Эффективная хэш-функция — односторонняя (однонаправленная) функция, |При использовании которой коллизии возникают очень редко, а ее результат имеет рЬтносительно маленькую установленную длину. Идеальная хэш-функция также легко вычислима. Односторонняя функция— функция, не имеющая такой обратной, с помощью которой можно было бы фактически быстро вычислить первоначальные. Данные по значению хэш-кода. Фраза "коллизии возникают очень редко" означает, что вероятность того, что по двум первоначально различным входным данным будет сгенерирован тот же самый хэш-код, является очень маленькой, и мала вероятность вычисления двух отличающихся входных данных, которые приводят к тому же самому рначению хэш-кода Известные алгоритмы хэширования MD5 и SHA1, как полагают, являются превосходным выбором для использования в цифровом подписании, и оба они поддерживаются в .NET.
Инструкция .ver 1:0:584:39032 указывает окончательную версию сборки SimpleComponent, которая определена частично атрибутом AssemblyVersionAttribute в исходном тексте компонента. Управление версиями более подробно описано в следующем подразделе.
Управление версиями сборки
Как мы только что видели, декларация сборки содержит версию сборки, а также версии каждой из сборок, от которых она зависит. Детальный набор правил, используемых общеязыковой средой выполнения CLR для того, чтобы определить зависимости версии, называют политикой управления версиями. Заданная по умолчанию политика управления версиями определена зависимостями, указанными в декларациях сборки, но при необходимости ее можно изменить в файле конфигурации приложения или в общесистемном файле конфигурации. Автоматическая проверка версии выполняется общеязыковой средой выполнения CLR только на сборках со строгими именами (то есть, на сборках с цифровой подписью). Однако, каждой сборке, независимо от того, как она развернута, должен быть назначен номер версии. Номер версии сборки состоит из следующих четырех полей.
Главная версия (Major version): Главные несовместимые изменения. Младшая версия (Minor version): Менее значительные несовместимые изменения. Номер компоновки (Build number): Обратно совместимые изменения, сделанные в ходе разработки. Пересмотр (Revision): Обратно совместимые изменения, сделанные в ходе текущего быстрого исправления (Quick Fix Engineering, QFE).Вышеупомянутые соглашения относительно назначения каждого поля номера версии не предписаны общеязыковой средой выполнения CLR. Именно программист устанавливает эти или любые другие соглашения при проверке совместимости сборки и определении политики управления версиями в файле конфигурации, который мы обсудим позже в этой главе.
Традиционно изменение значения главного или младшего номера указывает явную несовместимость с предыдущей версией. Это используется при существенных изменениях в новом выпуске сборки, и существующие клиенты не могут использовать новую версию. Изменения номера компоновки подразумевают совместимость вниз, и этот номер обычно изменяется каждый раз при очередной компоновке сборки в ходе разработки. Совместимость вниз между номерами компоновки является намеренной; однако, этого, очевидно, не может гарантировать общеязыковая среда выполнения CLR, и потому данное свойство должно быть проверено. Изменение номера пересмотра относится к изменениям, сделанным в ходе текущего быстрого исправления (Quick Fix Engineering, QFE). Это поле обычно используется для срочного исправления, которое общеязыковой средой выполнения CLR считается обратно совместимым, если в файле конфигурации не указано иное назначение этого поля. И снова, общеязыковая среда выполнения CLR не может гарантировать, что изменение, сделанное в ходе текущего быстрого исправления (Quick Fix Engineering, QFE), на 100 процентов обратно совместимо, но совместимость вниз желательна и должна быть тщательно проверена.
Информация, относящаяся к версии, может быть определена в исходном тексте, в атрибуте _assembly::AssemblyVersionAttribute. Класс AssemblyVersionAttribute определен в пространстве имен System: :Runtime: :CompilerServices (Система:: Время выполнения::СотрПег5ешсе5). Если этот атрибут не используется, в декларации сборки по умолчанию задается номер версии 0.0.0.0, который, вообще говоря, является признаком небрежности. В проекте, созданном Мастером проектов на управляемом C++ на основе Библиотеки классов (managed C++ Class Library project wizard), исходный файл Assemblylnfo.cpp автоматически генерируется с версией 1.0.*, т.е. главная версия равна 1, младшая версия — 0, причем значения пересмотра и компоновки генерируются автоматически. Если изменить AssemblyVersionAttribute на, например, "1.1.0.0", как показано ниже, то номер версии, отображенный в декларации, изменится, и будет равен 1:1: 0:0.
//Assemblylnfо.срр
#using <mscorlib.dll>
[_assembly::AssemblyVersionAttribute("1.1.0.0")];
Чтобы не указывать значений пересмотра и компоновки, можно использовать символ звездочка (*). Когда вы вообще определяете какой-либо номер версии, вы должны, как минимум, определить главный номер. Если вы определяете только главный номер, остающиеся значения будут по умолчанию иметь значение нуль. Если вы определяете также младшее значение, то можете опустить оставшиеся поля, которые по умолчанию будут обнулены, или можете указать звездочку, тогда значения будут сгенерированы автоматически. Звездочка в данном случае означает, что значение компоновки будет равняться количеству дней, прошедших с 1 января 2000 года, а значение пересмотра будет установлено равным количеству секунд, прошедших с полуночи, деленному на 2. Если вы определяете значения главного и младшего номеров, а также номера компоновки, причем указываете звездочку для значения пересмотра, то только номер пересмотра будет равен количеству секунд, прошедшему с полуночи, деленному на 2. Когда все четыре поля указаны явно, все четыре значения будут отражены в декларации. Следующие примеры показывают правильные (допустимые) спецификации версии.
Определено в исходном тексте | Записано в декларации |
Ни одно поле | 0:0:0:0 |
1 | 1:0:0:0 |
1.1 | 1:1:0:0 |
1.1. * | 1:1:464:27461 |
1.1.43 | 1:1:43:0 |
1.1.43.* | 1:1:43:29832 |
1.1.43.52 | 1:1:43:52 |
Если указать звездочку, то версия автоматически будет изменяться каждый раз при компоновке компонента; однако каждая новая версия считается обратно совместимой, так как главные и младшие номера не изменяются автоматически. Чтобы определить новую обратно несовместимую версию, вы должны явно изменить главный и/или младший номер версии.
Гарантируется, что строгое имя будет глобально уникальным для любой версии любой сборки. Строгие имена генерируются тогда, когда сборка получает цифровую подпись. Это гарантирует, что строгое имя не только уникально, но и может быть сгенерировано только индивидуумом, который имеет секретный ключ.
Строгое имя состоит из простого текстового имени, открытого ключа и хэш-кода, зашифрованного соответствующим секретным ключом. Хэш-код также называется профилем сообщения, а зашифрованный хэш-код— цифровой подписью, электронной подписью и цифровой сигнатурой. Хэш-код фактически эффективно идентифицирует двоичное содержимое сборки, а цифровая сигнатура (подпись) фактически эффективно идентифицирует автора сборки. Все сборки, имеющие одно и то же строгое имя, считаются идентичными (при определении идентичности во внимание принимаются также номера версии). Сборки, строгие имена которых отличаются друг от друга, считаются различными. Полагают, что строгое имя является криптографически стойким, поскольку в противоположность простому текстовому имени, оно однозначно определит сборку на основании ее содержимого и секретного ключа ее автора. Строгое имя имеет следующие полезные свойства:
Как мы видели ранее, чтобы создать новую версию сборки, нужно просто изменить номер версии, который состоит из следующих четырех полей: главный (major) номер, младший (minor) номер, номер компоновки (build) и номер пересмотра (revision). He забывайте, что поля главного и младшего номеров указывают изменения версии, которые не являются обратно совместимыми. Что случается, когда вы устанавливаете две несовместимые версии одной и той же сборки? Чтобы увидеть полученный эффект, попробуйте изменить главный или младший номер версии сборки SharedComponent, которую в предыдущем подразделе мы развернули в глобальном кэше сборок. Ранее была развернута версия 1.0.584.42238 Поэтому, если вы создадите новую версию, у которой младший номер заменен (т. е. версию 1:1 *.*), и развернете ее в глобальном кэше сборок, то с помощью Проводника Windows (Windows Explorer) вы увидите, что доступны обе версии — с различными номерами версий, естественно (Рисунок 7.6).
Сборки могут быть развернуты как обычные автономные файлы, содержащие двоичный код (т.е. динамически подключаемые библиотеки (DLL) или исполняемые файлы), либо при этом могут использоваться файлы CAB, MSI, или MSM. Файл CAB — файл с расширением . cab для имени файла. Он используется, чтобы сжать и объединить другие файлы в один удобный управляемый файл. Хотя файлы CAB могут использоваться для общих целей, они традиционно используются для инсталляции с компакт-диска и загрузки из Web. Файлы MSI и MSM — файлы инсталлятора Microsoft Windows Installer; имена таких файлов имеют расширения .msi и .msm. Файлы MSI (и, косвенно, файлы MSM) используются программой инсталляции в Windows— Msiexec.exe — для того, чтобы развернуть автономные приложения и компоненты многократного использования.
Файлы MSI — пакеты инсталляции программы Microsoft Windows Installer; имена таких файлов имеют расширение .msi. Файлы MSM — модули слияния; имена таких файлов имеют расширение . msm. Инсталлятор Windows (Windows Installer) поддерживает инсталляцию программ, исправление, обновление и удаление. Пакеты инсталлятора Windows (Windows Installer) — независимые (отдельные) файлы базы данных, содержащие информацию об инсталляции, используемую службой инсталлятора Windows (Windows Installer). Несколько упрощая реальное положение дел, можно сказать, что файл MSM имеет внутреннюю структуру, которая подобна структуре файла MSI. К сожалению, инсталлятор Windows (Windows Installer) не может использовать файл MSM непосредственно, так как ему нужны некоторые важные таблицы базы данных. Поэтому для использования в фактическом инсталляционном сеансе файл MSM должен быть слит (объединен с другими файлами) в файл MSI Однако файлы MSM полезны для того, чтобы выделить общедоступную инсталляционную информацию в независимый пакет, который может быть затем слит (объединен с другими) во многие другие пакеты MSI
Инсталляцию может выполнять инсталлятор Windows (Windows Installer) или Internet Explorer Кроме того, чтобы выполнить инсталляцию, можно просто вручную скопировать сборки и входящие в их состав файлы Чтобы помочь программисту разработать установку и решения развертывания, в Visual Studio NET предусмотрено несколько шаблонов и мастеров, генерирующих проекты установки стартера Эти инструментальные средства доступны в диалоговом окне New Project (Новый проект) под узлом Setup and Deployment Projects (Установка и Развертывание проектов), Рисунок 7.12 Как видно из рисунка, для того, чтобы генерировать установку стартера и проекты развертывания, предусмотрены следующие шаблоны