VR-online JOURNAL Фленов Михаил & VR-Group
VR-online для Программистов №15
Delphi World своими руками Формула прозрачности или прозрачная магия Приручение мыши, имитация ввода пользователя Delphi 2006 – новая реальность Минимум памяти Managed DirectX Полный контроль над Borland IDE
Copyright: VR-online Journal
http://www.vr-online.ru
VR-online Journal (Фленов Михаил & VR-Group)
Я решил попробовать продавать программы на российском рынке. Для этого я открыл новый сайт http://www.cydsoft.com/russia, где будет продаваться мой набор сетевых утилит CyD NET Utils. Для России я поставил вполне щадящую цену – 300р. Я думаю, такие деньги можно заплатить за программу, которая тебе необходима (если конечно она необходима). Пока сайт работает в тестовом режиме, но заценить его можно уже сейчас. Помимо программы на этом сайте я буду выкладывать различную документацию для системных администраторов и возможно какие-то исходные коды программ. Так что советую иногда заглядывать туда для получения новой инфы. Единственный вопрос – как теперь успевать ещё и делать что-то для VR? Я не собираюсь забрасывать этот проект и буду понемногу его продвигать и улучшать, но чтобы обновления проходили чаще, нам нужна твоя помощь. Присылай свои статьи, свои рассказы на около компьютерную тему и мы с удовольствием выложим её на сайт. Я выкладываю все и практически без редактирования. В этот номер попали не только мои статьи, но и наших постоянных читателей/посетителей, в общем, друзей. Надеюсь, что ты найдешь здесь что-то новое и интересное.
Фленов Михаил
http://www.vr-online.ru
Для программистов №15
VR-online JOURNAL Horrific aka Фленов Михаил
INFO: ИДЕЯ И РЕАЛИЗАЦИЯ: Флёнов Михаил (Horrific) ГРАФИКА: Фленов Михаил, tr4sh VR-Group: LittleBudda, Neon_Kaligula, Spider_NET, Styu INTERNET: WWW: http://www.vr-online.ru E-MAIL: mail@vr-online.ru
Данный журнал распространяется в виде PDF файлов. Вы можете выкладывать номера на любые носители без изменения внешнего вида журнала, без перевода в другие форматы, без изменения самого файла. В журнал запрещается вносить изменения. Перепечатка материалов запрещена. Журнал распространяется бесплатно, и ты можешь скачать его с нашего сайта, поэтому мы не видим смысла в перепечатывании материалов. Если ты хочешь стать автором журнала, то присылай свою статью на наш e-mail и мы обязательно включим её в очередной номер.
2
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Delphi World своими руками Вы думаете это сложно? Коновалов Сергей (c) 2006 Sergio_Lightning@yahoo.com Первоначально я хотел рассмотреть написание оболочки как пример использования Dev Express, но позже передумал. Ибо это тянет на отдельный цикл статей, и теперь они перед вами! Итак, зачем собственно переписывать то, что и так неплохо работает? 1) Поиск: меня абсолютно не устраивает быстродействие и возможности поиска 2) До недавнего времени у меня был не самый мощный компьютер, поэтому хотелось чтобы оболочка стартовала быстрее 3) Перегруженный и абсолютно не эргономичный интерфейс 4) Пара досадных багов и ненужные понты разработчика - Как же можно решить озвученные мною проблемы? - Использовать для хранения статей базу данных! Проектирование базы данных Самым рациональным будет использование файл-серверной MS ACCESS и даже не пытайтесь меня отговорить. Не будем создавать ничего лишнего, поэтому таблиц будет всего две: в первой храним статьи, во второй - категории. Как пользоваться аксессом – читайте книжки. Теорию реляционных баз данных ищите в них же. Собственно вот моя структура:
Pages id Name idCategory Content
счетчик текстовое 255 целое memo
ключевое поле название статьи категория статьи (внешний ключ) Содержит весь исходный текст html-файла
tb_Categories id Name idParent
целое (!) текстовое 255 целое
ключевое поле название категории родительская категория
Обратите внимание, что ключевое поле категории не является счетчиком! Почему так – будет объяснено ниже.
http://www.vr-online.ru
3
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Не понимаете, зачем нужен родитель и что это вообще такое? Да вот:
Как видно, родителем у категории «Dot Net» будет «Программа и Интерфейс». Одной из особенностью нашей программы будет использование неограниченных вложенностей категорий, в отличии от DW, где их всего две (почитайте про бинарные деревья). Импорт статей Это один из главнейших вопросов. Загрузить все статьи целиком не представляет сложности, но разбить их по категориям… Ну уж нет, лучше попытаться найти место, где хранится список связей. Ларчик открывался совсем просто, но начнем по порядку. Заходим в папку base, где лежит очень очень много файлов и ищем menu.list. В этом файле содержится список первых 3000. Открываем с помощью текстового редактора (я делаю это блокнотом). Формат такой: если перед словами нет ни одного отступа (табуляции), то это корневая категория, если 1 отступ – подчиненная первой. Ну а если два, то это уже название самой статьи. А файлик, который с ней связан, идет после разделителя |. Смешно, не так ли? Думаю, теперь вам ничего не мешает написать процедуру импорта, анализируя этот файл. Чтобы помочь в этом деле привожу блок-схему добавления/изменения одной статьи или категории:
http://www.vr-online.ru
4
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Не буду приводить листинг процедуры, чтобы не загромождать текст, а сделаю лишь пару замечаний: - в начале открываем файл menu.list вручную. Естественно, в финальном варианте пользователю ничего такого делать не придется. - предполагается, что открытый файл находится в папки Base проекта DW. - если в базе уже есть категория или статья, то диалога о замене не будет (ради упрощения кода). - переменные idRoot и idCategory служат для хранения последних встретившихся категорий, причем не важно, добавленных или нет. - обратите внимание на переменную CategoryCounter и константу CategoryK. При построении дерева, мы будем использовать запрос, объединяющий таблицы (union), поэтому все id должны быть уникальными. Если для статей поля является счетчиком, то для категорий мы должны высчитывать его вручную. Константа как раз и определяет минимально значение (я взял его побольше, чтобы наверняка исключить совпадение), а в переменной хранится текущее, которое будет присвоено новой записи и тут же увеличено на единицу. - в списке импорта у нескольких статей после имени файла имеется символ табуляции. Вы можете попытаться найти такие статьи и удалить его сами, либо взять уже исправленный мною файл. Помните, что в основном статьи будут загружаться всего один раз, поэтому не стоит относиться к быстродействию процедуры с фанатизмом. Код можно улучшить (например, вместо загрузки всего списка статей читать текстовый файл по строчке с помощью Readln, убрать проверки существования файла, модифицировать алгоритм, использовать вместо метода locate что-нибудь другое и т.д.).
http://www.vr-online.ru
5
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Импорт дополнительных статей Что дальше? А дальше нужно загрузить оставшиеся статьи. Этот список хранится в файле new_articles.txt и имеет уже чуть другой формат: файл статьи | корневая категория | подкатегория | название статьи. Я не буду комментировать только что сказанное: что толку требовать от бесплатной программы? Но теперь понятно, почему вложенность составляет всего 2 категории. В реализации алгоритма ничего сложно нет, единственное замечание: в списке есть новые подкатегории - нужно будет делать дополнительные проверки на существовании их и, если их нет, то добавлять. Заключение Проведя успешно импорт, проверим, насколько он удался. В dw 11 категорий и 197 подкатегорий
У нас также 11 категорий:
И 194 подкатегории:
http://www.vr-online.ru
6
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Куда делись 3 подкатегории? Посмотрите на построенное дерево:
В корневых категориях «Рабочий стол», «Синтаксис» и «Технологии» существуют категории, с аналогичными названиями, поэтому они не добавились. Для решения этой проблемы достаточно подправить либо название корневой категории (только в menu.list), либо имена дочерних (и в menu, и в new_articles). Не буду приводить скриншот (поверьте на слово), но статей оказалось также 5005. К статье я приложил исправленные файлы menu.list, new_articeles.txt, пустую базу данных (с таблицами, но без записей) и исходный код проекта.
http://www.vr-online.ru
7
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Формула Прозрачности или Прозрачная Магия Ни в заумных фолиантах, ни на просторах дикой сети я не нашел достойного свитка, вразумительно и доступно рассказывающего о создании Полупрозрачных Окон. Хотя возможно я плохо искал, или те свитки были скрыты невидимой чарой. В этом манускрипте я постарался подробно рассказать о магии прозрачности окон.
Это знают даже маглы. Все уже наверное знают, что начиная с Delphi 6 формы умеют поддерживать свою прозрачность. Для этого не требуется абсолютно никаких усилий. Достаточно использовать свойства формы: • AlphaBlend := True – включить прозрачность окна; • AlphaBlendValue := 128 (от 0 до 255) – установить степень прозрачности формы. 255 – полная непрозрачность. • TransparentColor := True – включить прозрачный цвет. • TransparentColorValue – значение прозрачного цвета (по умолчанию 0 – clBlack) Уже на этом этапе новичку требуется дать небольшое пояснение. Существуют 2 режима прозрачности: 1. По цветовому ключу (TransparentColor). То есть какой-то определенный цвет обозначается как прозрачный (TransparentColorValue). Пиксель с этим цветом не прорисовывается, а вместо него появляется пиксел расположенный «под» формой. В форме появится прозрачное окошко. Окошко будет абсолютно прозрачное, то есть через него спокойно можно кликать, например, по иконкам на рабочем столе! 2. Более продвинутый (AlphaBlend). Здесь происходит смешивание (blending) цвета пикселя формы и пикселя расположенного «под» формой с учетом установленной степени непрозрачности формы (AlphaBlendValue).
Оба эти вместе.
режима
вполне
можно
использовать
В этом манускрипте рассмотрен в основном второй способ, хотя и для первого тоже нашлось немного места.
http://www.vr-online.ru
8
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Азы: Формула прозрачности Но нам же мало стандартных свойств! Мы хотим сделать красивый виджет, взять за основу картинку и сделать окно невообразимой формы и различной прозрачности. И вот тут мы вступаем на зыбкую почву слабодокументированных, но очень соблазнительных возможностей. Для непосвященного наши действия будут попахивать магией. Хотя абсолютно все будет досконально разжевано и объяснено на пальцах. Для совершения заклинаний нам понадобится волшебник, и даже все маглы уже знают, что самым лучшим волшебником является профессор Дамблдор. Мы воспользуемся виртуальной личиной профессора, любезно им предоставленной. Также им была безвозмездно предоставлена формула прозрачности:
Здесь:
• • • •
ResultColor – суммарный цвет пикселя; SourceColor – цвет источника, накладываемой картинки; DestinationColor – цвет приемника, фона, на который накладываем; Transparency – прозрачность картинки источника. Это значение от 0 до 1. При единице накладываемая картинка совершенно непрозрачна и фона под ней не видно.
Как видишь, формула очень простая. Ты любуйся, а пока про нее расскажу. В цветовой модели RGB, которую мы будем использовать для картинок, цвет пикселя состоит из трех компонентов: красный, зеленый и синий. И для получения суммарного цвета пикселя надо применить эту формулу к каждому цветовому компоненту. Transparency имеет значение от 0 до 1. Но нам по ходу дела будет удобнее использовать для задания прозрачности значения от 0 до 255 (то есть размером в 1 байт). Это связано с тем, что в используемых нами картинках цвет пикселов указан в системе RGB (красный, зеленый, синий) и размер каждого цветового компонента составляет 8 бит или 1 байт. Кроме того мы хотим, чтобы кроме общей прозрачности рисунка мы могли задавать прозрачность каждого пикселя в отдельности. Поэтому Transparency мы будем вычислять вот так:
Alpha – Отдельная прозрачность для каждого пикселя, или альфа-канал, или perpixel прозрачность. Значение от 0 до 255. SCA – Soutrce Common Alpha. Суммарная прозрачность всей картинки. Значение от 0 до 255. Теперь можно вывести универсальную формулу прозрачности:
Уровень «СОВУ»: Перцеголовая чара. Знания СОВУ (Совершенно Обычный Волшебный Уровень) являются базовыми и без них ты не сможешь разобраться ни в уровне ПАУК, ни, тем более, в боевой магии. Как всегда, чтобы в чем-то хорошо разобраться, надо сделать все своими ручками. И
http://www.vr-online.ru
9
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
сейчас мы разберем ручное создание эффекта прозрачности изображения. Кроме весьма полезного опыта, сотворенный нами код будет полезен начинающим кодерам графических редакторов. Итак формула прозрачности у нас уже есть. Сейчас мы с ее помощью для разминки пальцев составим Перцеголовую Чару. Кроме профессора нам для работы потребуется перец, выращенный, конечно, на огороде Хагрида. Заклинание будет заключаться в следующем: на фон нежного пастельного цвета мы сначала наложим полупрозрачного Дамблдора, а затем на получившийся рисунок на место дамблдоровой головы наложим полупрозрачное изображение перца. В приложенном свитке с демонстрацией можно менять степень прозрачности накладываемых изображений.
При наложении мы будем использовать единую прозрачность для всего изображения (без чресписксельной прозрачности). Поэтому формула немного упростится:
Образы волшебника, перца и фон поместим на форму в виде TImage. Изображения у нас окружены яркими цветами. Эти цвета нам нужны в качестве прозрачных ключей, чтобы при наложении убрать фон с исходных картинок. То есть мы применим сразу оба типа прозрачности – и по цветовому ключу, и альфа-смешивание. Вот основная функция смешивания изображений: procedure BlendImage(X,Y: Integer; SourceImage, DestinationImage: TImage; Alpha: Byte; TransparentColor: TColor); var xx, yy: Integer; Transparency: Byte; begin for xx := 0 to SourceImage.Width - 1 do for yy := 0 to SourceImage.Height - 1 do begin // Устанавливаем полную прозрачность для фона if SourceImage.Canvas.Pixels[xx,yy] = TransparentColor then Transparency := 0
http://www.vr-online.ru
1
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
else Transparency := Alpha; // Проводим смешивание DestinationImage.Canvas.Pixels[xx + X, yy + Y] := BlendColor(SourceImage.Canvas.Pixels[xx,yy], DestinationImage.Canvas.Pixels[xx + X, yy + Y], Transparency); end; end; В функцию передаем рисунок-источник и рисунок, на который будем накладывать изображение. X и Y – координаты верхнего левого угла рисункаисточника на целевом рисунке. CommonAlpha – общая прозрачность рисунка (0..255, при 255 – непрозрачный рисунок). TransparentColor – прозрачный цвет. Используем его, чтобы убрать фон с рисунка источника. Доступ к пикселям рисунков осуществляем с помощью свойства Canvas.Pixels[]. Обходим в цикле все точки рисунка-источника, получаем их цвет. Если цвет соответствует TransparentColor, то устанавливаем полную прозрачность (Transparency) пикселя, иначе она равна CommonAlpha. Дальше проводим смешивание цветов картинок и устанавливаем новый цвет пикселя. Смешивание проводит функция BlendColor: function BlendColor(SColor, DColor: TColor; CommonAlpha: Byte): TColor; var r, g, b: Byte; begin r := Round((GetRValue(SColor) * CommonAlpha / 256) + GetRValue(DColor) * (1 - CommonAlpha /256)); g := Round((GetGValue(SColor) * CommonAlpha / 256) + GetGValue(DColor) * (1 - CommonAlpha/256)); b := Round((GetBValue(SColor) * CommonAlpha / 256) + GetBValue(DColor) * (1 - CommonAlpha /256)); Result := RGB(r,g,b); end; Она получает два цвета и смешивает их с учетом прозрачности. Собственно тут и применяется формула прозрачности. Поочередно вызовем функцию BlendImage для Дамблдора и перца. Получился перцеголовый Дамлдор. Все! Заклинание готово. Подробности реализации смотри в приложенном демонстрационном свитке. Сразу надо отметить, что свойство канвы Pixels[] использует функции Windows API – GetPixel и SetPixel. Но эти функции работают просто кошмарно медленно. Перейдя на новый уровень, ты узнаешь как существенно оптимизировать код заклинания и заставить его работать намного быстрее. Уровень «ПАУК»: Оптимизация и Плащ-невидимка. Уровень ПАУК (Пресложная Аттестация Умений Колдуна) предполагает доскональное знание изучаемого предмета и готовность к реальному применению знаний. Как уже говорилось функции GetPixel, SetPixel и использующее их свойство канвы Pixels[] работают бессовестно медленно. А в функции BlendImage свойство Pixels используется 4 раза. Если ты захочешь сделать так, чтобы перец на голове проявлялся постепенно и будешь в цикле рисовать его с постепенно увеличивающейся непрозрачностью, то можешь и не дождаться окончания процесса. Срочно необходима оптимизация! Выкинем API функции GetPixel и SetPixel и напишем свои быстрые. Для этого будем получать цвет пикселя непосредственно из области памяти, в которую записано изображение. К счастью разработчики Delphi предусмотрели такой вариант. Объект TBitmap имеет свойство Scanline[] – массив указателей на начала строк пикселей в
http://www.vr-online.ru
1
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
изображении. Зная объем памяти занимаемый одним пикселем можно легко вычислить указатель на пиксель, прибавив к указателю на строку (это координата Y) положение пикселя в строке (координата X) умноженное на объем памяти, занимаемый одним пикселем (для 24-битной картинки 24/8 = 3 байта). Для доступа к цветовым компонентам в системе RGB используем структуру TRGBTriple (описана в Windows.pas). // Быстрое получение цвета пикселя. Только для 24-битных картинок! // Нет проверки на ошибки ! function GetPixel(Bitmap: TBitmap; X,Y: Integer): TColor; var pixel: PRGBTriple; begin if Bitmap.PixelFormat = pf24bit then begin DWORD(pixel) := DWORD(Bitmap.ScanLine[Y]) + (X * SizeOf(TRGBTriple)); Result := RGB(pixel.rgbtRed, pixel.rgbtGreen, pixel.rgbtBlue); end else Result := 0; end; // Быстрая установка цвета пикселя. Только для 24-битных картинок! // Нет проверки на ошибки ! procedure SetPixel(Bitmap: TBitmap; X,Y: Integer; Color: TColor); var pixel: PRGBTriple; begin if Bitmap.PixelFormat = pf24bit then begin DWORD(pixel) := DWORD(Bitmap.ScanLine[Y]) + (X * SizeOf(TRGBTriple)); pixel.rgbtRed := GetRValue(Color); pixel.rgbtGreen := GetGValue(Color); pixel.rgbtBlue := GetBValue(Color); end; end; Кроме того, можно оптимизировать и функцию смешивания цветов BlendColor. Для этого надо преобразовать формулу так, чтобы убрать все математические операции с плавающей запятой. Учтем также, что операция X shr 8 эквивалентна X / 256, но выполняется быстрее:
function FastBlendColor(Src, Dest: TColor; CommonAlpha: Byte): TColor; begin Result := RGB( GetRValue(Dest)+(GetRValue(Src)-GetRValue(Dest))*CommonAlpha shr 8, GetGValue(Dest)+(GetGValue(Src)-GetGValue(Dest))*CommonAlpha shr 8, GetBValue(Dest)+(GetBValue(Src)-GetBValue(Dest))*CommonAlpha shr 8); end; В результате мы получаем увеличение производительности более, чем в 5 раз! И, как увидим дальше, это далеко не предел.
Теперь о плаще-невидимке. Мне не удалось достать хорошего работающего плаща для проведения наших опытов, но кое-что у меня все-таки есть. Как-то, бродя
http://www.vr-online.ru
1
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
по задворкам Хогварца, я обнаружил на куче мусора старый потрепанный плащневидимку. Он не работал. Вернее работал наполовину и добиться полной невидимости в нем не получалось. Наверное поэтому его и выбросили. При осмотре плаща я обнаружил причину поломки. Как раз в области оптического процессора прямо сквозь цепи фотонных нанодатчиков была сделана довольно корявая вышивка: «Harry Potter». Очевидно старый владелец этой вещи, абсолютно не разбирался в ее работе. Ну что же, приспособим это барахло для наших нужд. Заставим Дамблдора одеть этот плащ и постепенно появляться перед нами.
Плащ-невидимка представляет собой 8-битную серую картинку. Размер каждого пикселя как раз представляет собой значение от 0 до 255. Значения цвета пикселей плаща будут определять прозрачность каждого пикселя на изображении волшебника. То есть плащ будет альфа-каналом картинки волшебника. В этом примере сразу будем использовать быстрый доступ к пикселям изображения с использованием свойства TBitmap.Scanline[]. Установку значений цветовых компонентов будем делать по полной формуле прозрачности, но оптимизируем ее, чтобы не было операций с плавающей точкой:
Код функции откомментирован построчно, поэтому объяснения будут лишними. Смотри код и читай комментарии. procedure FastBlendImage(Src, Dst, Map: TBitmap; SCA: Byte; X,Y: Integer); var xx, yy: Integer;// Счетчики циклов SrcBase, MapBase, DstBase: Pointer; // Указатели на строки пикселей SrcInc, MapInc, DstInc: Integer; // Длины строк пикселей SrcPixel, DstPixel: PRGBTriple; // Указатели на 24-битные пиксели Alpha: PByte; // Указатель на 8-битный пиксель Transparency: Byte; // Per-pixel прозрачность. 255 = непрозрачность begin // Получим указатели на начало первой строки во всех картинках SrcBase := Src.ScanLine[0]; MapBase := Map.ScanLine[0]; DstBase := Dst.ScanLine[Y]; // Первой строкой будет Y-строка // Получим длину строки каждой картинки в байтах. Для этого // найдем разницу между указателями на 1 и вторую строки SrcInc := Integer(Src.ScanLine[1]) - Integer(Src.ScanLine[0]); MapInc := Integer(Map.ScanLine[1]) - Integer(Map.ScanLine[0]); DstInc := Integer(Dst.ScanLine[1]) - Integer(Dst.ScanLine[0]); // цикл yy перебирает все строки в картинке источнике
http://www.vr-online.ru
1
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
for yy := 0 to Src.Height - 1 do begin // Получаем указатели на первый пиксель в текущей строке DWORD(SrcPixel) := DWORD(SrcBase); Alpha := MapBase; DWORD(DstPixel) := DWORD(DstBase) + (X * SizeOf(TRGBTriple)); // цикл xx перебирает все пиксели текущей строки картинки источника for xx := 0 to Src.Width - 1 do begin // Получаем значение прозрачности из плаща-невидимки Transparency := Byte(Alpha^); // Смешиваем цветовые компоненты по полной формуле прозрачности DstPixel.rgbtRed := DstPixel.rgbtRed + (SrcPixel.rgbtRed - DstPixel.rgbtRed) * Transparency * SCA shr 16; DstPixel.rgbtGreen := DstPixel.rgbtGreen + (SrcPixel.rgbtGreen - DstPixel.rgbtGreen) * Transparency * SCA shr 16; DstPixel.rgbtBlue := DstPixel.rgbtBlue + (SrcPixel.rgbtBlue - DstPixel.rgbtBlue) * Transparency * SCA shr 16; // Получаем указатели на следующие пиксели в текущей строке, // увеличивая указатель на пиксель на размер пикселя DWORD(SrcPixel) := DWORD(SrcPixel) + SizeOf(TRGBTriple); DWORD(DstPixel) := DWORD(DstPixel) + SizeOf(TRGBTriple); DWORD(Alpha) := DWORD(Alpha) + SizeOf(Byte); end; // Получаем указаетель на следующую строку, увеличивая указатель // на строку на вычисленый ранее размер строки. DWORD(SrcBase) := DWORD(SrcBase) + SrcInc; DWORD(MapBase) := DWORD(MapBase) + MapInc; DWORD(DstBase) := DWORD(DstBase) + DstInc; end; end; Эта процедура работает очень быстро. В демонстрационном свитке для сравнения используются все рассмотренные методы доступа к пикселям. Так вот эта процедура быстрее доступа с помощью новых GetPixel и SetPixel в 8 раз. А при сравнению с использованием свойства Canvas.Pixels[] производительность возрастает в ≈56 раз !! Теперь ты готов к реальному применению заклинаний прозрачности и можешь переходить к боевой магии. Боевая магия: Джинн из файла. Ничего особенно воинственного мы тут делать не собираемся. Просто сейчас мы применим наши знания на практике, чтобы поразить окружающим красивыми визуальными эффектами. Выпустим джинна из файла! Для приготовления джинна нам пригодится уже знакомый виртуальный образ Дамблдора, а также разработанный на предыдущем уровне плащ-невидимка. Если запустить программу из ярлыка на рабочем столе или из Explorer’а, то джинн плавненько появится прямо из файла. Предполагается, что взявшись за реализацию боевого заклинания, ты имеешь представление, что из себя представляет окно и контекст устройства.
http://www.vr-online.ru
1
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Этот джинн является полноценным окном. Его можно таскать по столу мышкой и заставить исчезнуть по двойному клику. Он даже сможет исполнять твои желания. Правда для этого ты должен сам четко объяснить ему, что ты хочешь. То есть остается собственно написать функции, которые этот джинн будет выполнять. ^_^ Итак, поехали. Джинна мы сделаем из простой формы путем довольно сложной трансфигурации. Перво-наперво обыкновенное окно надо превратить в многослойное. Это делается путем добавления к стилю окна флага WS_EX_LAYERED. SetWindowLong(Handle, GWL_EXSTYLE, GetWindowLong(Handle, GWL_EXSTYLE) or WS_EX_LAYERED); Дальше с многослойным окном можно собственно проводить трансфигурацию. Для этого применяется заклинание UpdateLayeredWindow. В Windows Vista добавлено еще одно заклинание – UpdateLayeredWindowIndirect (вызывается похоже) function UpdateLayeredWindow( Handle: THandle; //Дескриптор нашего многослойного окна, то есть HWND. hdcDest: HDC; //Контекст устройства на которое будет проецироваться //окно. То есть контекст экрана – GetDC(0); pptDst: PPoint; //Новая позиция окна на экране. Верхний левый угол. _psize: PSize; //Новый размер окна. hdcSrc: HDC; //Контекст с рисунком нового окна. Об этом отдельно. pptSrc: PPoint; //Левый верхний угол выводимого рисунка в hdcSrc. crKey: COLORREF; //Прозрачный цвет. Мы не используем. Будет равен 0. pblend: PBLENDFUNCTION; //Указатель на структуру с параметрами смешивания dwFlags: DWORD // Одно из следующих значений: //1. ULW_ALPHA – альфа смешивание, используется // BLENDFUNCTION – наш выбор; //2. ULW_COLORKEY – используется цветовой ключ crKey; //3. ULW_OPAQUE – непрозрачное окно. ): Boolean; // Если получится - возвратится True. BLENDFUNCTION = packed record BlendOp: BYTE; // Вид операции смешивания. Пока документирован // только один - AC_SRC_OVER. BlendFlags: BYTE; //Не используется. Должен быть равен нулю. SourceConstantAlpha: BYTE;// Общая прозрачность рисунка-источника. // 0 – полная прозрачность, // 255 – полная непрозрачность. AlphaFormat: BYTE; //Определяет, как интерпретируются целевой рисунок //и рисунок-источник. В настоящий момент //документирован только AC_SRC_ALPHA(означает, что //у рисунка-источника есть альфа-канал), хотя есть //еще AC_SRC_NO_PREMULT_ALPHA, AC_SRC_NO_ALPHA, // AC_DST_NO_PREMULT_ALPHA, AC_DST_NO_ALPHA. end;
http://www.vr-online.ru
1
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
В основном проблем с параметрами тут нет, кроме одного - hdcSrc: HDC; Это должен быть контекст устройства вывода, совместимый в нашем случае с экраном, и в него должна быть выбрана картинка, которую мы хотим видеть на месте нашего окна. Для того чтобы использовать per-pixel прозрачность эта картинка должна быть 32битной. То есть должна содержать альфа-канал. Из всех форматов изображений наиболее универсально поддерживает альфа-канал формат PNG. И в основном именно его для этой цели и используют. Для его загрузки можно использовать сторонние библиотеки (например PNGlib) или воспользоваться мощностями GDI+ (Пример можешь глянуть на моей страничке ). Но мы не пойдем этим путем и создадим 32-битную картинку вручную внутри стандартного TBitmap. В этот нам опять поможет наш любимый профессор. У нас есть 24-битная картинка с образом Дамблдора и 8-битная серая картинка с плащомневидимкой. 24+8=32. Нам остается только свести эти картинки вместе в одну 32битную картинку. Есть один очень важный момент. Windows при создании полупрозрачного окна руководствуется той же самой формулой прозрачности, что и мы в своих примерах, но одно из действий она не выполняет! Вот это:
Это действие надо сделать самому, перед тем как передать картинку в функцию. То есть мы должны во время соединения волшебника и плаща цветовые компоненты всех пикселей Дамблдора умножить на значение альфа-канала (плащаневидимки) и разделить на 256. Вот такой подводный камень. Он перегородил дорогу многим, пытавшимся создать Полупрозрачное Окно. Если ты будешь часто менять изображение в окне, то тебе придется каждый раз заново обрабатывать пиксели изображения альфа-каналом. Поэтому эту операцию надо оптимизировать еще жестче. Как всегда, чем быстрее работает алгоритм, тем больше он требует памяти. Это я к тому, что мы может рассчитать все возможные значения пикселя только один раз и поместить их в таблицу (PreMultiplyTable), а потом не выполнять никаких вычислений, а просто брать данные из таблицы. Оптимизации нет предела! При загрузке программы будем заполнять таблицу значениями: var .. PreMultiplyTable: array [0..255,0..255] of Byte; procedure InitPreMultiplyTable; var Alpha, Color: Byte; begin for Alpha := 0 to 255 do for Color := 0 to 255 do PreMultiplyTable[Alpha,Color] := Alpha * Color shr 8; end; Размер таблицы PreMultiplyTable составляет 256*256=65536 байт. То есть за большую скорость мы расплатились 64 Кб памяти. Процедура сведения картинок выполняется по быстрому алгоритму и очень похожа на то, что мы делали на уровне ПАУК. Доступ к цветовым компонентам 32битной картинки делаем с помощью структуры TRGBQuad (используем ее поле rgbReserved для альфа-канала). procedure TForm1.FormCreate(Sender: TObject); var src, map: TBitmap; //Для загрузки картинок Дамблдора и плаща
http://www.vr-online.ru
1
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
xx, yy: Integer; // Счетчики циклов SrcBase, MapBase, DstBase: Pointer; // Указатели на строки пикселей SrcInc, MapInc, DstInc: Integer; // Размеры строк пикселей SrcPixel: PRGBTriple; // пиксель 24-битной картинки Alpha: PByte; // пиксель 8-битной картинки DstPixel: PRGBQuad; // пиксель 32-битной картинки begin inherited; // Получаем координаты мыши (TPoint). Из этой точки появится джинн GetCursorPos(mouse); // Инициируем предумноженную таблицу InitPreMultiplyTable; // Загружаем Дамблдора - 24 битный src := TBitmap.Create; src.LoadFromFile('wizard.bmp'); // Загружаем плащ невидимку - 8 битный map := TBitmap.Create; map.LoadFromFile('map.bmp'); // Создаем 32-битный TBitmap размерами как у Дамблдора dst := TBitmap.Create; dst.Width := src.Width; dst.Height := src.Height; dst.PixelFormat := pf32bit; // Получаем указатели на первые строки пикселей картинок SrcBase := Src.ScanLine[0]; MapBase := Map.ScanLine[0]; DstBase := Dst.ScanLine[0]; // Получаем размеры строк пикселей в картинках SrcInc := Integer(Src.ScanLine[1]) - Integer(Src.ScanLine[0]); MapInc := Integer(Map.ScanLine[1]) - Integer(Map.ScanLine[0]); DstInc := Integer(Dst.ScanLine[1]) - Integer(Dst.ScanLine[0]); // Перебираем все строки for yy := 0 to Dst.Height - 1 do begin // Получаем первый пиксель в строке DWORD(SrcPixel) := DWORD(SrcBase); Alpha := MapBase; DWORD(DstPixel) := DWORD(DstBase); // Перебираем все пиксели в строке for xx := 0 to Dst.Width - 1 do begin // Устанавливаем цветовые компоненты 32-битной картинки, по // значениям предумноженной таблицы. DstPixel.rgbRed := PreMultiplyTable[Byte(Alpha^), SrcPixel.rgbtRed]; DstPixel.rgbGreen := PreMultiplyTable[Byte(Alpha^), SrcPixel.rgbtGreen]; DstPixel.rgbBlue := PreMultiplyTable[Byte(Alpha^), SrcPixel.rgbtBlue]; DstPixel.rgbReserved := Byte(Alpha^); // альфа-канал без изменений // Получаем следующий пиксел в картинках DWORD(SrcPixel) := DWORD(SrcPixel) + SizeOf(TRGBTriple); DWORD(DstPixel) := DWORD(DstPixel) + SizeOf(TRGBQuad); DWORD(Alpha) := DWORD(Alpha) + SizeOf(Byte); end; // Получаем следующую строку DWORD(SrcBase) := DWORD(SrcBase) + SrcInc; DWORD(MapBase) := DWORD(MapBase) + MapInc; DWORD(DstBase) := DWORD(DstBase) + DstInc; end; // Освободаем память от ненужных более картинок src.Free; map.Free;
http://www.vr-online.ru
1
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
// Убираем у окна бордюр и шапку. Без этого ничего не выйдет BorderStyle := bsNone; // Прквращаем окно в многослойное. SetWindowLong(Handle, GWL_EXSTYLE, GetWindowLong(Handle, GWL_EXSTYLE) or WS_EX_LAYERED); end; Как видишь весь подготовительный код мы поместили в обработчик создания формы. Так как в моем примере картинка окна не изменяется, то 32-битная картинка создается только один раз и в этом примере использование предумноженной таблицы прироста скорости не дает. Саму трансфигурацию выполним при активации формы. Контекст устройства для заклинания UpdateLayeredWindow получим из самой картинки: TBitmap.Canvas.Handle. Дальше получим координаты мышки на экране (а она будет как раз над файлом в проводнике, ты же нему кликал для запуска примера). И будем показывать постепенно появляющегося джинна, плавно отъезжающего от мышки, то есть от файла. Создастся впечатление, что джинн появился прямо из файла. Вот так вот :) Всего лишь ловкость рук. procedure TForm1.FormActivate(Sender: TObject); var screenDC: HDC; // контекст экрана pt1, pt2 : TPoint; // точки для UpdateLayeredWindow sz : TSize; // размер окна для UpdateLayeredWindow bf : TBlendFunction; // структура для UpdateLayeredWindow i: Integer; // счетчик цикла begin // Получаем контекст экрана screenDC := GetWindowDC(GetDesktopWindow); // или GetDC(0); // Крутим цикл for i := 2 to 64 do begin // Вычисляем новое положение окна от координат мыши pt1 := Point(mouse.X + i , mouse.Y + i); pt2 := Point(0,0); // Берем для вывода всю 32-битную картинку sz.cx := dst.Width; // Устанавливаем размеры окна равными sz.cy := dst.Height; // размерам картинки // Заполняем параметры смешивания with bf do begin BlendOp := AC_SRC_OVER; BlendFlags := 0; SourceConstantAlpha := i * 4 - 1; // Меняем общую прозрачность джинная AlphaFormat := AC_SRC_ALPHA; //используем альфа-канал end; // Наконец-то вызываем заклинание. if not UpdateLayeredWindow(Handle, screendc, @pt1, @sz, dst.Canvas.Handle, @pt2,0, @bf,ULW_ALPHA) then ShowMessage('UpdateLayeredWindow: ' + SysErrorMessage(GetLastError)); // Если оно не получилось, то выведется описание ошибки Sleep(1); // Небольшая актерская пауза end; // Освобождаем контекст экрана ReleaseDC(GetDesktopWindow,screenDC); end; Остальной код в примере нужен для того, чтобы таскать джинна по столу за тело. С эти разберешся сам. Конечно надо сделать еще так чтобы джинн был всегда поверх всех окон, чтобы его можно было вызвать горячей клавишей, чтобы он не запускался второй раз, но это уже не относится к теме данного манускрипта. Кстати
http://www.vr-online.ru
1
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
обрати внимание что, хотя окно выводится прямоугольное, прозрачные участки джинна прозрачны также и для кликов мышью. Этот значит, что теперь ты можешь создать окно самой вычурной формы без использования всяких регионов. Это положительные моменты. Но есть тут один очень жирный минус. Используя функцию UpdateLayeredWindow, ты сообщаешь операционной системе о том, что берешь на себя полную ответственность за рисования окна и управление им. Windows будет только брать картинку из контекста, который ты указал в hdcSrc и обеспечивать прозрачность в соответствии с параметрами функции и альфа-каналом рисунка. В твое окно перестанет приходить сообщение WM_PAINT и событие OnPaint перестанет работать. Перерисовывать окно ты будешь должен сам по мере надобности. Для этого снова вызываешь UpdateLayeredWindow и передаешь ей обновленную картинку. И вот тут и возникает проблема с компонентами на форме. Если во время разработки ты поместил на форму элементы управления (типа кнопок или полей ввода), то на новом окне они не прорисуются. Ведь выводится только твоя картинка. Но эти элементы будут работать, реагировать на клики (хотя и не всегда). Наиболее разумным решением этой проблемы будет полный отказ от стандартных элементов управления. Вместо этого надо рисовать элементы управления вручную в виде картинок прямо в контексте устройства, но перед его обработкой альфа-каналом. (применительно к коду примера рисовать элементы надо на картинке волшебника перед сведением картинок в 32-битную) Это все. Теперь ты знаешь заклинание Полупрозрачных Окон и, надеюсь, разобрался с магией прозрачности. Да! Свитки примеров компилировались в Delphi7 и тестировались в WindowsXP. Если что-то непонятно, то пиши, а лучше создавай тему на форуме vr-online. Вот тебе дополнительные материалы по теме и для дальнейшего развития:
• • •
Тема на форуме vr-online. Можешь постить сюда свои вопросы. UpdateLayeredWindow – русское описание Многослойные окна – русский перевод статьи MSDN. Видно, что
• • •
«Прозрачность - это просто» В.Брусенцев. RSDN. HOWTO: Platform Independent Alpha Blending
• • • •
переводчик был автоматическим.
Красивые примеры на ассемблере. Спасибо Hamper’у за наводку. http://chime.student.utwente.nl/qweerdy/ppa.zip http://chime.student.utwente.nl/qweerdy/scrollblend.zip http://chime.student.utwente.nl/qweerdy/pngskin3.zip http://chime.student.utwente.nl/qweerdy/pngskin4.zip
http://chime.student.utwente.nl/qweerdy/pngskin5.zip PNGlib для этих примеров Модуль AlphaEffects (Delphi) – создание прозрачных эффектов появления и исчезновения формы.
http://forum.vingrad.ru/topic-100336/unread1/hl/updatelayeredwindow/index.html http://www.rsdn.ru/Forum/?mid=1461922 http://www.rsdn.ru/Forum/?mid=657236 Орехов Роман also known as tripsin © 2006 tripsin@yandex.ru
http://www.vr-online.ru
1
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Приручение мыши. Имитация ввода пользователя. Почему-то очень часто на форуме возникает вопрос: «Как мне программно кликнуть мышкой?» или «Как мне программно нажать на кнопку в другом приложении?» Пора положить этому конец! :) Здесь мы подробно разберем 2 способа. У каждого есть свои достоинства и недостатки. Какой удобнее, такой и пользуй. 1 Способ. Посылка сообщений. Первое, что приходит на ум при решении этой задачи, это найти хэндл нужного окно послать ему сообщения: WM_LBUTTONDOWN и WM_LBUTTONUP. Если ты нажимаешь на кнопку в своем собственном приложении, то это может выглядеть так: procedure TForm1.Button2MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if Button = mbLeft then begin // Жмем левую кнопку SendMessage(button1.Handle, WM_LBUTTONDOWN, 0,0); Sleep(1000); // Application.ProcessMessages; SendMessage(button1.Handle, WM_LBUTTONUP, 0,0); end; if Button = mbRight then begin // Жмем правую кнопку SendMessage(button1.Handle, WM_RBUTTONDOWN, 0,0); Sleep(1000); SendMessage(button1.Handle, WM_RBUTTONUP, 0,0); end; end; Здесь при нажатии на Button2 имитируется нажатие на Button1, причем нажатия на левую и правую кнопки мыши обрабатываются отдельно. Sleep(1000) – вставлено, чтобы ты лучше увидел, как кнопочка нажимается. Для посылки сообщения используется API-функция SendMessage. В 1 параметре передается хэндл окна, в которое посылаем сообщение (кнопка ведь разновидность окна). Если вместе хэндла поставить HWND_BROADCAST, то сообщение придет всем окнам верхнего уровня. Дочерние окна (к которым относятся и кнопки) сообщения не получат. Так что нажать сразу на все кнопки на экране не получится. :) Во 2 параметре – собственно номер сообщения. Кроме указанных сообщений можно послать еще: WM_LBUTTONDBLCLK, WM_RBUTTONDBLCLK, WM_MBUTTONDBLCLK, WM_MOUSEWHEEL, WM_XBUTTONDOWN, WM_XBUTTONUP, WM_XBUTTONDBLCLK. Думаю назначение их понятно из названий. Вместо нуля в 3 параметре (wParam) можно передать информацию о дополнительно нажатых кнопках. Это может быть сочетание (через or) следующих флагов: • • • • •
MK_LBUTTON = 1 – Левая кнопка мыши. MK_RBUTTON = 2 – Правая кнопка мыши. MK_SHIFT = 4 – Собственно нажат SHIFT MK_CONTROL = 8 – Нажата кнопка CTRL MK_MBUTTON = 16 – Средняя кнопка мыши.
Последние 2 флага действуют только в последних версия винды: Windows 2000/XP, ну и наверное в Vista :)
http://www.vr-online.ru
2
VR-online Journal (Фленов Михаил & VR-Group) • •
Для программистов №15
MK_XBUTTON1 = 32 – Первая дополнительная кнопка. MK_XBUTTON2 = 64 – Вторая дополнительная кнопка.
В 4 параметре (lParam) передаются координаты клика в виде примерно структуры TSmallPoint. После того, как оконная функция программы обработает сообщение, SendMessage возвращает 0. Чтобы не ждать конца обработки сообщения, можно просто поместить сообщение в очередь с помощью PostMessage (она возвращает управление сразу). Параметры у нее те же. Впрочем, в собственной программе можно просто вызвать обработчик нажатия кнопки напрямую, но тогда ты не увидишь, как нажимается кнопочка. С другими приложениями может возникнуть проблема с поиском окошка. Во-первых, надо вообще знать какое конкретно окно тебе нужно. Искать окна можно с помощью EnumWindows, FindWindow, FindWindowEx.
2 Способ. Помещение информации непосредственно во входной буфер мыши. Мы имеем возможность воздействовать непосредственно на мышь и симулировать любые ее события. При этом нам не надо будет искать окна. Мы сможем воздействовать на любое приложение, двигать мышью, кликать иконки на экране, т.е. все, что может мышь сделать, мы можем сымитировать. Обычно для этого используется функция: mouse_event. (Ее описание ты сможешь найти в различных хелпах, например DRKB) Но в NT/2000/XP-системах MSDN рекомендует использовать новую функцию SendInput (хотя она появилась еще в Windows 98). Ее-то мы и рассмотрим. Кроме мыши эта функция может симулировать клавиатуру и другие устройства ввода. Вообще довольно трудно придумать мирное применение имитации нажатия клавиш и кнопок мыши. Кроме шуточных приколов, я думаю, эта тема может пригодиться в программе удаленного визуального администрирования (типа Remote Administrator). Приведенное ниже описание фактически адаптированный для Delphi перевод MSDN. Синтаксис. Для Delphi определен в Windows.pas (все остальные структуры там же): function SendInput(cInputs: UINT;var pInputs: TInput;cbSize: Integer): UINT; stdcall; Параметры: 1. cInputs - Устанавливает число структур TInput, передаваемых в массиве во второй параметр. 2. pInputs - Массив структур TInput. Описание смотри ниже. За один вызов функции можно создать много событий и их последовательность заносится в этот массив. Вообще сюда передается указатель на этот массив, если вызывать функию по нормальному на Си, поэтому в качестве указателя надо указать первый элемент массива. (Разработчики Delphi почему-то решили, что так удобнее.) Каждая структура обозначает событие, которое будет вставлено во входной буфер клавиатуры или мыши. То есть можно сразу управлять и клавой и мышкой. 3. cbSize - Размер структуры TInput в байтах. Если размер будет указан неправильно, то функция завершится с ошибкой. Функция возвращает число событий, которые она успешно вставило во входной поток мыши или клавиатуры. Если функция возвращает нуль, значит ввод данных был
http://www.vr-online.ru
2
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
блокирован другим потоком. Чтобы получить дополнительные сведения об ошибке, вызывай функцию GetLastError. Функция SendInput вставляет события из массива структур TInput последовательно во входной поток мыши или клавиатуры. Эти события не смешиваются с событиями ввода данных от пользователя (когда он юзает клавиатуру или мышь), вызовами функций keybd_event, mouse_event, и другими вызовами SendInput. Эта функция не сбрасывает текущее состояние клавиатуры. Любые кнопки, которые уже нажаты, когда функция вызывается, останутся нажатыми. Поэтому, чтобы избежать неприятностей при работе с клавиатурой, проверяй состояние клавиатуры при помощи функции GetAsyncKeyState и по мере необходимости корректируй свои действия.
Структура TInput передается в функцию SendInput, и содержит информацию для синтезирования событий ввода типа нажатий клавиши, перемещения и нажатия кнопок мыши. Она является как-бы контейнером для трех других структур. В Си это называется объединение (union), т.е. используется одна из этих структур Объявление tagINPUT = packed record Itype: DWORD; case Integer of 0: (mi: TMouseInput); 1: (ki: TKeybdInput); 2: (hi: THardwareInput); end; TInput = tagINPUT; Члены структуры Itype - Устанавливает тип события ввода и какая из трех структур будет использоваться Этот член может быть одним из нижеследующих значений. • • •
INPUT_MOUSE = 0 - Это событие мыши. Используй структуру mi объединения. INPUT_KEYBOARD = 1 - Это событие клавиатуры. Используй структуру ki объединения. Ее можно использовать, что реализовать устройство речевого вода текста или рукописного ввода, когда клавиатуры вообще нет. INPUT_HARDWARE = 2 – В Windows 95/98/Me: Это событие аппаратного ввода данных, а не клавиатуры или мыши. Используйте структуру hi объединения.
Понятно, что мы можем создавать события мыши, клавиатуры и других устройств ввода, как и говорилось выше.
Так как мы решили приручить мышь, то рассмотрим только структуру TMouseInput. Структура TMouseInput содержит информацию об имитируемом событии нажатия кнопки мыши и используется с объединении структуры TInput. Эта структура содержит информацию идентичную той, которая используется в списке параметров функции mouse_event. Объявление. tagMOUSEINPUT = packed record dx: Longint;
http://www.vr-online.ru
2
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
dy: Longint; mouseData: DWORD; dwFlags: DWORD; time: DWORD; dwExtraInfo: DWORD; end; TMouseInput = tagMOUSEINPUT; Члены структуры dx, dy – Используются когда установлен флаг MOUSEEVENTF_MOVE, т.е. если ты хочешь, чтобы мышь переместилась. Устанавливают абсолютные или относительные координаты мыши в зависимости от флага MOUSEEVENTF_ABSOLUTE. Если он не установлен, то координаты определяют смещение от прежней позиции мыши. Смещение вводится не в пикселах, а непонятно в чем. Я честно говоря не понял. Смещал на 100 ед. – первый раз курсор сместился на 170 пикселов, а второй на 254. :( Положительные значения обозначают смещение вправо и вниз, отрицательные – влево и вверх. Если MOUSEEVENTF_ABSOLUTE установлен, поля dx и dy содержат нормализованные абсолютные координаты между 0 и 65 535. Координата (0,0) обозначает левый верхний угол экрана; координата (65535,65535) обозначает нижний правый угол. Преобразовать координаты из пикселов в нормализованные ты можешь примерно так: normalized_x := x_in_pixels * 65535 div screen_width; Во много экранной системе эти координаты относятся к главному монитору. Если хочешь использовать весь виртуальный рабочий стол (т.е. несколько мониторов), то установи флаг MOUSEEVENTF_VIRTUALDESK (это только в Windows 2000/XP). mouseData – Передает данные для дополнительных кнопок и колесика. Значение этого члена опять зависит от флагов установленных в параметре dwFlags: •
•
• •
MOUSEEVENTF_WHEEL - в mouseData устанавливается величина перемещения колесика мыши. Положительное значение обозначает, что колесо вращается от себя; отрицательное - что колесо вращалось к себе. Один щелчок колеса при прокрутке определяется как WHEEL_DELTA, который равен 120. MOUSEEVENTF_XDOWN или MOUSEEVENTF_XUP - параметр mouseData устанавливает, какие дополнительные (X) кнопки были нажаты или отпущены. Это значение может быть любой комбинацией из XBUTTON1 (первая X-кнопка) и XBUTTON2(вторая X-кнопка). Если флаги MOUSEEVENTF_WHEEL, MOUSEEVENTF_XDOWN и MOUSEEVENTF_XUP не установлены, то параметр mouseData должен равняться нулю. Устанавливать сочетание флагов MOUSEEVENTF_WHEEL, MOUSEEVENTF_XDOWN или MOUSEEVENTF_XUP нельзя.
dwFlags – В этом поле устанавливается набор битовых флажков, которые определяют различные виды движений и щелчков мышки и содержимое остальных полей структуры. Биты в этом поле могут быть любой разумной комбинацией нижеследующих значений. Не надо устанавливать состояние кнопок мыши каждый раз при перемещении. Есть при движении мыши левая кнопка должна быть нажата, то надо один раз установить MOUSEEVENTF_LEFTDOWN перед движением, потом двигать мышой сколько надо и в конце отжать левую кнопку (MOUSEEVENTF_LEFTUP). Перечислю эти флаги. Часть из них уже была описана выше.
http://www.vr-online.ru
2
VR-online Journal (Фленов Михаил & VR-Group) • • • • • • • • •
Для программистов №15
MOUSEEVENTF_ABSOLUTE – в dx и dy абсолютные координаты. MOUSEEVENTF_MOVE – Перемещение мыши. MOUSEEVENTF_LEFTDOWN – Нажата левая кнопка. MOUSEEVENTF_LEFTUP – Отпущена левая кнопка. MOUSEEVENTF_RIGHTDOWN – Нажата правая кнопка. MOUSEEVENTF_RIGHTUP – Отпущена правая кнопка. MOUSEEVENTF_MIDDLEDOWN – Нажата средняя кнопка. MOUSEEVENTF_MIDDLEUP – Отпущена средняя кнопка. MOUSEEVENTF_VIRTUALDESK - Должен использоваться с только с флагом MOUSEEVENTF_ABSOLUTE. Координаты распространяются на весь виртуальный рабочий стол, а не только на главный монитор.
Следующие флаги есть только в новых версия виндов (>=Windows 2000/XP) • • •
MOUSEEVENTF_WHEEL – Движения колесика мыши. Величина движения в mouseData. MOUSEEVENTF_XDOWN – Нажата дополнительная кнопка. Какая кнопка – определяет mouseData. MOUSEEVENTF_XUP – Отпущена дополнительная кнопка. Какая кнопка – определяет mouseData.
time - Отметка времени события, в миллисекундах. Если этот параметр = 0, система поставит свою собственную отметку времени. Практической пользу от него я не для себя не нашел. Кто знает - пишите. Как я понял это время передается в структуру TMsg при вызове GetMessage в цикле обработки сообщений. dwExtraInfo - Устанавливает дополнительное значение, связанное с событием нажатия кнопки мыши. С помощью функции GetMessageExtraInfo можно получить эту дополнительную информацию. Например, для общения между прогами можно использовать сообщения, но в целях безопасности желательно знать, а от нужной ли проги пришло сообщение? Так вот чтобы отличить поддельное сообщение можно устанавливать в этом параметре какой-нибудь идентификатор сессии (число то есть) Простые программы могут это делать с помошью SetMessageExtraInfo. Относительное движение мыши подчиненно эффектам быстродействия мыши и порогового значения времени для двойного щелчка. Пользователь устанавливает эти значения в свойствах мыши в Панели управления. Ты можешь получать и устанавливать эти значения, используя функцию SystemParametersInfo.
Хватит нудной теории! Сейчас мы забацаем в качестве примера, что-нибудь прикольное. Я обозвал прогу Mad Mouse. Она будет случайным образом перетаскивать объекты на экране: ярлыки, куски текста, файлы в броузере и т.п., что под руку (тьфу, по мышку :) ) попадется. Будет драг_энд_дропать по черному безо всяких там COM`ов и интерфейсов! Будет кликать по всем подряд и левой, и правой, и даблкликом, а еще временами жать Enter. На рабочем столе, да и на винте тоже, очень скоро наступит полный армагеддон (сокращенно – писец). Чем больше ярлыков на рабочем столе, тем интереснее эффект. Кстати прикол довольно жестокий, так что призываю использовать его только в исследовательских и познавательных целях. За использование тобой проги во вредных целях автор отвественности не несет. Также сделаем в программе предохранитель от случайного запуска в виде мессаджбокса с вопросом. А еще прога будет завершаться при нажатии на ESC. От греха подальше, я отлаживал прогу на Windows 98 (в XP тоже все работает) в
http://www.vr-online.ru
2
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
виртуальном компьютере VMvare, чего и тебе советую. Через десять минут рабочий стол превратился вот в это:
А через 20 минут Безумная Мышь установила несколько программ из дисрибутивов и приступила к «поиску и установке нового оборудования». Создавай стандартный проект и удаляй из него нафиг Unit1 с его формой. Начинаем (ш)кодить без графического интерфейса. program madmouse; ... Обойдемся только API-функциями ... uses Windows; ... Объявим переменные. ... var arrI: array [0..2] of TInput; ... Массив для событий ввода. Нам потребуется максимум 4 события на 1 шкод. Шкоды это неделимые единицы пакостных действий. :) ... desk_dc: HDC; desk_wnd: HWND; ... Контекст и хэндл окна экрана нужны, что бы получить его размеры. ... screen_width, screen_height: Integer; ... Размеры экрана
http://www.vr-online.ru
2
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
... screen_width_factor, screen_height_factor: Integer; ... Коэффициенты преобразований для структуры TMouseInput. Читай ее описание. Дальше собственно исполняемый код. Сначала вставим наш предохранитель. ... if MessageBox(0, ‘Одумайся! Ты запускаешь программу Mad Mouse!’ + #13#10 + ‘Если что - жми <ESC>’, ‘Последнее предупреждение’, MB_OKCANCEL or MB_DEFBUTTON2) = IDCANCEL then ExitProcess(0); ... Получим размеры экрана ... begin desk_wnd := GetDesktopWindow; desk_dc := GetWindowDC(desk_wnd); screen_width := GetDeviceCaps(desk_dc,HORZRES); screen_height := GetDeviceCaps(desk_dc,VERTRES); screen_width_factor := 65535 div screen_width; creen_height_factor := 65535 div screen_height; ReleaseDC(desk_wnd, desk_dc); ... Здесь мы получаем хэндл окна экрана. По нему получаем контекст экрана и извлекаем из него размеры. По размерам рассчитываем коэффициенты для преобразований. Дальше проведем подготовительные операции. Обнулим весь массив, чтобы не возиться с вводом ненужных полей. Сразу заполним поля, которые меняться не будут. Инициализируем счетчик случайных чисел. ... ZeroMemory(@arrI, SizeOf(TInput) * 4); arrI[0].Itype := INPUT_MOUSE; arrI[1].Itype := INPUT_MOUSE; arrI[2].Itype := INPUT_MOUSE; arrI[3].Itype := INPUT_MOUSE; Randomize; ... Запускаем основной рабочий цикл. ... repeat arrI[0].mi.dx := Random(screen_width) * screen_width_factor; arrI[0].mi.dy := Random(screen_height) * screen_height_factor; n := 0; case Random(6) of ...
http://www.vr-online.ru
2
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Координаты мышки в первом элементы массива будут нужных в любом случае, поэтому сразу пихаем в них случайные значения. В «n» будет определяться количество задаваемых сразу событий. Дальше случайно выбираем один из шести придуманных нами шкодов, и заполняем нужные поля в структурах для каждого события: ... 0: // Просто двигаем мышу begin n := 1; arrI[0].mi.dwFlags := MOUSEEVENTF_MOVE or MOUSEEVENTF_ABSOLUTE; end; 1: // Левый клик begin n := 2; arrI[0].mi.dwFlags := MOUSEEVENTF_MOVE or MOUSEEVENTF_ABSOLUTE or MOUSEEVENTF_LEFTDOWN; arrI[1].mi.dwFlags := MOUSEEVENTF_LEFTUP; end; 2: // Правый клик begin n := 2; arrI[0].mi.dwFlags := MOUSEEVENTF_MOVE or MOUSEEVENTF_ABSOLUTE or MOUSEEVENTF_RIGHTDOWN; arrI[1].mi.dwFlags := MOUSEEVENTF_RIGHTUP; end; 3: // Двойной клик } begin n := 4; arrI[0].mi.dwFlags := MOUSEEVENTF_MOVE or MOUSEEVENTF_ABSOLUTE or MOUSEEVENTF_LEFTDOWN; arrI[1].mi.dwFlags := MOUSEEVENTF_LEFTUP; arrI[2].mi.dwFlags := MOUSEEVENTF_LEFTDOWN; arrI[3].mi.dwFlags := MOUSEEVENTF_LEFTUP; end; 4: // Drag`n`drop begin n := 3; arrI[0].mi.dwFlags := MOUSEEVENTF_MOVE or MOUSEEVENTF_ABSOLUTE or MOUSEEVENTF_LEFTDOWN; arrI[1].mi.dwFlags := MOUSEEVENTF_MOVE or MOUSEEVENTF_ABSOLUTE; arrI[1].mi.dx := Random(screen_width) * screen_width_factor; arrI[1].mi.dy := Random(screen_height) * screen_height_factor; arrI[2].mi.dwFlags := MOUSEEVENTF_LEFTUP; end; 5: // Нажмем энтер begin PressEnter; end; end; ... Наконец-то, мы передаем весь массив с событиями в функцию SendInput, если n будет больше нуля (n = 0 только в последнем случае, когда нажимаем кнопку <Enter>).
http://www.vr-online.ru
2
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
... if n <> 0 then SendInput(n,arrI[0],SizeOf(TInput)); Sleep(300); ... После запуска событий делаем задержку небольшую, чтобы система могла очухаться, а ты смог ощутить весь ужас происходящего (почти актерская пауза :) ), и затем продолжаем крутить цикл пока кто-нибудь не нажмет на <ESC> ... until GetAsyncKeyState(VK_ESCAPE) <> 0; end. В последнем шкоде я решил нажимать энтер, для получения большего эффекта. Вот код процедуры PressEnter: procedure PressEnter; var K: array [0..1] of TInput; // массив на 2 события: нажать и отжать begin ZeroMemory(@K, SizeOf(TInput)*2); // обнуляем массив K[0].Itype := INPUT_KEYBOARD; // Заполняем поля K[0].ki.wVk := VK_RETURN; // Это код виртуальной клавиши K[1].Itype := INPUT_KEYBOARD; K[1].ki.wVk := VK_RETURN; K[1].ki.dwFlags := KEYEVENTF_KEYUP; // Отжимаем кнопку SendInput(2,K[0],SizeOf(TInput)); // Запускаем события end; Вот и весь код маленькой, но весьма зловредной проги Mad Mouse (исходник прилагается к статье). Можешь сам добавить другие события: иногда жать ESC, крутить колесико мышки, а еще жать CTRL, SHIFT и ALT, но отпускать не сразу, а после других событий, тогда эффект будет еще страшнее. Если надумаешь сделать боевую версию, то прикрути к ней какой-нибудь автозапуск и запуск вредного кода в зависимости, например, от даты. Еще надо предотвращать повторный запуск проги, а то она будет сама себе мешать. Тогда ты получишь настоящую бомбу с часовым механизмом и поразишь своего недруга прямо в прокуренный и пропитанный пивом ... жесткий диск. (C) tripsin, 2006 год.
http://www.vr-online.ru
2
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Delphi 2006 – новая реальность Фленов Михаил aka Horrific http://www.vr-online.ru После выхода Delphi 7 корпорацию Borland ожидали серьезные проблемы, потому что Delphi 8 и 2005 пролетели, как фанера над Парижем. Первую ждал провал из-за того, что .NET ещё не получила достаточную популярность, а вторая версия накрылась медным тазом из-за глючности. Но с появлением Delphi 2006 все становиться на свои места. Это шедевр, который постепенно становиться бестселлером даже в США, где влавствует Visual C++. Некоторые считают, что Delphi 2006 это всего лишь исправленная 2005, но это не так. Если посмотреть на размер Reviewer Guide, который занимает аж 70 страниц, то понимаешь, что перед нами совершенно новый продукт и об этом говорит абсолютно все. Первое, что бросается в глаза после установки Delphi 2006 – это то, что можно запустить среду разработки в одном из трех вариантов: 1. Delphi Developer Studio – включает в себя все возможности, но загружаться будет очень долго. 2. Delphi for Microsoft Win32 – загружается быстрее, но позволяет создавать проекты только для платформы Win 32. 3. Delphi for the Microsoft .NET – не трудно уже догадаться, что грузиться этот вариант будет достаточно быстро, но работать можно будет только с приложениями для .NET. Скорость загрузки Developer Studio - больная тема. Обширные возможности привели к тому, что полный вариант запускается очень долго и возможность загрузить только необходимую версию просто неоценима. Визуальный дизайнер не изменялся уже достаточно долгое время. А действительно, что там можно сделать такого нового, когда среда разработки является самой визуальной! Оказывается можно улучшить позиционирование. Что-то подобное есть уже в Visual Studio, но Borland пошла дальше и сделала все намного круче. Когда мы двигаем компонент по форме или изменяем его размер, то в определенный момент могут появиться тоненькие полоски, соединяющие два или более компонентов. Это указывает на то, что эти компоненты находяться на одной линии (горизонтальной или вертикальной). Если линия синего цвета, то компоненты имеют общую нижнюю или верхнюю границу. Если линия красного цвета, то на одной оси находяться центральные точки компонентов. Таким образом, построение качественных интерфейсов становиться более простой задачей и я надеюсь, что теперь они будут более аккуратными. Почему-то программисты Delphi очень часто бросают компоненты на форму и даже не пытаются их выравнивать.
http://www.vr-online.ru
2
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Как же иногда достает писать эти слова begin и end;. Благодаря Delphi 2006 второе больше писать не придеться. Просто пишешь begin, нажимешь Enter и end появляется автоматически. Первое время меня это даже раздражало, потому что по привычке начинаешь писать слово, которое среда разработки уже поставила. В остальном редактор кода практически не изменился по сравнению с Delphi 2005. Было добалено несколько новых возможностей по рефакторингу кода, улучшена стабильность, исчезли глюки с отображением кода. Ах да, есть ещё одна новая фишка – напротив измененных строк теперь появляются желтые полоски. Таким образом, сразу видно, какие строки в модуле мы сегодня редактировали или добавляли. Если строка изменялась, но уже была сохранена, то полоса изменяет свой цвет на зеленый. Это мелочь, но очень приятная и удобная. Теперь поговорим о невидимом изменении, которое получат все твои программы скомпилированные в Delphi 2006 – это новый менеджер памяти FastMM. Новый менеджер повышает производительность Win32 приложений. Просто перекомпилируй проект в Delphi 2006 и все. Никаких больше телодвижений не нужно. Новый менеджер намного лучше и быстрее освобождает ненужную память. В приложениях .NET такой фишки нет, потому что тут менеджер памяти самой платформы .NET и Borland Delphi повлиять на него практически не может. Работать над большим проектом в одиночку очень сложно, а если проект очень большой, то просто невозможно. А как же работать над одним проектом большому количеству программистов? Просто разделить задачи или модули не получается, потому что задачи очень часто пересекаются, поэтому необходимо использовать специализированные системы. В Delphi 2006 уже встроена StarTeam – система совместной работы над проектом. Конечно же, наличие StarTeam не значит, что ты обязан использовать именно его. Например, я привык использовать более простую Microsift Visual Source Safe.
http://www.vr-online.ru
3
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Минимум памяти Фленов Михаил aka Horrific http://www.vr-online.ru Современный компьютер обладает как минимум 512 метрами памяти (плюс файл подкачки), поэтому программисты не особо заботятся об экономии оперативки. Мы выделяем под собственные нужды тонны ячеек и страниц, не обращая внимания на возможные проблемы. А ведь память не резиновая и может когда-нибудь закончиться. А бывают случаи, когда не хватит памяти даже суперкомпьютера. Если каждый будет так бездарно расходовать содержимое микросхем, то для нормальной работы не хватит и гига оперативки. Даже при наличии 512 мегабайт, расходовать память, не думая о последствиях глупо. Дело в том, что Windows XP в домашней редакции уже съедает от этого объема 128 метров, а профессиональная редакция отнимает все 256. Всякие примочки и побрякушки в районе часов, антивирусы и сетевые экраны могут отнять еще 64 метра. Получается, что для других приложений остается не так уж и много места. Если одновременно будет запущен Delphi 2006, 3DS Max и Photoshop, то работа станет невыносимой, ведь эти монстры сжирают оперативку хуже вирусов. В такие моменты вспоминаются времена MS-DOS с его ограничениями и советские машины (за которыми мне удалось поработать в начале 90-х), когда приходилось экономить каждый байт памяти. Сейчас байты и тем более биты уже никто не считает, а вот кило и мега считать необходимо в любой программе, поэтому данная статья может пригодиться даже тем, кто не собирается писать программы в тяжелых условиях с ограниченным объемом памяти. Необходимо всегда расходовать предоставленные ресурсы разумно. Массивы Самый банальный и бессмысленный расход памяти происходит благодаря использованию массивов фиксированной длины. Допустим, что тебе нужно хранить в памяти заранее неизвестное количество чисел. Как поступить в этом случае? Можно зарезервировать максимально возможный буфер и никаких проблем. Да, проблем никаких, а вот утечка памяти произойдет достаточно серьезная. Допустим, что ты выделил массив из 1024 чисел. Если каждое число типа Integer, то из-под ног утекает 4096 байт памяти. А если тип данных Int64? А если это не число, а структура, размером в 100 байт? Это уже сто килобайт памяти. Десять таких массивов и драгоценный мегабайт уходит в небытие. Простейший вариант решения проблемы – в массиве хранить не сами структуры, а указатели. В этом случае размер массива значительно сокращается, но он все равно не оптимален. Мы резервируем 1024 элемента, а реально в программе может использоваться только один. Идеальное решение – использование динамических массивов. Не стоит бояться динамики, она безобидна по сравнению с Фредди Крюгером. Можно выделить два основных типа динамических массива: 1. Просто выделяем память для хранения элементов массива, а по мере необходимости память расширяется. Данный вариант самый простой и самый компактный, но очень неудобный, когда необходимо изменять последовательность элементов, например, для сортировки, или удалять элементы из середины массива. 2. Можно написать класс, который Простейший вариант такого класса:
http://www.vr-online.ru
будет
управлять
динамическим
массивом.
3
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
type PMassivItem = ^TMassivItem; TMassivItem = record nextItem:PMassivItem; //следующий элемент массива prevItem:PMassivItem; //предыдущий элемент массива Name:String; // пример данных, которые нужно хранить // здесь могут быть еще поля элемента массива end; TMassiv = class public FirstItem:PMassivItem; // первый элемент списка LastItem:PMassivItem; // последний элемент списка itemsNumber:Integer; // количество элементов constructor Create(str:String); // конструктор procedure AddItem(str:String); // добавление элемента procedure DeleteItem(index:Integer); // удаление end; procedure TMassiv.AddItem(str:String); var newItem:PMassivItem; begin newItem:= new(PMassivItem); newItem.Name:=str; newItem.nextItem:=nil; newItem.prevItem:=LastItem; LastItem.nextItem:=newItem; LastItem:=newItem; Inc(itemsNumber); end; constructor TMassiv.Create(str:String); begin itemsNumber:=1; FirstItem:=new(PMassivItem); FirstItem.Name:=str;
http://www.vr-online.ru
3
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
FirstItem.nextItem:=nil; FirstItem.prevItem:=nil; LastItem:=FirstItem; end; procedure TMassiv.DeleteItem(index: Integer); var i:Integer; currentItem:PMassivItem; begin // поиск удаляемого элемента currentItem:=FirstItem; for i:=0 to index-1 do currentItem:=currentItem.nextItem; // наводим новые связи и освобождаем память if currentItem.prevItem<>nil then currentItem.prevItem.nextItem:=currentItem.nextItem; if currentItem.nextItem<>nil then currentItem.nextItem.prevItem:=currentItem.prevItem; Dispose(currentItem);//очистка памяти Dec(itemsNumber);
//уменьшаем счетчик
end;
Данный код упрощен до минимума. В архиве ты найдешь более полный вариант с примером использования. Да, такое хранение данных требует некоторой избыточности для каждого элемента, но зато наш массив полностью динамичен и занимает в памяти ровно столько, сколько необходимо. Ничего лишнего резервировать ненужно. А главное, размерность массива ограничена размером типа данных, который используется для хранения количества элементов. Как вариант, для хранения динамического массива указателей можно использовать класс TList (в данном случае совет относиться к программистам Delphi). Лично я регулярно использую этот класс, потому что он прост и удобен, но иногда пишу свою реализацию динамики, все зависит от задачи. На самом деле алгоритмов создания списков очень много, но я выделяю эти два, как наиболее эффективные и достаточно простые в реализации. В зависимости от задачи
http://www.vr-online.ru
3
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
можно придумать и другой алгоритм, все зависит от того, как хранятся данные, нужно ли их сортировать, есть ли необходимость удалять из середины, куда добавляются новые элементы – в конец или могут вставляться в середину списка и т.д. Допустим, что ты пишешь программу воспроизведения/записи звука или видео. На первый взгляд, для воспроизведение mp3 файла необходимо столько же памяти, сколько данные занимают в файле. Достаточно только загрузить все память и отправить данные звуковой карте. Ошибочка вышла. Не факт, что звуковая карта сможет понять сжатые данные. Придется производить декомпрессию самостоятельно и направлять звуковой карте не сжатые данные. Если разложить по полочкам mp3 файл, то для хранения всех данных может понадобиться 60, а то и все 100 мегабайт памяти. Я думаю, за такое расточительство тебя ни один пользователь не простит. Еще один пример – простой ZIP архиватор. Что если необходимо сжать файл размером в 500 метров? Загрузить его в память, а потом заархивировать его там будет достаточно проблематичным. Во всех этих случаях вполне логичным будет выделить два небольших участка памяти. В первый необходимо загружать данные из файла для последующей манипуляции (компрессии/декомпрессии), а во второй будет помещаться результат работы. Тут нужно быть аккуратным, потому что размеры сжатых и не сжатых данных разные и легко выйти за границу буфера. Не стоит пытаться запихнуть в оперативку все сразу, потому что это бессмысленно. Загружайте всегда данные небольшими порциями. Циклическое использование памяти При работе со звуком Microsoft уже предоставляет нам очень удобные механизмы работы с памятью, которые мы сейчас рассмотрим. Возможно, данный алгоритм ты будешь использовать в своих программах при решении других задач. Итак, MMSystem получает при воспроизведении и возвращает при записи звуковые данные через специализированные буферы. Так как данные загружаются порциями и для предотвращения задержек в воспроизведении/записи музыки, используется очередь из буферов. Для нормальной работы достаточно двух блоков памяти, но можно сделать и больше. Для примера, выделяем память для хранения двух буферов данных. Размер памяти можно выбрать любой, но он должен быть не слишком маленьким, дабы избежать слишком частой смены буферов и не слишком большой, чтобы сэкономить память для других целей. Точный размер подбирается в зависимости от качества звука, потому что от этого зависит и размер необходимых данных. Из личного опыта я всегда выделяю столько, чтобы хватило на воспроизведение/запись не менее 5 и не более 10 секунд звука. Теперь, говорим звуковой карте, что нужно использовать буфер 1, а буфер 2 помещаем в очередь. Так как MMSystem работает в отдельном потоке, ждем, когда буфер 1 освободиться. Когда это произойдет, система переключиться на следующий буфер в очереди (буфер 2), и пока она будет с ним работать, мы освобождаем буфер 1 или заполняем его новыми данными, и снова помещаем в очередь. Таким образом, с помощью цикла можно по частям обработать данные любого размера с минимальными расходами памяти. Два буфера в данном случае минимально необходимы, но желательно использовать 3 или 4. Дело в том, что в момент освобождения буфера, система может быть занята и не успеет его очистить, подготовить и вернуть обратно в очередь. В этом случае может произойти заикание в воспроизведении или даже сбой, если идет запись. Сортировка
http://www.vr-online.ru
3
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Когда данных очень много, то просматривать их все очень сложно и проблематично даже для очень мощного процессора, если памяти не достаточно. Дело в том, что процессор – не самое главное в современном компьютере. Слабым местом по-прежнему остается жесткий диск. Я уже много раз говорил о том, что это механика и, не смотря на то, что производители пытаются из нее выжать максимум, она остается очень слабой. Можно попытаться оптимизировать процесс поиска данных через дефрагментацию, дабы головка жесткого диска не прыгала по блину как угорелая, а читала их последовательно, но затраты в соотношении с результатом слишком большие. Можно читать данные большими кусками, а обработку перенести в отдельный поток, дабы максимально распараллелить задачи, но это тоже не совсем то. Если данных действительно очень много, а памяти очень мало, в бой вступают мозги и алгоритмы. Мы должны реализовать алгоритм так, чтобы не было необходимости просматривать все данные. И в этом нам помогут – сортировка и индексация. Давай посмотрим, как они могут нам помочь. Для начала возьмем сортировку. Когда данные упорядочены, с ними проще работать и искать необходимую информацию. Допустим, что у нас есть список всех жителей города и тебе нужно найти номер телефона Иванова. С такой фамилией в целом городе может быть не один человек, а сотня. Просматривать весь список достаточно проблематично, а если он будет упорядочен по фамилии, то достаточно будет просмотреть все записи на букву "И" и готово. Экономия времени и памяти на лицо. Индексация Поддерживать списки в упорядоченном виде достаточно проблематично, особенно, если записи очень часто изменяются, добавляются, удаляются и при этом каждая из них может иметь произвольный или очень большой размер. В этом случае записи хранят на диске в произвольном виде, а для быстрого поиска используют индексы. Так поступает большинство баз данных, дабы сэкономить память и процессорное время. Индексы – могут состоять из двух колонок: поле, по которому происходит упорядочивание данных и указатель на место, где находятся данные этой записи. Сами данные хранятся в произвольном порядке, а вот индексная табличка сортируется. Так как она очень маленькая и состоит только из двух полей, такую табличку достаточно легко поддерживать в упорядоченном виде, а для хранения ее в памяти нужно намного меньше места, чем для хранения всего списка. Теперь, если нам снова нужно найти номер телефона по фамилии, мы бежим по индексной таблице и ищем необходимую фамилию. Когда она найдена, переходим по указателю из индексной таблицы и получаем данные человека, в том числе его номер телефона и адрес. Индексные таблички хороши, но только в меру. Для телефонного справочника, может понадобиться три индекса: для фамилии, телефона и адреса. Благодаря трем индексам мы можем легко упорядочить данные по любому из этих параметров, но содержать в упорядоченном виде придется уже три таблички, а это уже сложнее. Деревья Индексы – очень мощное средство для экономии памяти и упрощения сортировки. Но для поиска данных все равно приходиться сканировать всю таблицу, пока мы не найдем необходимую запись. Если искомый текст начинается на букву "А", то мы найдем его быстро, ведь он будет находиться в самом начале. Но если искомая строка начинается на "Я", то во время поиска мы придется сканировать до самого конца и экономия скорости будет небольшой. Да, мы все еще экономим память, потому что загружаем в оперативку только индексную таблицу, а не все данные, но скорость все
http://www.vr-online.ru
3
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
еще не высокая. А если построить индекс в виде дерева, то мы сэкономим и место и время. Индексы в виде деревьев очень часто используются в базах данных, в том числе и в MS SQL Server. Так что, понимание этого индекса пригодиться не только программистам (для реализации в собственных проектах), но и администраторам, для лучшего понимания внутренностей баз данных. Индекс не зря называют деревом, потому что все данные в нем располагаются именно в таком виде. Давай рассмотрим, как выглядит дерево в памяти, и ты сразу увидишь все преимущества и недостатки. Я попытался его изобразить графически, но художник из меня не очень хороший (это если мягко выразиться). Во главе всего находиться главный блок, с которого всегда начинается поиск. На рисунке я набросал дерево, состоящее из первых попадавших в мою голову слов. Да, сегодня в моей голове явно бред, пора бы отдохнуть.
Прогулка по дереву Итак, дерево состоит из блоков равного размера. Конечно же, размер блока должен быть достаточен для хранения хотя бы нескольких записей, но он и не должен быть слишком большим. Некоторые специалисты рекомендуют делать его равным 8 кило (мой любимый размерчик!). Не знаю, откуда взялась эта рекомендация, но ей можно найти логическое объяснение. Блок получается не большой и не маленький, а в самый раз. Внутри блока все строки желательно упорядочить, дабы упросить в нем поиск. Так как блоки у нас небольшие, то поддерживать все записи в нем в упорядоченном состоянии достаточно просто.
http://www.vr-online.ru
3
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Попробуем найти слово "Гусь". Начинаем поиск с первого блока и последовательно просматриваем все строки. Находим слово "Бутылка", которое меньше, чем искомый гусь, но следующее слово (Пена) будет уже больше. Переходим в следующий блок, на который указывает найденная бутылочка. Ищем здесь и видим, что ближайшее к искомому слово – Груша. Переходим по ссылке на следующий блок, где и как раз и есть искомый Гусь. Ощутил преимущества древовидного индекса? Если нет, то вот они: 1. Для поиска даже самой последней записи в наборе данных, не нужно просматривать абсолютно все блоки. Вполне возможно, что достаточно будет просмотреть только 3 или четыре индексных блока; 2. На поиск любых данных затрачивается примерно одинаковое время; 3. Для эффективной работы вполне достаточно столько памяти, сколько занимает один индексный блок. Все блоки держать в оперативке нет смысла, но если есть лишняя память, то можно кешировать блоки;
Недостаток, на мой взгляд, только один, но он очень серьезный – поддерживать такой индекс не так уж и просто. О возможных вариантах поддержки можно писать отдельную статью. Я же могу порекомендовать тебе прочитать что-нибудь про архитектуру индексов в базах данных Oracle и MS SQL Server. Архитектуру первой я знаю не очень хорошо, а вот в MS SQL Server индексные деревья сделаны достаточно эффективно. Вот так вот при минимуме затрат можно быстро искать и обрабатывать большие объемы информации. В данном случае, мы затронули разновидность лиственных под
http://www.vr-online.ru
3
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
называнием B-Tree (Balanced Tree или сбалансированные деревья). Бывают и другие разновидности, но это уже совершенно другая история. Кластер или нет Существует две разновидности деревьев – кластерные и не кластерные. В первом случае, на кончиках веток индексного дерева находятся сами данные. Это значит, что прогулявшись по всем блокам мы находим непосредственно искомые данные. Получается, что в данном случае данные упорядочены с помощью такого индекса физически. Конечно же, на один список (таблицу) можно создать только один кластерный индекс, потому что упорядочить физически по двум разным параметрам один и тот же набор данных просто нереально. В не кластерном дереве на кончиках веток находятся только ссылки на данные, а сами данные могут находиться где угодно на диске и в любом порядке. Таких индексов можно создавать сколько угодно. Итого Мы поделились с тобой только основными алгоритмами экономии памяти и немного затронули даже оптимизацию. На эту тему можно говорить вечно, потому что оптимизация скорости и памяти – очень интересны сами по себе. Мы же не будем углубляться дальше основ, потому что дальнейшая оптимизация скорости и ресурсов требует хорошего знания математики. Если у тебя нет проблем с этой наукой, то за новыми алгоритмами можешь обратиться к трудам Кнута. Да, тексты его книг завернуты не по детски и без хорошего знания высшей математики и бутылки водки тут не разобраться, но зато пищи для ума хватит на год вперед.
http://www.vr-online.ru
3
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Managed DirectX Фленов Михаил aka Horrific http://www.vr-online.ru
Не секрет, что вся технология .NET появилась из-за зависти MS к успеху Java. Как же это в мобильных телефонах не стоят окна и работают проги на каком-то Java! Как же это предприятия строят сервисы на чужой технологии J2EE! И вот, выпуская джина по имени .NET из бутылки MS приготовила серьезную проблему для конкурентов в виде библиотеки Managed DirectX (управляемый DirectX). Действительно ли это серьезный аргумент или нет? Попробуем разобраться. Что такое Managed DirectX Так что же это за зверь Managed DirectX и с чем его едят? Это поддержка DirectX из управляемого кода, т.е. из программ, написанных на и для платформы .NET. Изначально эта технология даже называлась DirectX .NET, но потом была переименована в Managed DirectX. Первый раз я услышал о новом DirectX где-то в 2003-м году. Информация, которая просачивалась в Интернет, была обрывочна, а на официальном сайте появилась бета версия управляемого DirectX. Как я понял, эта библиотека была написана на C# на базе устаревшего к тому времени DirectX8. Я сам не видел эту библиотеку в действии, потому что в те времена пользовался телефонным подключением к Интернету и качать большой файл был не в состоянии. Судя по отзывам, библиотека была провальной, и тормозила как ржавый Запорожец по сравнению с Мерседесом (классическим DirectX). Затем пошло затишье. Информации о новой технологии стало минимум и в основном слухи. Судя по ним, библиотеку переписывали дважды, причем полностью. Как всегда официальные лица либо отмалчивались либо несли какую-то чушь, поэтому определить, что было правдой слишком сложно. К тому же, всем известная корпорация любит наводить тумана. И вот, перед самым появлением DirectX 9 мы узнаем, что MS рассылает бета версию обновленной библиотеки Managed DirectX, а в 9-ю версию DX SDK должен попасть полный вариант. Бета версию мне снова не удалось увидеть, но когда на жестком диске появился инсталл DX SDK 9.0, то первым, что бросилось в глаза – наличие в папке Help двух файлов помощи directx9_с.chm и directx9_m.chm. Первый файл описывает классический DirectX для С++, а второй DirectX 9.0 for Managed Code или просто Managed DirectX. После установки DX SDK директории C# и VB.NET языков. Да, если раньше VB.NET милости просим – DirectX.
сразу заглядываем в директорию Samples и тут мы видим с примерами использования DirectX для соответствующих создавать игры на VB было проблематично, то с помощью легко и не принужденно можно создать любое приложение
Поддержка Библиотека Managed DirectX разделена на следующие пространства имен: - Microsoft.DirectX.Direct3D – интерфейсы для реализации 3D графики; - Microsoft.DirectX.DirectDraw – старые, но очень добрые функции для работы с 2D поверхностями и соответствующей графикой; - Microsoft.DirectX.DirectSound – интерфейсы для работы со звуком; - Microsoft.DirectX.DirectInput – интерфейсы для работы с устройствами ввода.
http://www.vr-online.ru
3
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Это основные пространства и уже видно, что у нас есть все необходимое для создания игр. Помимо этого в managed DirectX есть интерфейсы для реализации средств безопасности и поддержка сети. Все эти интерфейсы можно использовать в следующих языках программирования: - Microsoft Visual C# - Microsoft Visual Basic .NET - Microsoft Visual C++ - Microsoft JScript .NET Самое интересное – это последний язык. Судя по всему, Managed DirectX планируют использовать в Интернете для создания сервисов с поддержкой 3D графики! Тест производительности Попробуем произвести достаточно простой тест производительности. Так как примеры, которые идут в поставке с DX SDK - схожи, по выполняемым функциям и при этом выводиться FPS, то достаточно запустить на одном компьютере сначала пример на классическом DirectX, а затем с использованием Managed DirectX. Я в качестве примера взял плавающего дельфина DXSDK\Samples\C#\Direct3D\DolphinVS. На моем буке Pentium M 1.7 c видел от ATI Mobility Radeon 9700, получились следующие результаты FPS для плавающего дельфина: - программа на C# с использованием Managed DirectX в среднем показала 540 FPS; - программа на C с использованием классического DirectX в среднем показала 620 FPS; Разница в 80 FPS (в данном случае это примерно 15%) оставляет двоякое впечатление. С одной стороны, разработчики достаточно хорошо постарались, и для управляемого кода получилась достаточно высокая производительность. Не забываем, что код C# выполняется как бы в виртуальной машине. С другой стороны – падение производительности на 15 процентов неоправданно и я бы просто зажал их. Лучше потратить эти драгоценные потерянные такты процессора на что-то более полезное. И все же, для Managed DirectX найдется применение в простых играх и разработчики на таких языках как C# и VB.NET будут очень довольны. Ведь раньше создание игр на VB было сущей каторгой, если не сказать больше – невозможной. Источники информации Как мы уже заметили, информации о Managed DirectX пока очень мало, потому что технология новая и многие относятся к ней с опаской. Самый лучший источник – это блог Тома Миллера. Том является одним из разработчиков API для Managed DirectX и кто, как не он, знает все тонкости и последние новости. Помимо этого, Том Миллер даже написал целую книгу, в которой подробно описывает Managed DirectX 9 версии. Книга посвящена разработке графики и игр и называется Managed DirectX 9 Graphics and Game Programming (Sams Publishing, 2004). В России эту книгу перевело издательство КомБук, при этом, название и обложка книги не изменились. Личные размышления Технология Managed DirectX – мощное средство для создания игр. Представьте себе игру, написанную на C# в сочетании с Managed DirectX. По заявлениям MS программы .NET могут выполняться на любой платформе при наличии соответствующего .NET Framework, а значит, мы получаем межплатформенную игру. Вот с последним серьезные проблемы. Во-первых, нормальной портации .NET на системы отличные от
http://www.vr-online.ru
4
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Windows просто нет. Есть попытки сделать что-то под Linux, но они не работоспособны на все 100%. Портировать DirectX нормальные люди мне кажется вообще не будут, потому что это связано с серьезными проблемами. Managed DirectX использует библиотеки не управляемого DirectX, который, в свою очередь, портировать сверх сложно из-за использования технологии COM. Одну эту технологию много раз пытались перенести на Linux и половина попыток закончились неудачно, а остальные накрылись медным тазом. То же самое может ожидать и Managed DirectX. А если вспомнить, что есть еще такие ОС как Sun, FreeBSD, MacOS, куда не ступала нога .NET, то понимаем, что межплатформенность – мечта, которая может и не сбыться. На первый взгляд вырисовывается страшная картина. Да, это мое личное мнение, но оно основано на уже существующих фактах. С другой стороны, MS уже вложили сумасшедшие деньги и вероятность провала всей технологии .NET стремиться к нулю. Если возникнут проблемы, великий Билл кинет пару миллиардов баксов на рекламу и все оживет с новой силой.
Так что же ожидает технологию Managed DirectX? Я думаю, что она будет развиваться, но только в сегменте стационарных компьютерах и мобильных устройств с ОС от Microsoft. А так как наладонники и вкарманники пока не обладают графическими возможностями ATI и GeForce, то развитие будет медленным и печальным. Я отслеживаю это развитие и одним глазком подглядываю в планы, чтобы в случае чего вскочить на коня и начать разработку игр на этой технологии. Сейчас же мы не рекомендуем тратить на подобнее шаги время, потому что Managed DirectX работает только на стационарах, где есть более быстрый классический DirectX. Как только появятся мобильники с полноценной поддержкой, так сразу можно изучать Managed, благо он не сильно сложный и не сильно отличается от классики.
http://www.vr-online.ru
4
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Полный контроль над IDE Коновалов Сергей (с) 2006
Sergio_Lghtning@yahoo.com «…запрещается модифицировать код, сгенерированный средой Делфи…» Один из популярных авторов Какие файлы использует Делфи, что скрыто внутри dfm, как работает визуальное проектирование, чем секция implementation отличается от interface, что такое эксперты и как их писать, варианты загрузки, как работать со средой нескольким учетным записям... После прочтения цикла статьей среда частично перестанет быть для вас черным ящиком. Часть 1: Делфи в реестре 1.1 Вступление В BDS 4.0 (Delphi 2006) разработчики осознали, что многие возможности среды не используются программистом. Действительно, зачем загружать библиотеки для работы с c#, если пользователь намерен работать только с Delphi, причем не тот, который for .net? Так появились возможность выбора между полным стартом, дот нэтом и win32:
Но такого, чтобы например, запустить среду с загруженными компонентами только для работы с базами данных, придумано не было. Я всегда хотел получить только то, что нужно. Конечно, сперва я деинсталлировал все мало используемые мною компоненты и оставляя самые нужные, но шло время, и многие вкладки приходилось восстанавливать. Одним из вариантов решения проблемы была попытка установки нескольких копий, но ни к чему хорошему это не привело. Все изменилось, когда я из любопытства залез в реестр… 1.2 Реестр В разделе HKEY_LOCAL_MACHINE\SOFTWARE\Borland нет ничего интересного, поэтому перейдем сразу к HKEY_CURRENT_USER\Software\Borland\Delphi\7.0 – настройки, относящиеся к конкретной учетной записи. Что мы видим? Да много чего:
http://www.vr-online.ru
4
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Здесь расположена вся информация о среде, начиная от установленных компонентах, заканчивая положением форм. Я не сомневаюсь, что вы сами сможете разобраться во всем, но все же немного пролью свет для самых ленивых и малограмотных в английском. Итак, начнем! Для начала отредактируем список Reopen:
В разделе Closed Files содержится список последних закрытых файлов. Значением параметра будет SourceModule,'C:\Documents and Settings\Сережа\Рабочий стол\Новая папка\Project1.dpr',1,1,32,1,50,0,0: первый параметр – тип файла (у меня *.pas), второй, как не трудно догадаться, путь, остальные мне неизвестны. Последние проекты хранятся в Closed Projects. Code Insight. Где это настраивается – первый рисунок, а что это такое вы можете видеть на втором.
http://www.vr-online.ru
4
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Помимо всего изображенного, в разделе хранится инфа о ширине и высоте окна. В прошлой статье я сделал упущение на эту тему, поэтому больше не буду рассматривать естественные вещи (наподобие разделов code explorer, debugging и т.д). Переходим к самому вкусному, а именно к пакетам. Known IDE Package – пакеты(package), из которых собственно и состоит среда. Вот что будет, если полностью очистить раздел:
http://www.vr-online.ru
4
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Мы сможем создавать только консольное приложение, а все возможности интегрированной среды разработки нам будут не доступны (т.к. код расположен в соответствующих bpl’ках, которые мы не загрузили, просто потому что делфи не знает об их существовании). Known Packages – а вот это уже пакеты компонентов. Давайте посмотрим, что случится если все удалить:
http://www.vr-online.ru
4
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Нереально быстрый старт среды! Отсюда делаем вывод, что скорость загрузки напрямую зависит от установленных пакетов компонентов! Чуете, к чему я веду?! Да, именно таким способом можно создавать свои варианты загрузки среды! Но об этом позже. Надеюсь теперь вы понимаете, каким образом устанавливаются компоненты, с помощью запуска одного только инсталлятора? Palette – палитра компонентов. Не трудно догадаться, что компоненты, которые установлены, но скрыты, имеют статус (название вкладки).Hidden, а сами скрытые вкладки (вкладка).HiddenPage:
Чуть поиграемся с Property Editors – свойствами редакторов компонентов. Заходим в Property Editors\Customize Actions Editor\TCustomizeActnEditDesigner и изменяем значения ширины, высоты, левой позиции, правой на произвольные. Теперь запускаем среду, кидаем Action Manager и смотрим:
http://www.vr-online.ru
4
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Если вы являетесь любителем DevExpress и часто пользуете cxGrid, то можно настроить первоначальные значения свойств. Находим Editors\TcxGridViewTemplate – дальше выбираем TcxGridTableView и настраиваем так, как хотим видеть при первоначальном расположении. Я например убирают ColumnFiltering, GroupByBox и т.д. Вот результат:
http://www.vr-online.ru
4
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Можно помучить следующие пункты меню:
Например давайте сделаем так, чтобы при выборе пункта Image Editor, запускался MS Paint. Смотрим на следующий скриншот:
http://www.vr-online.ru
4
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Вам понятно, что необходимо изменить значение параметра Path1? Таким образом можно заменить стандартные приложения, на какие-нибудь более продвинутые. Можно, но есть способ лучше! Читайте об этом в главе «Эксперты». Я описал наиболее интересные вещи, однако это далеко не все. Поройтесь в разделе Borland самостоятельно. 1.3 Варианты загрузки среды Итак, как вы уже знаете, информация об установленных компонентах хранится в реестре. Манипулируя списком вручную, можно создавать различные комбинации: запуск со всеми компонентами, минимальный запуск, работа с БД и т.д. Самым примитивным способом будет создание несколько копий reg – файлов. Для начала необходимо полностью забэкапиться – выберите пункт Delphi и в контекстном меню экспортируйте файл реестра. Теперь можно экспериментировать, удаляя или оставляя пакеты в HKEY_CURRENT_USER\Software\Borland\Delphi\7.0\Known Packages. Таким образом у вас получиться несколько файлов, при выполнении которого, информация в реестре будет обновляться на нужную. Конечно, не очень удобно каждый раз сначала кликать по reg – файлу, а потом по экзешнику. Процесс можно автоматизировать. Достаточно написать приложение, в котором реализовать вызов функции ShellExecute сначала для нужного файла реестра (либо работать с реестром самому), а потом для исполняемого (по умолчанию C:\Program Files\Borland\Delphi7\Bin\delphi32.exe). Вот пример такой реализации:
http://www.vr-online.ru
4
VR-online Journal (Фленов Михаил & VR-Group)
Для программистов №15
Конечно, у этого способа есть недостатки, например запуск делфи не через ярлык, а путем открытия ассоциированного с ним файла. Идеальным вариантом будет написание соответствующего эксперта (на данный момент мне этого не удалось, и не факт, что это вообще осуществимо!). 1.4 Работа с Delphi под несколькими учетными записями Хранение информации об IDE и пакетах для каждого пользователя может сделать невозможным работу со средой на одном компьютере под разными учетными записями. Все дело в том, что установленные компоненты при помощи инсталлятора будет видеть только пользователь, который собственно и производил установку. У другого человека естественно своя информация в ветке HKEY_CURRENT_USER. Но попытавшись провести повторную установку компонентов, будет проинформирован, что они уже установлены и единственный выход – переустановить их. Выходом будет создание экспорта раздела HKEY_CURRENT_USER\Software\Borland\Delphi (а если точнее HKEY_CURRENT_USER\Software\Borland\Delphi\7.0\Known Packages) первым пользователем. Пожелания, дополнения, критика:
Sergio_Lightning@yahoo.com
http://www.vr-online.ru
5