Создание Windows-приложений на основе Visual C#

Использование библиотек кода в windows-формах

Показывать лекцию целиком
Для работы с данной лекцией используйте примеры.

Взаимодействие управляемого и неуправляемого кода

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

Код, выполняющийся под управлением среды выполнения (в случае платформы .NET — среды Common Language Runtime), называется управляемым. Напротив, код, запускаемый не под управлением среды, называется неуправляемым. Примером неуправляемого кода могут служить COM-компоненты, Microsoft ActiveX интерфейсы и функции API Win32.

Microsoft .NET Framework позволяет взаимодействовать с COM-компонентами, COM+-службами, внешними типами библиотек и разными службами операционной системы. Перехват исключений различается в управляемом и неуправляемом коде. Платформа .NET Framework предлагает две службы взаимодействия управляемого кода с неуправляемым — Platform Invoke и COM interoperability, которые используют реальные возможности проекта.

Службы Platform Invoke

Службы Platform Invoke позволяют управляемому коду запускать функции неуправляемого кода, которые находятся в файлах библиотек динамической компоновки (DLL). Эти службы предоставляют механизмы обнаружения и запуска неуправляемых функций и преобразование типов данных входящих и исходящих аргументов функции. Когда управляемый код запускает функцию неуправляемого кода, локализованную в DLL-файле, службы Platform Invoke находят этот DLL файл, загружают его в оперативную память и находят адрес функции в памяти. После этого службы передают входящие аргументы функции в стек, преобразовывают данные, которые необходимо перевести, эмулируют сборку мусора и передают управление по адресу неуправляемой функции в памяти.

Службы COM Interoperability

