При нажатии на Сумма в примере ArbitraryMethod из Лекция 7, VS 2013 выдается ошибка: Необработанное исключение типа "System.InvalidOperationException" в System.Windows.Forms.dll Дополнительные сведения: Недопустимая операция в нескольких потоках: попытка доступа к элементу управления "lblResult" не из того потока, в котором он был создан. Затем: Необработанное исключение типа "System.InvalidOperationException" в mscorlib.dll Дополнительные сведения: Для каждой асинхронной операции метод EndInvoke может вызываться только один раз. |
Использование библиотек кода в windows-формах
Подсчет времени выполнения задачи
Сравним теперь работу классов 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); } }Листинг 5.10.
Итак, механизм счетчика готов. Запустим теперь две задачи: будем слагать строки, используя один раз 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); } } }Листинг 5.11.
Как и следовало ожидать, использование класса StringBuilder значительно ускоряет работу (рис. 5.9).
На диске, прилагаемом к книге, вы найдете приложение PrecisionCounter (Code\Glava5\ PrecisionCounter).
Программа для фотографирования экрана. Библиотеки user32.dll и GDI32
Операционная система Windows предоставляет возможность снятия снимков с экрана (скриншотов) — при нажатии клавиши Print Screen текущее изображение помещается в буфер обмена. Затем можно вставить изображение непосредственно в документ или графический редактор — для последующего сохранения в нужном формате. Сделаем что-то подобное — простое приложение для фотографирования экрана и последующего сохранения изображений. Создайте новое Windows-приложение и назовите его ScreenShot. Добавляем на него одну кнопку и устанавливаем следующие значения формы и кнопки:
Button1, свойство | Значение |
---|---|
Name | btnCreate |
BackgroundImage | Code\Glava5\ScreenShot\Image\button.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); } }Листинг 5.12.
Вся функциональность практически готова. Осталось только добавить обработчик кнопки 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 имя файла и сохраняем его.
На диске, прилагаемом к книге, вы найдете приложение ScreenShot (Code\Glava5\ScreenShot).
Вызов COM-компонентов из управляемого кода
При вызове объекта COM-клиентом .NET-среда Common Language Runtime создает временную оболочку RCW (Runtime Callable Wrapper), причем всего одну, независимо от количества ссылок на объект. Это дает гарантию, что все обращения к объекту будут происходит только через одного проводника. Далее CLR, используя полученные из библиотеки метаданные, создает вызываемый объект и оболочку для возврата данных. Также CLR берет на себя функцию контроля сборки мусора в оболочке — разработчику не приходится задумываться об этом.
Главной задачей оболочки RCW является скрытие различий между управляемым и неуправляемым кодом. Она командует жизненным циклом COM-объекта, передает вызовы методов между управляемым и неуправляемым кодами, конвертирует параметры методов. В результате RCW позволяет разработчикам писать код, который использует COM-объекты как обычные .NET-объекты.