Написание своего Mirror видеодрайвера
Существует определенный круг задач, которым необходимо точно детектировать все изменения экрана компьютера. К таким задачам можно отнести системы удаленного доступа к рабочему столу компьютера и программы для создания видеороликов происходящего на экране компьютера. Для этих целей можно воспользоваться технологией компании Microsoft - Mirror Video Driver. В статье рассматриваются различные аспекты написания собственного mirror видеодрайвера.
Creation the own Mirror video driver.pdf Системы для удаленного управления рабочим столом из-вестны давно. Первоначально они были созданы для удален-ного управления графической средой Unix систем X-Windows.Компания Microsoft также решила включить поддерж-ку удаленного управления в ОС Windows, начиная с версии2000. Для этого в ядро ОС была добавлена поддержка техно-логии Mirror Video Driver.Идея состоит в том, что ОС дублирует все графическиеоперации на mirror видеодрайвер. Таким образом, приложе-ние, использующее mirror драйвер, имеет возможность опре-делять любые изменения экрана. Это позволяет минимизиро-вать объем обрабатываемых данных, по сравнению с про-стым захватом всего изображения экрана.В результате минимизации объема данных, происходитминимизация нагрузки на центральный процессор, а значит,большая часть процессорного времени остается на обычныепользовательские приложения.К сожалению, сама компания Microsoft плохо документи-ровала сведения о mirror видеодрайвере, поэтому в статьераскрываются различные аспекты, которые не отображены вдокументации и которые в большинстве своем найденыопытным путем.ЧТО ТРЕБУЕТСЯ ДЛЯ НАПИСАНИЯ ДРАЙВЕРАПрежде всего, необходимо установить один из ва-риантов Visual C++. Желательно использовать самыепоследние версии, например, Visual C++ 7 (Visual Studio.Net 2003).Кроме этого, для написания драйвера необходимпакет Windows DDK (Driver Development Kit). Всю не-обходимую информацию о пакете вы сможете найти поссылке [x]. Минимальная версия пакета должна бытьне меньше, чем Windows 2000 DDK.После установки DDK в меню Программы появитсягруппа Development Kits, в которой находятся ярлыкидля настройки переменных окружения для разработкидрайвера. Ярлык Check Build предназначен для сборкидрайвера в режиме отладки, т.е. он будет скомпилиро-ван без включения оптимизации, и будет включать всебя информацию для отладчика.Ярлык Free Build предназначен для сборки драйверадля использования конечным пользователем. Компиля-тор проведет оптимизацию кода и исключит информа-цию для отладчика из результирующего кода.Ярлык DDK Documentation открывает документа-цию компании Microsoft, связанную с разработкойдрайверов.СТРУКТУРА MIRROR ВИДЕОДРАЙВЕРА Как любой видеодрайвер, mirror драйвер состоит издвух частей - Mirror miniport видеодрайвер и Mirrorдрайвер дисплея.Miniport видеодрайвер является специфической про-граммой для каждого видеоадаптера, т.е. пишется подконкретное оборудование. Он работает в нулевомкольце защиты ядра ОС Windows. Для обмена даннымис видеоадаптером miniport драйвер использует специ-альный Video port драйвер. Video port драйвер являетсянезависимым от оборудования и поставляется вместе соперационной системой. Miniport драйвер для связи соборудованием и операционной системой может вызы-вать только функции Video port драйвера VideoPortXxx.Таким образом, для написания своего mirror драй-вера необходимо создать miniport видеодрайвер и mirrorдрайвер дисплея.С ЧЕГО НАЧАТЬЗа основу своего драйвера можно взять пример ко-да, предложенный компанией Microsoft. В различныхверсиях DDK пример находится в разных местах, нонайти его можно по имени директории, в которой ле-жат требуемые файлы - mirror. В директории лежат двапроекта. В Mirror/dll лежит исходный код mirror драй-вера дисплея, а в Mirror/app лежит код win32 приложе-ния уровня пользователя, который умеет запускать драй-вер и завершать его работу.В директории miniport/mirror находится исходныйкод miniport видеодрайвера. Код содержит самый ми-нимальный набор функций, который должен быть под-держан miniport драйвером. Так как mirror видеодрай-вер является не настоящим драйвером оборудования,то нет необходимости реализовывать все функции miniportвидео-драйвера. Поэтому, для создания своегодрайвера мы просто можем скопировать код mirrorminiport драйвера из DDK.После этого можно считать, что одна часть созда-ваемого драйвера готова.ФУНКЦИИ MIRROR ДРАЙВЕРА ДИСПЛЕЯЛюбой драйвер дисплея является динамически за-гружаемой библиотекой (DLL) и должен поддерживатьопределенный обязательный набор функций (см. табл.).Функция ОписаниеDrvEnableDriver Является главной точкой входа в биб-лиотекуDrvAssertMode Сбрасывает видеорежимDrvEnablePDEV Включает физическое устройствоDrvCompletePDEV Информирует драйвер о завершенииподключения устройстваDrvDisablePDEV Когда устройство больше не нужно, тоосвобождаются все ресурсы, выделен-ные в функции DrvEnablePDEVDrvEnableSurface Создает поверхность для специфиче-ского устройстваDrvDisableSurface Информирует драйвер о томПриведем пример описания:static DRVFN gadrvfn[] ={{INDEX_DrvEnablePDEV,(PFN)DrvEnablePDEV},{INDEX_DrvCompletePDEV,(PFN)DrvCompletePDEV},{INDEX_DrvDisablePDEV,(PFN)DrvDisablePDEV},{INDEX_DrvEnableSurface,(PFN)DrvEnableSurface},{INDEX_DrvDisableSurface,(PFN)DrvDisableSurface},{INDEX_DrvAssertMode,(PFN)DrvAssertMode},…}И пример передачи этой таблицы операционнойсистеме:BOOL DrvEnableDriver(ULONG iEngineVersion,ULONG cj,PDRVENABLEDATA pded){iEngineVersion;if (cj >= sizeof(DRVENABLEDATA))pded->pdrvfn = gadrvfn;if (cj >= (sizeof(ULONG) * 2))pded->c = sizeof(gadrvfn) / sizeof(DRVFN);if (cj >= sizeof(ULONG))pded->iDriverVersion = DDI_DRIVER_VERSION;return(TRUE);}Условия для проверки параметра cj стоят для того,чтобы, даже в случае неправильного вызова, наш драйверне свалил бы систему, а корректно обработал параметры.ВНЕШНЯЯ ИНИЦИАЛИЗАЦИЯMIRROR ДРАЙВЕРА ДИСПЛЕЯЗагрузка драйвера происходит при запуске опера-ционной системы, а его инициализация производитсяпо требованию.Для того чтобы активизировать mirror драйвер изwin32 приложения уровня пользователя, необходимовыполнить следующую последовательность действий.Используя API функцию EnumDisplayDevices в ци-кле, перебираем все видео устройства до тех пор, покане найдем необходимый Mirror видеодрайвер.DISPLAY_DEVICE dispDevice;FillMemory(&dispDevice, sizeof(DISPLAY_DEVICE),0);dispDevice.cb = sizeof(DISPLAY_DEVICE);LPSTR deviceName = NULL;devmode.dmDeviceName[0] = '\0';INT devNum = 0;BOOL result;while (result = EnumDisplayDevices(NULL,devNum,&dispDevice,0)){if (strcmp(&dispDevice.DeviceString[0], driver-Name) == 0)break;devNum++;}Если после выполнения вышеприведенного кодапеременная result будет установлена в false, то в систе-ме нет искомого mirror драйвера.Всем драйверам дисплея назначаются символиче-ские имена, которые имеют форму DEVICEn, где n -это номер устройства в ОС.Если искомый mirror драйвер найден, то его симво-лическое имя находится в поле DeviceKey структурыDISPLAY_DEVICE.Используя найденный идентификатор, мы можемактивизировать mirror драйвер. Для этого необходимооткрыть на запись ключ реестра:HKLM\CurrentControlSet\HardwarePrfiles\Current\System\CurrentControlSet\Services\имя_mirror_драйвера\символическое_имяИ установить значение «Attach.ToDesktop» в 1.Если значение удалось установить, то активизиро-вать mirror драйвер необходимо функцией ChangeDisplaySettingsExс последующим вызовом:HDC hdc = CreateDC("DISPLAY", deviceName,NULL, NULL);Для того чтобы после перегрузки ОС Windows непроизошла активизация драйвера, необходимо сразу жеустановить значение «Attach.ToDesktop» в 0.Для деактивации драйвера мы еще раз вызываемфункцию ChangeDisplaySettingsEx, но уже со значени-ем «Attach.ToDesktop» равным нулю.В этой схеме есть один большой минус - она не ра-ботает, если у пользователя нет прав администратора.Обычному пользователю запрещено изменять значенияреестра в ключе HKEY_CURRENT_USER.Для того чтобы преодолеть эту проблему, предлага-ется в ходе установки драйвера изменить права на не-обходимую ветку в реестре с тем, чтобы обычныепользователи могли изменять содержимое значения«Attach.ToDesktop».ВНУТРЕННЯЯ ИНИЦИАЛИЗАЦИЯ…DrvData *drvdata = NULL;drvdata = EngAllocMem(FL_ZERO_MEMORY,sizeof(DrvData), 0x4D495252);drvdata->semaphore = NULL;drvdata->started = FALSE;…ppdev->pvTmpBuffer = drvdata;return((DHPDEV) ppdev);}В этом примере структура DrvData содержит все не-обходимые переменные, и указатель на нее присваиваетсяспециальному полю структуры PDEV - pvTmp-Buffer.Это поле предназначено для внутренних нужд драйвера.После инициализации физического устройства и за-полнения структуры PDEV операционная система вы-зывает драйверную функцию DrvEnableSurface. В этойфункции драйвер создает поверхность, на которой дол-жны производиться графические операции.Для mirror драйвера в качестве поверхности можноиспользовать область памяти, размера которой хватаетдля отображения графических данных. Например, еслимы установили видеорежим 800×600 при 32 битах нацвет, то размер памяти для поверхности должен быть800×600×4 = ~1,83 Мб.Но здесь надо быть очень внимательным. Так как вкачестве поверхности для mirror драйвера обычно ис-пользуется bitmap, то в зависимости от количества битна цвет размер требуемой памяти рассчитывается последующей формуле на языке C: ((Ширина × Количе-ство_бит_на_цвет +31) & ~31) / 8 × Высота.Функция DrvEnableSurface получает на вход указа-тель на созданную ранее структуру PDEV, поэтому име-ется возможность использовать и структуру DrvData.Приведем пример функции:HSURF DrvEnableSurface(DHPDEV dhpdev){PPDEV ppdev;HSURF hsurf;SIZEL sizl;ULONG ulBitmapType;FLONG flHooks;ULONG mirrorsize;HBITMAP hbmp;DrvData *drvdata;ppdev = (PPDEV) dhpdev;if(!ppdev->pvTmpBuffer)return NULL;drvdata = ppdev->pvTmpBuffer;sizl.cx = ppdev->cxScreen;sizl.cy = ppdev->cyScreen;drvdata->cx = ppdev->cxScreen;drvdata->cy = ppdev->cyScreen;drvdata->bits = ppdev->ulBitCount;drvdata->scanlength = ppdev->lDeltaScreen;if (ppdev->ulBitCount == 16){ulBitmapType = BMF_16BPP;flHooks = HOOKS_BMF16BPP;}else if (ppdev->ulBitCount == 24){ulBitmapType = BMF_24BPP;flHooks = HOOKS_BMF24BPP;}else{ulBitmapType = BMF_32BPP;flHooks = HOOKS_BMF32BPP;}flHooks |= flGlobalHooks;mirrorsize = (ULONG)(ppdev->lDeltaScreen *sizl.cy);drvdata->bmpbuffer =EngAllocMem(FL_ZERO_MEMORY, mirrorsize,0x4D495251);hbmp =EngCreateBitmap(sizl,ppdev->lDeltaScreen, ulBitmapType, 0,drvdata->bmpbuffer);if (hbmp == (HBITMAP) 0){return(FALSE);}if (!EngAssociateSurface((HSURF)hbmp,ppdev->hdevEng, flHooks)){EngDeleteSurface((HSURF)hbmp);return(FALSE);}ppdev->hsurfEng = (HSURF) hbmp;return((HSURF)hbmp);}С помощью функции EngAllolcMem мы выделяемнеобходимый объем памяти для bitmap. Функция Eng-CreateBitmap создает дескриптор bitmap, который ассо-циируется с поверхностью устройства при помощи функ-ции EngAssociateSurface. В функции используется кон-станта flGlobalHooks. Константа содержит набор флаж-ков, каждый из которых идентифицирует графическуюоперацию, которую драйвер будет перехватывать у GDI.Например, константа может иметь следующий вид:#define flGlobalHooks HOOK_BITBLT |HOOK_TEXTOUT | HOOK_COPYBITS | HOOK_STROKEPATH |HOOK_FILLPATH | HOOK_LINETO | HOOK_GRADIENTFILL |HOOK_STRETCHBLT | HOOK_TRANSPARENTBLT |HOOK_STRETCHBLTROP | HOOK_ALPHABLEND |HOOK_PLGBLTКаждая константа HOOK_Xxx определяет ту функ-цию в mirror драйвере дисплея, которая будет вызвана,если такая же функция была вызвана для первичноговидеодрайвера. Так драйвер говорит о своем желанииперехватывать графические функции.Все функции EngXxx предоставляются стандартнойбиблиотекой Windows GDI, работающей в ядре ОС.Если операционнойgadrvfn. Кроме этого, необходимо выставить соответ-ствующий флаг в константе flGlobalHooks.BOOL DrvCopyBits(OUT SURFOBJ *psoDst,IN SURFOBJ *psoSrc,IN CLIPOBJ *pco,IN XLATEOBJ *pxlo,IN RECTL *prclDst,IN POINTL *pptlSrc){if (psoDst){if (psoDst->dhpdev){PPDEV ppdev = (PPDEV) psoDst->dhpdev;if((ppdev->hsurfEng ==psoDst->hsurf)&&(ppdev->pvTmpBuffer)){DrvData* drvdata =(DrvData*)ppdev->pvTmpBuffer;EngCopyBits(psoDst, psoSrc, pco, pxlo,prclDst, pptlSrc);}}}}Функция DrvCopyBits используется для рисованиярастровых изображений. Если параметры psoDst иpsoSrc представляют собой одну и ту же поверхность,то функция производит перемещение одной части по-верхности в другую. Такая операция происходит прискроллировании содержимого окна.Для активизации функции перехвата DrvCopyBitsнеобходимо в таблицу gadrvfn добавить строчку«{INDEX_DrvCopyBits,(PFN) DrvCopyBits}», а в кон-станту flGlobalHooks флаг HOOK_COPYBITS.Имеется возможность воспользоваться стандартнымисредствами GDI для осуществления операций, которые неподдерживаются mirror драйвером дисплея. Как правило,они все начинаются с букв Eng, а дальше идет названиеперехватываемой функции, а нашем случае - CopyBits.Для списка всех возможных графических функцийдля перехвата необходимо смотреть документацию поWindows DDK.ОТЛАДКА MIRROR ДРАЙВЕРА ДИСПЛЕЯКак любой драйвер ядра ОС, mirror драйвер тяжелоотлаживать на одном компьютере. Так как драйвер ра-ботает в ядре, то любая ошибка может привезти к пере-грузке или зависанию компьютера. Для помощи в от-ладке можно предложить использовать возможность ви-деодрайвера работать с файлами.К сожалению, функции драйвера дисплея по работе сфайлами очень бедны, поэтому приведем пример функ-ции, для записи текстовых строк в лог файл.void WriteLog(DrvData* drvdata, char* string){ULONG file;char *fileptr;int size = strlen(string);fileptr =(char*)EngMapFile(L"\\??\\c:\\bbcap.log",drvdata->logpos + size, &file);if(fileptr){memcpy(fileptr + drvdata->logpos, string,size);drvdata->logpos = drvdata->logpos + size;EngUnmapFile(file);}}Так как структура DrvData доступна практически влюбом месте кода, то в нее добавлено специальное полеlogpos, в котором хранится текущая позиция в лог файле.При помощи функции EngMapFile лог файл ото-бражается на область памяти с заданным размером.Размер задаем с учетом строки, которая должна бытьсохранена в лог файле. EngMapFile возвращает указа-тель на область памяти, в которую файл отображен.Копируем строку в конец выделенной памяти и за-крываем файл используя функцию EngUnmapFile.Функцию WriteLog можно модифицировать дляподдержки сохранения форматированного вывода.ПЕРЕДАЧА ПАРАМЕТРОВ И КОМАНДИЗ WIN32 ПРИЛОЖЕНИЯВ MIRROR ДРАЙВЕРАДля передачи команд из Win32 приложения можнодобавить в mirror драйвер дисплея функцию перехват-чик DrvDrawEscape.При вызове win32 API функции DrawEscape GDIпередаст управление в mirror драйвер функции пере-хватчику DrvDrawEscape.В функции DrvDrawEscape можно напрямую ис-пользовать все параметры и указатели, передаваемыефункции DrawEscape, так как графическая подсистемаработает в контексте того же процесса и потока, в ко-тором была вызвана графическая функция.Поэтому, имеется возможность передавать данные изWin32 приложения mirror драйверу дисплея и обратно.ЗАКЛЮЧЕНИЕНесмотря на не полную документацию в этой об-ласти, написать свой собственный драйвер не сложно.Автором статьи был разработан mirror видеодрай-вер, который используется в коммерческом программ-ном продукте BB FlashBack для определения областейизменений экрана компьютера.
Скачать электронную версию публикации
Загружен, раз: 544
Ключевые слова
Авторы
ФИО | Организация | Дополнительно | |
Лавров Валерий Александрович | Томский государственный университет | старший преподаватель кафедры теоретических основ информатики факультета информатики | LVA@mail.tomsknet.ru |
Ссылки