Объектная модель COM (Common Object Model) компонентов имеет некоторые существенные отличия от объектной модели компонентов .NET Framework.

  • Управление жизненным циклом объектов. Клиенты СОМ-объектов сами управляют этим циклом, среда CLR платформы .NET берет эту задачу на себя.
  • Обращение к службам и объектам. Клиенты COM-объектов узнают о функциональности службы путем опроса ее интерфейса, клиенты .NET-объектов могут получить описание функциональности, используя пространство имен Reflection.
  • Среда CLR перемещает объекты в памяти для улучшения производительности, обновляя ссылки этих объектов. Клиенты COM имеют постоянную ссылку на ячейки памяти, не изменяющуюся в течение всей жизни объекта.
  • Для преодоления этих различий CLR предлагает классы-оболочки, которые позволяют управляемым и неуправляемым клиентам думать, что они работают с объектами той же среды, что и они сами. Когда управляемый клиент вызывает неуправляемый метод, CLR создает временную оболочку RCW (Runtime Callable Wrapper). RCW абстрагирует различия между управляемым и неуправляемым кодом. CLR также создает оболочку CCW (COM Callable Wrapper) для обращения процесса, которая позволяет COM-объектам запускать методы .NET-объектов.

    Обзор механизма преобразования

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

  • System.Byte
  • System.SByte
  • System.Int16
  • System.UInt16
  • System.Int32
  • System.UInt32
  • System.Int64
  • System.IntPtr
  • System.UintPtr
  • Но есть и такие типы данных, которые имеют неоднозначное значение в управляемом и неуправляемом языках программирования. Эти типы нуждаются в конвертировании при переходе из управляемого в неуправляемый код и наоборот. Например, управляемая строка (string) имеет несколько значений в неуправляемом коде. В таблице 5.1 представлен список типов данных, которые нуждаются в конвертировании при переходе из одной среды в другую.

    Конвертирование типов данных
    Тип данныхТип при конвертировании
    System.Array C-style или SAFEARRAY
    System.Boolean 1-, 2- или 4-битовое значение со значением true как 1 или –1
    System.Char Символ кодировки ANSI или Unicode
    System.Class Интерфейс class
    System.Object Различные интерфейсы
    System.Mdarray C-style или SAFEARRAY
    System.String Строка с завершающим нулем (null-terminated string) или BSTR
    System.Valuetype Структура
    System.Szarray C-style или SAFEARRAY

    В большинстве случаев стандартные RCW и CCW, сгенерированные средой выполнения, предоставляют соответствующее преобразование типов между компонентами COM и .NET Framework. Используя атрибуты, вы можете настроить перевод из одного типа в другой.

    Работа службы Platform Invoke. Запуск функций WinAPI

    Наиболее часто служба Platform Invoke применяется для запуска функций WinAPI, находящихся в файлах dll. Когда служба вызывает функцию, совершаются следующие операции:

  • обнаружение необходимой библиотеки;
  • загрузка найденной библиотеки в оперативную память;
  • обнаружение адреса библиотеки в памяти и передача аргументов функции, с их преобразованием при необходимости;
  • после этого Platform Invoke передает управление неуправляемой функции и ждет завершения.
  • Для подключения функции необходимо представить адрес библиотеки и данные о функции: название, входящие и исходящие аргументы.

    Windows API — это набор функций, входящий в состав семейства операционной системы Microsoft Windows. Преимуществом использования этих функций является то, что они уже полностью готовы и не приходится тратить время на реализацию подобной функциональности. Однако с ними тяжело работать — в частности, довольно сложно отлавливать исключения, возникающие в работе приложения.

    Первым шагом в запуске неуправляемой функции является объявление функции. Функция должна быть статической (static) и внешней (extern). Далее следует импорт библиотеки, содержащей эту функцию. Импортировать библиотеку нужно, используя атрибут DllImport, который находится в пространстве имен System.Runtime.InteropServices. Атрибут DllImport нуждается в названии библиотеки и может принимать один из параметров, указанных в таблице 5.2:

    Некоторые параметры атрибута DllImport
    АргументОписание
    EntryPoint Указывает название функции. Если название метода совпадает с названием неуправляемой функции, то этот аргумент ставить необязательно
    CharSet Указывает кодировку строковых значений. Значением по умолчанию является ANSI
    ExactSpelling Предотвращает изменение точки входа (entry point) при изменении кодировки. Если значение аргумента CharSet установлено в Auto, то значение по умолчанию — true, в противном случае — false
    CallingConvention Указывает тип конвертирования аргументов, передаваемых в функцию. Значением по умолчанию является WinAPI
    PreserveSig Указывает, что сигнатура управляемого метода не должна трансформироваться в сигнатуру неуправляемого метода, который возвращает значение типа HRESULT и может иметь дополнительные аргументы в качестве возвращаемых значений Значением по умолчанию является true (сигнатура не должна трансформироваться).
    SetLastError Указывает возможность использования метода GetLastError для обработки ошибок при выполнении неуправляемого метода. Значением по умолчанию является false

    Аргументы, которые принадлежат к простым типам данных, такие как byte или int, Platform Invoke автоматически преобразовывает в соответствующие типы в неуправляемой платформе. В таблице 5.3 приводится соответствие типов данных в функциях WinAPI и библиотеки .NET Framework.

    Тип данных в неуправляемом кодеТип данных в .NET Framework (C#)
    HANDLE Int
    BYTE Byte
    SHORT Short
    WORD Ushort
    INT Int
    UINT Uint
    LONG Int
    ULONG Uint
    BOOLEAN Int
    CHAR Char
    LPSTR (и большинство строковых типов данных) String для входящих аргументов, StringBuilder для двусторонних аргументов
    FLOAT Float
    DOUBLE double

    При указании библиотеки не нужно указывать путь до нее. CLR ищет библиотеку в каталоге приложения, затем в подкаталогах, в папке Windows и папке Window\System32. При указании адреса библиотеки могут возникнуть исключения — CLR проверяет только его. Например, если был указан адрес "C:\Windos\system32\someDll.dl", а у пользователя операционная система располагается на диске D — приложение работать не будет. При использовании функций WinAPI сами файлы библиотек не нужно включать в проект.

    Получение имени пользователя. Библиотека secur32.dll

    Итак, приступим к практике. Создайте новое Windows-приложение и назовите его GetUserName. Перетаскиваем на форму из окна ToolBox кнопку и надпись. Устанавливаем следующие свойства формы и элементов управления:

    Form1, форма, свойствоЗначение
    Size 560; 96
    Text Получение имени пользователя
    label1, свойствоЗначение
    Name lblGetUserName
    Location 168; 20
    Size 368; 23
    Text
    Button1, свойствоЗначение
    Name btnGetUserName
    Location 16; 20
    Text Получить!

    В окне Solution Explorer щелкаем правой кнопкой на названии проекта и выбираем Add\Add New Item…. В появившемся окне шаблонов выбираем класс и называем его WinAPIClass.cs. Переходим в его код и добавляем пространство имен для работы с библиотеками dll и объектом StringBuilder:

    using System;
    using System.Runtime.InteropServices;
    using System.Text;

    Обращаемся к библиотеке secur32.dll и создаем метод GetUserNameEx (привожу пространство имен класса полностью 1Конструктор класса был удален ):

    namespace GetUserName
    {
    		public class WinAPIClass
    	{
    		#region Получение имени текущего пользователя
    		/// <summary>
    		/// Получение имени текущего пользователя.
    		/// </summary>
    		/// <param name="nameFormat">Формат имени из перечисления NameFormat.</param>
    		/// <param name="userName">Выходной параметр — имя.пользователя</param>
    		/// <param name="userNameSize">Количество символов в имени.</param>
    		/// <returns></returns>
    		//Подключение библиотеки secur32.dll
    		[DllImport("secur32.dll", CharSet=CharSet.Auto)]
    		public static extern int GetUserNameEx (int nameFormat, StringBuilder userName, ref uint userNameSize);
    		
    		/// <summary>
    		/// Формат имени.
    		/// </summary>
    		public enum NameFormat:int
    		{
    			NameUnknown = 0,
    			NameFullyQualifiedDN = 1,
    			NameSamCompatible = 2,
    			NameDisplay = 3,
    			NameUniqueId = 6,
    			NameCanonical = 7,
    			NameUserPrincipal = 8,
    			NameCanonicalEx = 9,
    			NameServicePrincipal = 10,
    			NameDnsDomain = 12
    		};
    		#endregion
    	
    	}
    }

    Все! Класс, использующий функцию WinAPI, готов. В обработчике кнопки btnGetUserName обращаемся к созданному классу:

    private void btnGetUserName_Click(object sender, System.EventArgs e)
    	{
    	StringBuilder builder = new StringBuilder(100);
    	uint size = 100;
    WinAPIClass.GetUserNameEx((int)WinAPIClass.NameFormat.NameSamCompatible, builder, ref size);
    	lblGetUserName.Text = String.Format("Имя пользователя: {0}", builder.ToString());
    	}

    Обратите внимание на то, что в качестве значения перечисления идет тип данных int, объект StringBuilder передается в качестве строки и к последнему параметру добавляется ключевое слово ref, т.к. параметр передается по ссылке. Результатом запуска приложения будет определение имени текущего пользователя (рис. 5.1).

    (рис 5.1) Результат запуска приложения GetUserName

    Класс WinAPIClass, в котором происходит обращение к библиотеке secur32.dll, мог быть написан на другом, C-подобном языке, в оригинале он был реализован именно на C. Интересно сравнить типы данных двух листингов объявления методов:

  • Метод GetUserNameEx на языке С
    BOOLEAN GetUserNameEx(
    EXTENDED_NAME_FORMAT 
    // формат имени
    NameFormat,  
    // буфер для названия
    LPTSTR lpNameBuffer, 
    // размер буфера для названия
    PULONG nSize  
     );
  • Метод GetUserNameEx на языке С#
    [DllImport("secur32.dll", CharSet=CharSet.Auto)]
    public static extern int GetUserNameEx 
    (int nameFormat, StringBuilder userName, 
    ref uint userNameSize);
  • Выбор типа данных может повлиять на выполнение преобразования входящих и исходящих параметров. Например, в библиотеках WinAPI тип данных long — 32-битный, в то время как в C# тип данных long — 64-битный.

    На диске, прилагаемом к книге, вы найдете приложение GetUserName (Code\Glava5\GetUserName).

    Диалоговые окна. Библиотека user32.dll

    Всякий раз, когда мы начинаем изучать новый язык программирования, в число первых примеров входит вывод текстовой строки на экран. Операционная система Windows содержит определенное число типов диалоговых окон, с помощью которых пользователь получает уведомления о различных событиях. Создайте новое Windows-приложение и назовите его MessageBox. Располагаем на форме четыре кнопки и устанавливаем следующие свойства формы и кнопок:

    Form1, форма, свойствоЗначение
    Size 232; 144
    Text Message Box
    Button1, свойствоЗначение
    Name btnABORTRETRYIGNORE
    Location 8; 8
    Size 200; 23
    Text Прервать Повторить Пропустить
    Button2, свойствоЗначение
    Name btnYESNOCANCEL
    Location 8; 32
    Size 200; 23
    Text Да Нет Отмена
    Button3, свойствоЗначение
    Name btnOK
    Location 8; 56
    Size 200; 23
    Text OK
    Button4, свойствоЗначение
    Name btnYESNO
    Location 8; 80
    Size 200; 23
    Text Да Нет

    Снова добавляем к проекту класс и называем его WinAPIClass.cs.

    Листинг этого класса:

    using System;
    using System.Runtime.InteropServices;
    namespace MessageBox
    {
    	
    	public class WinAPIClass
    	{
    		#region Вызов диалогового окна MessageBox
    		/// <summary>
    		/// Вызов диалогового окна MessageBox.
    		/// </summary>
    		/// <param name="handle">Родительская форма окна</param>
    		/// <param name="text">Текст окна.</param>
    		/// <param name="title">Заголовок окна.</param>
    		/// <param name="type">Тип окна.</param>		
    		/// <returns></returns>
    		[DllImport("user32.dll", CharSet=CharSet.Auto)]
    		public static extern int MessageBox(IntPtr handle, string text, string title, uint type);
    		/// <summary>
    		/// Тип диалогового окна.
    		/// </summary>
    		public enum MessageBoxType:int
    		{
    			/// <summary>
    			/// Три кнопки — Abort, Retry, Ignore
    			/// </summary>
    			MB_ABORTRETRYIGNORE = 2,
    			/// <summary>
    			/// Три кнопки — Cancel, Try, Continue
    			/// </summary>
    			MB_CANCELTRYCONTINUE = 6,			
    			/// <summary>
    			/// Одна кнопка — Ok.
    			/// </summary>
    			MB_OK = 0,
    			/// <summary>
    			/// Две кнопки — Ok, Cancel.
    			/// </summary>
    			MB_OKCANCEL = 1,
    			/// <summary>
    			/// Две кнопки — Retry, Cancel
    			/// </summary>
    			MB_RETRYCANCEL = 5,
    			/// <summary>
    			/// Две кнопки — Yes, No
    			/// </summary>
    			MB_YESNO = 4,
    			/// <summary>
    			///  Три кнопки — Yes, No, Cancel
    			/// </summary>
    			MB_YESNOCANCEL = 3,
    			/// <summary>
    			/// Иконка-восклицание
    			/// </summary>
    			MB_ICONEXCLAMATION = 0x30,
    			/// <summary>
    			/// Иконка-предупреждение
    			/// </summary>
    			MB_ICONWARNING = 0x30,
    			/// <summary>
    			/// Иконка-информация
    			/// </summary>
    			MB_ICONINFORMATION = 0x40,
    			/// <summary>
    			/// Иконка-вопрос
    			/// </summary>
    			MB_ICONQUESTION = 0x20,
    			/// <summary>
    			/// Иконка-стоп
    			/// </summary>
    			MB_ICONSTOP = 0x10,
    			/// <summary>
    			/// Иконка-ошибка
    			/// </summary>
    			MB_ICONERROR = 0x10,
    					
    		}
    		/// <summary>
    		/// Тип возвращаемого значения.
    		/// </summary>
    		public enum MessageBoxReturnType:int
    		{
    			IDABORT = 3,
    			IDCANCEL = 2,
    			IDCONTINUE = 11,
    			IDIGNORE = 5,
    			IDNO = 7,
    			IDOK = 1,
    			IDRETRY = 4,
    			IDTRYAGAIN = 10,
    			IDYES = 6
    		}
    		#endregion
    	}
    }

    В перечислении MessageBoxType приводятся различные типы стандартных окон и иконок. В обработчиках кнопок выбираем несколько вариантов окон и иконок:

    private void btnABORTRETRYIGNORE_Click(object sender, System.EventArgs e)
    		{
    			//Текст сообщения
    			string text = "Hello World";
    			//Заголовок окна
    			string title = "From WinApi";
    			// Тип данных IntPtr — указатель на объект. 
    			WinAPIClass.MessageBox(IntPtr.Zero, text, title, (uint)(WinAPIClass.MessageBoxType.MB_ABORTRETRYIGNORE | WinAPIClass.MessageBoxType.MB_ICONQUESTION));
    			
    			
    		}
    
    		private void btnYESNOCANCEL_Click(object sender, System.EventArgs e)
    		{
    			string text = "Hello World";
    			string title = "From WinApi";
    		WinAPIClass.MessageBox(IntPtr.Zero, text, title, (uint)(WinAPIClass.MessageBoxType.MB_YESNOCANCEL | WinAPIClass.MessageBoxType.MB_ICONINFORMATION));
    		}
    
    		private void btnOK_Click(object sender, System.EventArgs e)
    		{
    			string text = "Hello World";
    			string title = "From WinApi";
    			WinAPIClass.MessageBox(IntPtr.Zero, text, title, (uint)(WinAPIClass.MessageBoxType.MB_OK | WinAPIClass.MessageBoxType.MB_ICONSTOP));
    		
    		
    		}
    
    		private void btnYESNO_Click(object sender, System.EventArgs e)
    		{
    			string text = "Hello World";
    			string title = "From WinApi";
    			WinAPIClass.MessageBox(IntPtr.Zero, text, title, (uint)(WinAPIClass.MessageBoxType.MB_YESNO | WinAPIClass.MessageBoxType.MB_ICONEXCLAMATION));
    		
    		
    		}

    Запускаем приложение. При нажатии на кнопки появляются соответствующие диалоговые окна (рис. 5.2).

    (рис 5.2) Результат работы приложения MessageBox

    На языке С метод в обработчике будет выглядеть следующим образом (сравните преобразование типов данных!):

    INT MessageBox(HWND hWnd, LPCTSTR lpText, 
    LPCTSTR lpCaption, UINT uType);

    На диске, прилагаемом к книге, вы найдете приложение MessageBox. (Code\Glava5\MessageBox).

    Динамик компьютера. Библиотека Kernel32.dll

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

    Создайте новое Windows-приложение и назовите его EasySound. Располагаем на форме семь кнопок и устанавливаем следующие свойства формы и кнопок:

    Form1, форма, свойствоЗначение
    Size 344; 264
    Text Easy Sound
    Button1, свойствоЗначение
    Name btnDo
    Location 0; 0
    Size 48; 232
    Text До
    Button2, свойствоЗначение
    Name btnRe
    Location 48; 0
    Size 48; 232
    Text Ре
    Button3, свойствоЗначение
    Name btnMi
    Location 96; 0
    Size 48; 232
    Text Ми
    Button4, свойствоЗначение
    Name btnFa
    Location 144; 0
    Size 48; 232
    Text Фа
    Button5, свойствоЗначение
    Name btnSol
    Location 192; 0
    Size 48; 232
    Text Соль
    Button6, свойствоЗначение
    Name btnLya
    Location 240; 0
    Size 48; 232
    Text Ля
    Button7, свойствоЗначение
    Name btnС
    Location 288; 0
    Size 48; 232
    Text Си

    Добавляем класс WinAPIClass:

    using System;
    using System.Runtime.InteropServices;
    namespace EasySound
    {
    
     public class WinAPIClass
      {
        #region Вызов сигнала
    	/// <summary>
    	/// Вызов сигнала.
    	/// </summary>
    	/// <param name="frequency">Частота сигнала в герцах. Диапазон от 37 до 32767 Гц.</param>
    	/// <param name="duration">Продолжительность сигнала в миллисекундах.</param>			
    	/// <returns></returns>		
    	[DllImport("Kernel32.dll")]
    	public static extern bool Beep (int frequency, int duration);
    	#endregion
    		
    	}
    }

    Создаем обработчики для кнопок:

    private void btnDo_Click(object sender, System.EventArgs e)
    		{
    //В скобках: первый параметр — частота, второй — продолжительность сигнала
    		WinAPIClass.Beep(1000, 100);
    		}
    
    		private void btnRe_Click(object sender, System.EventArgs e)
    		{
    		WinAPIClass.Beep(3000, 100);
    		}
    
    		private void btnMi_Click(object sender, System.EventArgs e)
    		{
    		WinAPIClass.Beep(5000, 100);
    		}
    
    		private void btnFa_Click(object sender, System.EventArgs e)
    		{
    		WinAPIClass.Beep(7000, 100);
    		}
    
    		private void btnSol_Click(object sender, System.EventArgs e)
    		{
    		WinAPIClass.Beep(9000, 100);
    		}
    
    		private void btnLya_Click(object sender, System.EventArgs e)
    		{
    		WinAPIClass.Beep(11000, 100);
    		}
    
    		private void btnC_Click(object sender, System.EventArgs e)
    		{
    		WinAPIClass.Beep(13000, 100);
    		}

    Запускаем приложение. Я не уверен, что в музыкальном смысле создаваемые сигналы в точности идентичны своим названиям, но тем не менее определенное сходство наверняка есть (рис. 5.3).

    (рис 5.3) Приложение Easy Sound

    На языке С метод в обработчике будет выглядеть следующим образом:

    BOOL Beep(DWORD dwFreq, DWORD dwDuration);

    На диске, прилагаемом к книге, вы найдете приложение EasySound (Code\Glava5\EasySound).

    Проявление окна

    Обычная Windows-форма тоже может быть анимирована, что иногда используется для создания заставки автозагрузочных дисков (рис. 5.4). К сожалению, возможное отсутствие библиотеки .NET Framework на компьютере пользователя делает ненадежным использование такого рода приложений, написанных на C#. Остается надеяться, что выход новой версии – Microsoft Windows Longhorn — изменит ситуацию в лучшую сторону (подробнее о распространении приложений — в лекции 9).

    (рис 5.4) Анимированная форма — заставка автозагрузочного диска

    Создайте новое приложение и назовите его AnimatedWindow. Расположите на форме три кнопки и установите следующие свойства формы и кнопок:

    Form1, форма, свойствоЗначение
    Text Анимация формы
    Button1, свойствоЗначение
    Name btnAW_BLEND
    Location 30; 62
    Size 232; 23
    Text Проявление
    Button2, свойствоЗначение
    Name btnHOR_AW_SLIDE
    Location 30; 118
    Size 232; 23
    Text Горизонтальное появление
    Button3, свойствоЗначение
    Name btnCenter_AW_SLIDE
    Location 30; 182
    Size 232; 23
    Text Появление из центра

    Добавляем класс WinAPIClass:

    using System;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    namespace AnimatedWindow
    {
    	
    	public class WinAPIClass
    	{
    		#region Анимация окна
    		/// <summary>
    		/// Тип анимации окна. Перечисление возвращает тип данных int
    		/// после приведения. Каждому элементу перечисления присвоено 
    		/// свое значение типа int.
    		/// </summary>	
    		[Flags]
    			public enum AnimateWindowFlags:int
    		{
    			AW_HOR_POSITIVE = 1,
    			AW_HOR_NEGATIVE = 2,
    			AW_VER_POSITIVE = 4,
    			AW_VER_NEGATIVE = 8,
    			AW_CENTER = 16,
    			AW_HIDE = 65536,
    			AW_ACTIVATE = 131072,
    			AW_SLIDE = 262144,
    			AW_BLEND = 524288
    		};
    		
    		/// <summary>
    		/// Анимация окна.
    		/// </summary>
    		/// <param name="hwnd">Окно.</param>
    		/// <param name="dwTime">Время.</param>
    		/// <param name="dwFlags">Тип анимации. Если в неуправляемом 
    		/// коде используется перечисление, то его нужно конвертировать
    		/// в тип данных int. </param>			
    		/// <returns></returns>
    		[DllImportAttribute("user32.dll",EntryPoint="AnimateWindow",SetLastError=true)]
    		public static extern bool AnimateWindow(IntPtr hwnd,int dwTime,int dwFlags);
    		
    		/// <summary>
    		/// Анимация окна.
    		/// </summary>
    		/// <param name="ctrl">Окно.</param>
    		/// <param name="dwTime">Время.</param>
    		/// <param name="Flags">Флаги.</param>		
    		/// <returns></returns>
    		public static bool AnimateWindow(Control ctrl,int dwTime, AnimateWindowFlags Flags)
    		{
    			return AnimateWindow(ctrl.Handle,dwTime,(int)Flags);
    		}
    		#endregion
    	}
    }

    Создаем обработчики кнопок:

    private void btnAW_BLEND_Click(object sender, System.EventArgs e)
    		{
    			
    				// Скрываем окно
    				this.Hide();
    				// Запускаем анимацию. Второй параметр в скобках — время анимации в миллисекундах.
    				WinAPIClass.AnimateWindow(this, 3000, WinAPIClass.AnimateWindowFlags.AW_ACTIVATE|WinAPIClass.AnimateWindowFlags.AW_BLEND);
    				// Отображаем кнопки после анимации
    				this.btnAW_BLEND.Invalidate();
    				this.btnHOR_AW_SLIDE.Invalidate();
    				this.btnCenter_AW_SLIDE.Invalidate();
    
    		}
    
    		private void btnHOR_AW_SLIDE_Click(object sender, System.EventArgs e)
    		{
    			
    			this.Hide();
    			WinAPIClass.AnimateWindow(this, 3000, WinAPIClass.AnimateWindowFlags.AW_HOR_POSITIVE|WinAPIClass.AnimateWindowFlags.AW_SLIDE);
    			this.btnAW_BLEND.Invalidate();
    			this.btnHOR_AW_SLIDE.Invalidate();
    			this.btnCenter_AW_SLIDE.Invalidate();
    		}
    
    		private void btnCenter_AW_SLIDE_Click(object sender, System.EventArgs e)
    		{
    			this.Hide();
    			WinAPIClass.AnimateWindow(this, 3000, WinAPIClass.AnimateWindowFlags.AW_CENTER|WinAPIClass.AnimateWindowFlags.AW_SLIDE);
    			this.btnAW_BLEND.Invalidate();
    			this.btnHOR_AW_SLIDE.Invalidate();
    			this.btnCenter_AW_SLIDE.Invalidate();
    		
    		}

    При запуске приложения доступны три вида анимации (рис. 5.5). Добавьте кнопок на форму и попробуйте вызвать другие эффекты перечисления AnimateWindowFlags.

    (рис 5.5) Анимация формы. А — готовое приложение, Б — проявление (изменение прозрачности), В — горизонтальное появление справа, Г — появление из центра (расширение)

    На языке С метод в обработчике будет выглядеть следующим образом:

    BOOL AnimateWindow(HWND hwnd, DWORD dwTime, DWORD dwFlags);

    На диске, прилагаемом к книге, вы найдете приложение AnimatedWindow (Code\Glava5\ AnimatedWindow).

    Завершение работы Windows, перезагрузка, выход пользователя из системы

    Использование функций WinAPI позволяет добавлять в свои приложения даже такую функциональность, как завершение работы системы и ее перезагрузка. Создайте новое Windows- приложение и назовите его StartTurnOff. Добавляем на форму пять кнопок и устанавливаем следующие значения формы и кнопок:

    Form1, форма, свойствоЗначение
    BackgroundImage Code\Glava5\StartTurnOff\Image\Window.bmp
    FormBorderStyle None
    Size 314; 400
    StartPosition CenterScreen
    Button1, свойствоЗначение
    Name BtnCancel
    Location 243; 169
    Size 61; 19
    Text Отмена
    Button2, свойствоЗначение
    Name btnCancel2
    Location 245; 369
    Size 61; 19
    Text Отмена
    Button3, свойствоЗначение
    Name BtnTurnOff
    Image Code\Glava5\StartTurnOff\Image\TurnOff.bmp
    Location 140; 80
    Size 34; 34
    Text
    Button4, свойствоЗначение
    Name BtnRestart
    Image Code\Glava5\StartTurnOff\Image\Restart.bmp
    Location 238; 81
    Size 34; 34
    Text
    Button5, свойствоЗначение
    Name BtnUserOut
    Image Code\Glava5\StartTurnOff\Image\UserOut.bmp
    Location 198; 280
    Size 34; 34
    Text

    Добавляем класс WinAPIClass:

    using System;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    using System.Text;
    namespace StartTurnOff
    {
    		
    		public class WinAPIClass
    		{
    			
    			#region Завершение работы Windows. 
    			/// <summary>
    			/// Завершение работы.
    			/// </summary>
    			/// <param name="shutDownType">Тип завершения работы.</param>
    			/// <param name="reason">Причина</param>		
    			/// <returns></returns>
    			[DllImport("user32.dll")]
    			public static extern bool ExitWindowsEx(uint shutDownType, string reason);
    
    			//To shut down or restart the system, the calling process must use the 
    			//AdjustTokenPrivileges function to enable the SE_SHUTDOWN_NAME privilege.
    			// Для выключения или перезагрузки системы вызываемый процесс 
    			//должен использовать функцию AdjustTokenPrivileges для получения 
    			//определенного уровня доступа (привилегии)
    
    			/// <summary>
    			/// Тип завершения работы.
    			/// </summary>
    			public enum ShutdownType:int
    			{
    				LogOf = 0,
    				Shutdown = 0x00000001,
    				Reboot = 0x00000002,
    				PowerOf = 0x00000008			
    			}
    
    			#endregion
    			// Дополнительные переменные
    			public const int SE_PRIVILEGE_ENABLED = 2;
    			public const string SE_SHUTDOWN_NAME = "SeShutdownPrivilege";
    			public const int ANYSIZE_ARRAY = 1;
    			public const int TOKEN_QUERY = 8;
    			public const int TOKEN_ADJUST_PRIVILEGES = 32;//0x00000020;
    
    
    			
    			// Структура, используемая  для передачи в метод AdjustTokenPrivileges
    			//  в качестве необходимой привилегии.
    			[StructLayout(LayoutKind.Sequential, Pack=1)]
    			internal struct TokPriv1Luid
    			{
    				// Количество привилегий в наборе.
    				public int Count;
    				public long Luid;
    				// Тип привилегии.
    				public int Attr;
    			}
    
    			/// <summary>
    			/// Возвращение указателя на текущий процесс.
    			/// </summary>
    			/// <returns></returns>
    			[DllImport("kernel32.dll", ExactSpelling=true) ]
    			internal static extern IntPtr GetCurrentProcess();
    			/// <summary>
    			/// Открывание процесса  access token, который содержит информацию о 
    ///безопасности текущего пользователя, 
    			/// привязанного  к заданному процессу.
    			/// </summary>
    			/// <param name="h">Указатель на процесс.</param>
    			/// <param name="acc">Тип access token.</param>
    			/// <param name="phtok">Указатель на access token.</param>
    			/// <returns></returns>
    			[DllImport("advapi32.dll", ExactSpelling=true) ]
    			internal static extern bool OpenProcessToken( IntPtr h, int acc, ref IntPtr phtok );
    			/// <summary>
    			/// Возвращение значения переменной в текущей сессии ОС.
    			/// </summary>
    			/// <param name="host">Название системы. Если значение  null,  
    			///поиск осуществляется
    			///  в локальной системе.</param>
    			/// <param name="name">Название переменной.</param>
    			/// <param name="pluid">Указатель на значение переменной.</param>
    			/// <returns></returns>
    			[DllImport("advapi32.dll") ]
    			internal static extern bool LookupPrivilegeValue( string host, string name, ref long pluid );
    			/// <summary>
    			/// Включение  указанной привилегии.
    			/// </summary>
    			/// <param name="htok">Указатель на access token</param>
    			/// <param name="disall">Необходимость выключения  всех привилегий.</param>
    			/// <param name="newst">Новое значение привилегии. Если предыдущее
    			/// значение установлено в true, 
    			/// то это значение не учитывается.</param>
    			/// <param name="len">Размер буфера для следующего значения.</param>
    			/// <param name="prev">Предыдущая привилегия.</param>
    			/// <param name="relen">Указатель на  размер полученных привилегий.</param>
    			/// <returns></returns>
    			[DllImport("advapi32.dll", ExactSpelling=true) ]
    			internal static extern bool AdjustTokenPrivileges( IntPtr htok, bool disall,
    				ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen );
    		}
    	}

    В коде формы добавляем обработчики кнопок:

    private void btnCancel_Click(object sender, System.EventArgs e)
    		{
    			this.Close();
    		}
    
    		private void btnUserOut_Click(object sender, System.EventArgs e)
    		{
    			WinAPIClass.ExitWindowsEx((uint)WinAPIClass.ShutdownType.LogOf, "Win Api test");
    		}
    
    		private void btnTurnOff_Click(object sender, System.EventArgs e)
    		{
    			RestartOrShotDown(WinAPIClass.ShutdownType.Shutdown);
    		}
    		
    		private void btnRestart_Click(object sender, System.EventArgs e)
    		{			
    			RestartOrShotDown(WinAPIClass.ShutdownType.Reboot);
    		}
    		void RestartOrShotDown(WinAPIClass.ShutdownType type)
    		{				
    			WinAPIClass.TokPriv1Luid tp;
    			IntPtr hproc = WinAPIClass.GetCurrentProcess();
    			IntPtr hToken = IntPtr.Zero;
    			WinAPIClass.OpenProcessToken( hproc, WinAPIClass.TOKEN_ADJUST_PRIVILEGES | WinAPIClass.TOKEN_QUERY, ref hToken );
    			tp.Count = 1;
    			tp.Luid = 0;
    			tp.Attr = WinAPIClass.SE_PRIVILEGE_ENABLED;
    			WinAPIClass.LookupPrivilegeValue(null, WinAPIClass.SE_SHUTDOWN_NAME, ref tp.Luid );
    			WinAPIClass.AdjustTokenPrivileges(hToken, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero );
    			WinAPIClass.ExitWindowsEx((uint)type, "Win Api test");
    
    		}
    
    		private void btnCancel2_Click(object sender, System.EventArgs e)
    		{
    			this.Close();
    		}

    При запуске приложения появляется совмещенное окно завершения работы и смены пользователя (рис. 5.6). Кнопки "Выключение", "Перезагрузка" и "Выход" лучше всего проверять, завершив работу со средой Visual Studio .NET.

    (рис 5.6) Приложение StartTurnOff

    На диске, прилагаемом к книге, вы найдете приложение StartTurnOff (Code\Glava5\StartTurnOff).

    Как использовать другие функции WinAPI?

    Рассмотренные примеры позволяют сделать вывод: использование функций WinAPI относится к одному из самых быстрых способов разработки приложений. При этом основная трудность заключается не в добавлении функции к приложению, а в ее поиске. В самом деле, операционная система Windows представляет чрезвычайно большой набор функций. Библиотека MSDN содержит подробное описание практически всех функций — для просмотра их списка в алфавитном порядке выбираем Пуск\Все программы\Microsoft Visual Studio .NET 2003\Microsoft Visual Studio .NET 2003 Documentation, переходим на вкладку Поиск (Search) и в строке поиска набираем WinAPI functions. В списке статей появится заголовок Functions in Alphabetical Order (Функции в алфавитном порядке) — это и есть то, что нам нужно (рис. 5.7).

    (рис 5.7) Список функций WinAPI в алфавитном порядке в библиотеке MSDN

    Я привел методику поиска, а не расположение в содержании, — в зависимости от версии структура библиотеки может меняться, но она неизменно будет содержать список функций.

    Класс String Builder

    При изучении синтаксиса языка C# вы проходили класс String (пространство имен System.String) и, возможно, класс StringBuilder (пространство имен System.Text). Если эти понятия не совсем вам ясны, освежите материал — класс String описывается в любом учебнике по C#. Здесь же мы кратко рассмотрим класс StringBuilder — в следующем примере этот класс в качестве примера при вызове очередной функции WinAPI.

    Одним из основных преимуществ использования этого класса является скорость работы. Упрощенно говоря, при изменении строки, созданной как экземпляр класса String, у нас создается каждый раз новый экземпляр класса, а старый уничтожается; при использовании же класса StringBuilder мы всегда работаем с одним экземпляром.

    Экземпляр String Builder создается так же, как и другие экземпляры классов:

    StringBuilder sb1=new StringBuilder("Hello");

    Класс StringBuilder содержит только динамические методы. Рассмотрим на практике некоторые свойства и методы этого класса. Создайте новое консольное приложение и назовите его SBuilder

    У класса StringBuilder нет статических методов. Все его методы — динамические. Ниже перечислены основные свойства и методы класса StringBuilder.

    Свойство Length. Возвращает длину строки. Пример (здесь и далее см. рис. 5.8):

    StringBuilder sb1=new StringBuilder("Hello"); 
    int k=sb1.Length;
    Console.WriteLine("Длина строки sb1 "+k);

    Свойство только для чтения MaxCapacity. Возвращает максимальное количество символов, которые можно записать в экземпляр класса StringBuilder. Пример:

    StringBuilder sb1=new StringBuilder("Hello"); 
    System.Console.WriteLine("Максимальное количество символов "+sb1.MaxCapacity);

    Метод Append. Добавляет значение новой строки к существующему значению. Пример:

    StringBuilder sb1=new StringBuilder("Hello"); 
    StringBuilder sb2=new StringBuilder(" World ");
    sb1.Append(sb2);
    sb1.Append("!!!");
    System.Console.WriteLine(sb1);

    Метод Equals. Служит для сравнения двух строк. Возвращает true или false. Пример использования:

    StringBuilder sb1=new StringBuilder("Hello"); 
    StringBuilder sb2=new StringBuilder(" World ");       
    if(sb1.Equals(sb2))
    			System.Console.WriteLine("Строки равны");
    else
    			System.Console.WriteLine("Строки не равны");

    Метод Insert. Вставляет символы в заданную позицию (нумерация начинается с нуля). Пример использования:

    StringBuilder sb3=new StringBuilder("Пртствие!");
    sb3.Insert(2, "иве");
    System.Console.WriteLine(sb3);

    Метод Remove. Удаляет символы из строки. Первый параметр метода Remove — номер позиции, с которой начинается удаление, второй — количество удаляемых символов. Пример:

    StringBuilder sb4=new StringBuilder("Казнить нельзя, помиловать");
    sb4.Insert(7, ",");
    sb4.Remove(15, 1);
    System.Console.WriteLine(sb4);

    Метод Replace. Заменяет символы. Пример:

    StringBuilder sb5=new StringBuilder("Internet");
    sb5.Replace("er", "ra");
    System.Console.WriteLine(sb5);
    (рис 5.8) Результаты приложения SBuilder

    На диске, прилагаемом к книге, вы найдете приложение SBuilder (Code\Glava5\SBuilder).

    Подсчет времени выполнения задачи

    Сравним теперь работу классов StringBuilder и String, используя точный счетчик времени выполнения задачи. Фактически вычисления производятся в тактах процессора, а потом переводятся в миллисекунды. Создайте новое консольное приложение и назовите его PrecisionCounter. Добавляем новый класс к проекту и называем его MechanismCounter. Полный листинг этого класса с комментариями:

    using System;
    using System.Runtime.InteropServices;
    using System.Security;
    
    namespace PrecisionCounter
    {
    	public class MechanismCounter
    	{
    	
    			static Int64 _start;
    			static Int64 finish;
    			static Int64 freq;
    
    			/// <summary>
    			/// Начало подсчета времени выполнения.
    			/// </summary>
    			public static void Start()
    			{
    				MechanismCounter._start = 0;
    				MechanismCounter.QueryPerformanceCounter(ref _start);
    			}
    
    			/// <summary>
    			/// Завершение подсчета времени выполнения  и возвращение  времени в секундах.
    			/// </summary>
    			/// <returns>Время в секундах, потраченное на выполнение участка
    			/// кода. Десятичная часть отражает доли секунды.</returns>
    			public static float Finish()
    			{
    				MechanismCounter.finish = 0;
    				MechanismCounter.QueryPerformanceCounter(ref finish);
    				MechanismCounter.freq = 0;
    				MechanismCounter.QueryPerformanceFrequency(ref freq);
    				return (((float)(MechanismCounter.finish - MechanismCounter._start))/((float)MechanismCounter.freq));
    			}
    			/// <summary>
    			/// Получение текущего значения тактов процессора.
    			/// </summary>
    			/// <param name="performanceCount">Переменная, в которую запишется значение.</param>
    			/// <returns></returns>
    			[SuppressUnmanagedCodeSecurity]
    			[DllImport("Kernel32.dll")]
    			static extern bool QueryPerformanceCounter(ref Int64 performanceCount);
    			/// <summary>
    			/// Получение текущей частоты (количество тактов / секунда) работы процессора.
    			/// </summary>
    			/// <param name="frequency">Переменная, в которую запишется значение.</param>
    			/// <returns></returns>
    			[SuppressUnmanagedCodeSecurity]
    			[DllImport("Kernel32.dll")]
    			static extern bool QueryPerformanceFrequency(ref Int64 frequency);
    		}
    	}

    Итак, механизм счетчика готов. Запустим теперь две задачи: будем слагать строки, используя один раз String, а второй — StringBuilder:

    using System;
    using System.Text;
    
    namespace PrecisionCounter
    {
    
    	class TestCounter
    	{		
    		[STAThread]
    		static void Main(string[] args)
    		{
    			Output("Запуск процесса подсчета времени выполнения сложения строк с использованием типа данных string");
    			MechanismCounter.Start();
    			string s = String.Empty;
    			for(int i = 0; i < 10000; i++)
    			{
    				s+="TestPerfCounter";
    			}
    			float timeString = MechanismCounter.Finish();
    			Output("Завершение процесса подсчета времени. Время выполнения (сек): " + timeString);
    			
    			Output("Запуск процесса подсчета времени выполнения сложения строк с использованием типа данных StringBuilder");
    			MechanismCounter.Start();
    			StringBuilder sb = new StringBuilder();
    			for(int i = 0; i < 10000; i++)
    			{
    				sb.Append("TestCounter");
    			}
    			float timeStringBuilder = MechanismCounter.Finish();
    			Output("Завершение процесса подсчета времени. Время выполнения (сек): " + timeStringBuilder);
    		}
    		/// <summary>
    		/// Вывод на экран заданного значения.
    		/// </summary>
    		/// <param name="s">Значение.</param>
    		static void Output(string s)
    		{
    			Console.WriteLine(s);
    		}
    	}
    }

    Как и следовало ожидать, использование класса StringBuilder значительно ускоряет работу (рис. 5.9).

    (рис 5.9) Результат работы приложения PrecisionCounter

    На диске, прилагаемом к книге, вы найдете приложение PrecisionCounter (Code\Glava5\ PrecisionCounter).

    Программа для фотографирования экрана. Библиотеки user32.dll и GDI32

    Операционная система Windows предоставляет возможность снятия снимков с экрана (скриншотов) — при нажатии клавиши Print Screen текущее изображение помещается в буфер обмена. Затем можно вставить изображение непосредственно в документ или графический редактор — для последующего сохранения в нужном формате. Сделаем что-то подобное — простое приложение для фотографирования экрана и последующего сохранения изображений. Создайте новое Windows-приложение и назовите его ScreenShot. Добавляем на него одну кнопку и устанавливаем следующие значения формы и кнопки:

    Form1, форма, свойствоЗначение
    FormBorderStyle FixedSingle
    Size 174; 94
    Text ScreenShot!
    Button1, свойствоЗначение
    Name btnCreate
    BackgroundImage Code\Glava5\ScreenShot\Imageutton.bmp
    Location 0; 0
    Size 176; 64
    Text

    Из окна Toolbox перетаскиваем на форму элемент управления SaveFileDialog — его свойства установим программно. Добавляем класс к проекту и называем его ScreenShotDll.cs. Далее привожу полный листинг этого класса с комментариями:

    using System;
    using System.Runtime.InteropServices;
    using System.Drawing.Imaging;
    using System.Drawing;
    using System.IO;
    
    namespace ScreenShot
    {
    
    	 /// <summary>
    	 /// Класс для создания снимков экрана
    	 /// </summary>
    	 public class ScreenShotDll
    	 {
    		 /// <summary>
    		 ///Создание снимка
    		 /// </summary>
    		 /// <param name="fileName">Название файла.</param>
    		 /// <param name="imageFormat">Формат изображения.</param>
    		 public void CaptureScreen(string fileName,ImageFormat imageFormat)
    		 {
    			 int hdcSrc = User32.GetWindowDC(User32.GetDesktopWindow()), 
    				 hdcDest = GDI32.CreateCompatibleDC(hdcSrc),
    				 hBitmap = GDI32.CreateCompatibleBitmap(hdcSrc,
    				 GDI32.GetDeviceCaps(hdcSrc,8),GDI32.GetDeviceCaps(hdcSrc,10)); 
    			 GDI32.SelectObject(hdcDest,hBitmap);
    			 GDI32.BitBlt(hdcDest,0,0,GDI32.GetDeviceCaps(hdcSrc,8),
    				 GDI32.GetDeviceCaps(hdcSrc,10),
    				 hdcSrc,0,0,0x00CC0020);
    			 SaveImageAs(hBitmap,fileName,imageFormat);
    			 Cleanup(hBitmap,hdcSrc,hdcDest);
    		 }
    		 /// <summary>
    		 /// Получение снимка в формате Windows Bitmap
    		 /// </summary>
    		 /// <returns></returns>
    		 public Bitmap CaptureScreen()
    		 {
    			 int hdcSrc = User32.GetWindowDC(User32.GetDesktopWindow()), 
    				 hdcDest = GDI32.CreateCompatibleDC(hdcSrc),
    				 hBitmap = GDI32.CreateCompatibleBitmap(hdcSrc,
    				 GDI32.GetDeviceCaps(hdcSrc,8),GDI32.GetDeviceCaps(hdcSrc,10)); 
    			 GDI32.SelectObject(hdcDest,hBitmap);
    			 GDI32.BitBlt(hdcDest,0,0,GDI32.GetDeviceCaps(hdcSrc,8),
    				 GDI32.GetDeviceCaps(hdcSrc,10),
    				 hdcSrc,0,0,0x00CC0020);
    			 Bitmap btm = GetImage(hBitmap);
    			 Cleanup(hBitmap,hdcSrc,hdcDest);
    			 return btm;
    		 }
    		 /// <summary>
    		 /// Освобождение ресурсов системы.
    		 /// </summary>
    		 /// <param name="hBitmap">Указатель на структуру данных.</param>
    		 /// <param name="hdcSrc">Указатель на структуру данных источника.</param>
    		 /// <param name="hdcDest">Указатель на структуру данных назначения.</param>
    		 private void Cleanup(int hBitmap, int hdcSrc, int hdcDest)
    		 {
    			 User32.ReleaseDC(User32.GetDesktopWindow(),hdcSrc);
    			 GDI32.DeleteDC(hdcDest);
    			 GDI32.DeleteObject(hBitmap);
    		 }      
    		 /// <summary>
    		 /// Сохранение снимка
    		 /// </summary>
    		 /// <param name="hBitmap">Указатель на изображение.</param>
    		 /// <param name="fileName">Название файла.</param>
    		 /// <param name="imageFormat">Формат изображения.</param>
    		 private void SaveImageAs(int hBitmap, string fileName, ImageFormat imageFormat)
    		 {
    			 Bitmap image = new Bitmap(Image.FromHbitmap(new IntPtr(hBitmap)),
    				 Image.FromHbitmap(new IntPtr(hBitmap)).Width,
    				 Image.FromHbitmap(new IntPtr(hBitmap)).Height);
    			 image.Save(fileName,imageFormat);
    		 }
    		 /// <summary>
    		 /// Возвращение  изображения по указателю.
    		 /// </summary>
    		 /// <param name="hBitmap">Указатель на изображение.</param>
    		 /// <returns></returns>
    		 private Bitmap GetImage(int hBitmap)
    		 {
    			 Bitmap image = 
    				 new Bitmap(Image.FromHbitmap(new IntPtr(hBitmap)),
    				 Image.FromHbitmap(new IntPtr(hBitmap)).Width,
    				 Image.FromHbitmap(new IntPtr(hBitmap)).Height);
    			 return image;
    		 }
    	 }
    	 /// <summary>
    	 /// Реализация методов библиотеки User32
    	 /// </summary>
    	 class User32
    	 {
    		 /// <summary>
    		 /// Возвращение  указателя на рабочий стол.
    		 /// </summary>
    		 /// <returns></returns>
    		 [DllImport("User32.dll")]
    		 public static extern int GetDesktopWindow();
    		 /// <summary>
    		 /// Получение структуры данных.
    		 /// </summary>
    		 /// <param name="hWnd">Указатель на окно.</param>
    		 /// <returns></returns>
    		 [DllImport("User32.dll")]
    		 public static extern int GetWindowDC(int hWnd);
    		 /// <summary>
    		 /// Освобождение  структуры данных.
    		 /// </summary>
    		 /// <param name="hWnd">Указатель на окно.</param>
    		 /// <param name="hDC">Указатель на структуру данных.</param>
    		 /// <returns></returns>
    		 [DllImport("User32.dll")]
    		 public static extern int ReleaseDC(int hWnd,int hDC);
    	 }
    	 /// <summary>
    	 /// Реализация методов библиотеки GDI32
    	 /// </summary>
    	 class GDI32
    	 {
    		 /// <summary>
    		 /// Передача изображения.
    		 /// </summary>
    		 /// <param name="hdcDest">Указатель на назначение передачи.</param>
    		 /// <param name="nXDest">Х координата верхнего левого угла назначения.</param>
    		 /// <param name="nYDest">У координата верхнего левого угла назначения.</param>
    		 /// <param name="nWidth">Ширина прямоугольника.</param>
    		 /// <param name="nHeight">Высота прямоугольника.</param>
    		 /// <param name="hdcSrc">Указатель на источник.</param>
    		 /// <param name="nXSrc">Х координата верхнего левого угла источника.</param>
    		 /// <param name="nYSrc">У координата верхнего левого угла источника.</param>
    		 /// <param name="dwRop">Код растровой операции.</param>
    		 /// <returns></returns>
    		 [DllImport("GDI32.dll")]
    		 public static extern bool BitBlt(int hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, int hdcSrc, 
    			 int nXSrc, int nYSrc,int dwRop);
    		 /// <summary>
    		 /// Создание  и запись в структуру данных изображения.
    		 /// </summary>
    		 /// <param name="hdc">Указатель на структуру данных.</param>
    		 /// <param name="nWidth">Ширина изображения.</param>
    		 /// <param name="nHeight">Высота изображения.</param>
    		 /// <returns></returns>
    		 [DllImport("GDI32.dll")]
    		 public static extern int CreateCompatibleBitmap(int hdc,int nWidth, int nHeight);
    		 /// <summary>
    		 /// Создание и сохранение  в памяти структуры данных, совместимой с указанным устройством вывода.
    		 /// Для помещения  изображения в структуру данных 
    		 /// необходимо указать высоту, ширину и цветовой  режим  изображения. 
    		 /// Это можно сделать с помощью функции CreateCompatibleBitmap, поддерживаемой устройствами с растровым выводом. 
    		 /// Для получения информации об этих устройствах используется функция GetDeviceCaps. 
    		  /// После использования структуры данных ее нужно удалить с помощью функции DeleteDC.
    		 /// </summary>
    		 /// <param name="hdc">Указатель на существующую структуру данных. Если указатель равен null,
    		 /// то функция создает  структуру для экрана текущего приложения.</param>
    		 /// <returns></returns>
    		 [DllImport("GDI32.dll")]
    		 public static extern int CreateCompatibleDC(int hdc);
    		 /// <summary>
    		 /// Удаление  указанной структуры данных.
    		 /// </summary>
    		 /// <param name="hdc">Структура данных.</param>
    		 /// <returns></returns>
    		 [DllImport("GDI32.dll")]
    		 public static extern bool DeleteDC(int hdc);
    		 /// <summary>
    		 /// Удаление графических объектов с освобождением системных ресурсов.
    		 /// </summary>
    		 /// <param name="hObject">Указатель на графический объект.</param>
    		 /// <returns></returns>
    		 [DllImport("GDI32.dll")]
    		 public static extern bool DeleteObject(int hObject);
    		 /// <summary>
    		 /// Возвращение  информации об указанной структуре.
    		 /// </summary>
    		 /// <param name="hdc">Указатель на структуру данных.</param>
    		 /// <param name="nIndex">Индекс совместимости. .</param>
    		 /// <returns></returns>
    		 [DllImport("GDI32.dll")]
    		 public static extern int GetDeviceCaps(int hdc, int nIndex);
    		 /// <summary>
    		 /// Выбор объекта, находящегося в указанной структуре данных.
    		 /// </summary>
    		 /// <param name="hdc">Указатель на структуру данных.</param>
    		 /// <param name="hgdiobj">Указатель на объект.</param>
    		 /// <returns></returns>
    		 [DllImport("GDI32.dll")]
    		 public static extern int SelectObject(int hdc, int hgdiobj);
    	 }
     }

    Вся функциональность практически готова. Осталось только добавить обработчик кнопки btnCreate:

    private void btnCreate_Click(object sender, System.EventArgs e)
      {
    		
    	SaveFileDialog diag = new SaveFileDialog();
    	diag.Filter = "Файлы bmp|*.bmp";
    	if(diag.ShowDialog() != DialogResult.OK)
    	  return;
    	// Получаем адрес файла.
    	string filename = diag.FileName;
    	// Создаем экземпляр  класса ScreenShotDll
    	ScreenShotDll shoter = new ScreenShotDll();
    	// Создаем и сохраняем изображение.
    	shoter.CaptureScreen(filename, System.Drawing.Imaging.ImageFormat.Bmp);
    			
    		
      }

    Запускаем приложение (рис. 5.10). После нажатия на кнопку задаем в появившемся окне SaveFileDialog имя файла и сохраняем его.

    (рис 5.10) Приложение ScreenShot! и сделанный им снимок

    На диске, прилагаемом к книге, вы найдете приложение ScreenShot (Code\Glava5\ScreenShot).

    Вызов COM-компонентов из управляемого кода

    При вызове объекта COM-клиентом .NET-среда Common Language Runtime создает временную оболочку RCW (Runtime Callable Wrapper), причем всего одну, независимо от количества ссылок на объект. Это дает гарантию, что все обращения к объекту будут происходит только через одного проводника. Далее CLR, используя полученные из библиотеки метаданные, создает вызываемый объект и оболочку для возврата данных. Также CLR берет на себя функцию контроля сборки мусора в оболочке — разработчику не приходится задумываться об этом.

    Главной задачей оболочки RCW является скрытие различий между управляемым и неуправляемым кодом. Она командует жизненным циклом COM-объекта, передает вызовы методов между управляемым и неуправляемым кодами, конвертирует параметры методов. В результате RCW позволяет разработчикам писать код, который использует COM-объекты как обычные .NET-объекты.

    Приложение PDF Reader

    В качестве примера вызова COM-компонент создадим приложение, позволяющее просматривать документы в формате PDF. Создайте новое Windows-приложение и назовите его PDF Reader. Добавляем на форму элементы OpenFileDialog и MainMenu. Устанавливаем следующие свойства формы:

    Form1, форма, свойствоЗначение
    Text Обозреватель документов в формате PDF
    WindowState Maximized

    Добавляем пункты меню:

    NameTextShortcut
    mnuFile Файл
    mnuOpen Открыть CtrlО

    В окне Toolbox щелкаем правой кнопкой на вкладке Windows Forms, и в появившемся контекстном меню выбираем Add/Remove Items… . В окне Customize Toolbox переключаемся на вкладку COM Components и ставим галочку в чекбоксе компонента Adobe Acrobat 7.0 Browser Document (рис. 5.11).

    (рис 5.11) Добавление компонента в окно Toolbox

    Этот компонент появляется после установки программы Adobe Acrobat Reader 7.0. Нажимаем ОК — в окне Toolbox появился новый элемент (рис. 5.12), перетаскиваем его на форму и устанавливаем свойству Dock значение Fill.

    (рис 5.12) Компонент Adobe Acrobat 7.0 Browser Document

    Добавляем обработчик пункта меню "Открыть":

    private void mnuOpen_Click(object sender, System.EventArgs e)
    		{
    			openFileDialog1.Filter  = "Файлы pdf|*.pdf";
    			openFileDialog1.ShowDialog();
    			axAcroPDF1.LoadFile(openFileDialog1.FileName);
    		}

    Запускаем приложения. При открытии документа в формате pdf происходит, по сути, встраивание в форму интерфейса программы Adobe Acrobat Reader (рис. 5.13):

    (рис 5.13) Результат работы приложения PDF Reader

    На диске, прилагаемом к книге, вы найдете приложение PDF Reader. (Code\Glava5\PDF – Reader).

    Генерирование сборок взаимодействия. Утилита tlbimp.exe

    .NET-приложения могут использовать COM-классы с ранним или поздним связыванием. При использовании модели раннего связывания информация о типах должна быть известна на этапе компиляции. Для использования COM-типов в управляемом коде необходимо сгенерировать метаданные из библиотеки типов COM-компонента.

    Библиотека описания типов COM-компонента может быть независимой от компонента и располагаться в файлах с расширением .tlb, .olb или .ocx. Также описание типов компонента может быть внедрено в качестве ресурса в .exe или .dll файл.

    После обнаружения библиотеки описания типов COM-компонента можно приступить к генерации сборки, содержащей метаданные о типе. Это можно сделать с помощью утилиты tlbimp.exe (Type Library Importer). Для ее запуска выбираем Пуск\Все программы\ Microsoft Visual Studio .NET 2003 \ Visual Studio .NET Tools \ Visual Studio .NET 2003 Command Prompt и вводим название утилиты — tlbimp.exe. После генерации метаданных они сохраняются в виде обычной .NET-сборки. Такие сборки называются сборками взаимодействия, поскольку они включают в себя описание COM-компонента и не содержат кода. Утилита tlbimp.exe имеет следующий синтаксис:

    Tlbimp TlbFile [/out: name] [/reference: file] [/silent] [/verbose] [/strictref]
    [/unsafe][ [/primary]][/publickey: file] [/keyfile: file][/keycontainer: name]

    В таблице 5.4 представлено описание некоторых параметров утилиты:

    Некоторые параметры утилиты tlbimp.exe
    ПараметрОписание
    /out:filename Указывает адрес, название сборки и пространство имен, содержащее метаданные
    /keyfile:filename Параметры для генерирования сборки, подписанной строгим именем
    /reference:filename Если компонент ссылается на внешние сборки, то с помощью этого аргумента можно указать адрес файла, в котором необходимо искать внешние типы
    /primary Главная сборка взаимодействия для данного типа

    Например, для генерирования .NET-сборки myInteropassembly.dll на основании библиотеки activeds.tlb запускаем следующую строку в окне Visual Studio .NET 2003 Command Prompt:

    tlbimp c:\Windows\system32\activeds.tlb /out: c:\myInteropassembly.dll

    В результате на диске С появится сборка взаимодействия, которую можно будет использовать как частную или публичную (подробнее о сборке в лекции 9).

    Вернуться к учебному плану