Упражнение 7, стр. от
Упражнение 7
Създаване и използване на собствен DLL
1Цели на упражнението -
Запознаване с технологията за разработване и използване на собствени библиотеки с динамично свързване - DLL
-
Запознаване с нови функции от Windows API, осигуряващи контрол върху механизма на обработка на съобщенията
2Теоретична постановка 2.1Общи положения
Удобно е често използвани функции да се обособят в библиотека. Така те няма всеки път да се пишат в source файловете. В Windows могат да се ползват библиотеки със статично свързване (.LIB) и с динамично свързване (.DLL) Предимство на последните е, че EXE файлът става по-малък – кода на библиотечните функции не се добавя към него, а се ползва наготово така както е в DLL-а. Недостатък е необходимостта DLL да бъде на определено място – в директорията, от която се стартира EXE-то, в WINDOWS или WINDOWS\SYSTEM32 директорията или в някоя от директориите, посочени в променливата от обкръжението PATH. Друг недостатък е свързан с промяната на функциите в DLL-a. Те могат да се променят, но прототипите им трябва да остават едни и същи. Могат да се добавят функции, но не трябва да се премахват съществуващи (с цел съвместимост с програми, използващи предишни версии на дадения DLL).
Прототипът на главната функция на DLL изглежда така:
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call,
LPVOID lpReserved )
-
hModule е манипулатор на DLL-а
-
ul_reason_for_call съдържа една от четири стойности, показващи защо се вика главната функция:
-
DLL-а се свързва към процес (напр. стартира се процес, който ползва този DLL)
-
DLL-а се свързва към нишка
-
DLL-а се освобождава от процес (напр. процес, който ползва този DLL, завършва работата си)
-
DLL-а се освобождава от нишка
-
lpReserved - не се използва
-
Ако функцията върне FALSE, то процесът, който ползва DLL-а, няма да се стартира.
Ако няколко процеса ползват един и същ DLL, то всеки процес получава отделно копие от данните на DLL-а. Да речем, че е необходимо DLL-ът да поддържа брояч, отчитащ колко процеса го използват в даден момент. Би могъл да се напише следния код:
...
LONG refCnt=0;
...
BOOL APIENTRY DllMain(HANDLE hModule,DWORD reason,LPVOID rsrv)
{
switch (reason)
{
case DLL_PROCESS_ATTACH:
refCnt++; break;
case DLL_PROCESS_DETACH:
refCnt--; break;
}
return TRUE;
}
Така оставен, кодът няма да работи правилно, поради факта, че всеки процес, който ползва DLL-а, ще получава собствено копие от брояча. Той ще се инициализира с 0 и ще се инкрементира само веднъж. Така в рамките на всеки процес, броячът ще има стойност 1.
Проблемът може да се реши, ако броячът се направи общ за всички копия на DLL-а. Това става по следния начин:
#pragma data_seg("shared")
LONG refCnt=0;
#pragma data_seg()
#pragma comment (linker, "/section:shared,S")
Така пък могат да се получат синхронизационни проблеми при опита за едновременен достъп до вече поделения ресурс refCnt. За конкретния случай, най-лесния изход от положението е замяната на refCnt++ с InterlockedIncrement(&refCnt) и съответно refCnt-- с InterlockedDecrement(&refCnt). Цитираните тук функции са от Windows API и гарантират непрекъсваемо изпълнение на съответните операции.
2.2Нови функции, променливи и структури от API
Примерният DLL който ще разработим, илюстрира възможността за намеса в системния механизъм на обработка на съобщенията от Windows. Това става с така наречените hook-функции (от hook – кука).
Основната API-функция, която се използва за целта, е:
HHOOK SetWindowsHookEx( // ИНСТАЛИРА HOOK-ФУНКЦИЯ и връща манипулатор към нея
int idHook, // тип hook-функция
HOOKPROC hkproc, // адрес на hook-функцията
HINSTANCE hMod, // манипулатор на извикващия процес
DWORD dwThreadID // идентификатор на нишка
);
-
idHook Може да е измежду няколко предварително дефинирани константи. Определя кога да се вика функцията, чиито адрес е даден в hkproc. Пълен списък може да се получи от Help-а. Някои от константите са:
WH_KEYBOARD – при всяко натискане / отпускане на клавиш
WH_MOUSE – при всякакво събитие от мишката (местене, щракане)
WH_GETMESSAGE – след като системата открие съобщение, предназначено за даден процес и преди да го предаде на процесa (в неговото извикване на GetMessage(...))
-
hkprc Указва адреса на hook-функцията. Дефиницията трябва да е от вида:
LRESULT CALLBACK HookProc (
int code,
WPARAM wParam,
LPARAM lParam );
Единствено имената на функцията и параметрите не са фиксирани. Смисълът на параметрите и връщания резултат зависят от типа hook-функция (параметъра idHook).
-
hMod Съдържа манипулатор към модула, съдържащ hook-функцията. За да може да се следят събития за всички процеси в системата, hook-функцията трябва задължително да е в DLL (а не например в EXE-то на програмата, която я използва)
-
dwThreadID – Идентифицира нишка, за която се следят събития. Ако е 0 се следят събитията за всички нишки на всички процеси в системата.
За да се даде възможност на други hook-функции също да работят, трябва вътре в "нашата" hook-функция да се извика API-функция
LRESULT CallNextHookEx(
HHOOK hhook, // манипулатор на "нашата" hook-функция (получава се
// като резултат от SetWindowsHookEx(...)
int nCode, // тези три параметъра трябва да
WPARAM wParam, // са същите, както са получени в
LPARAM lParam // "нашата" hook-функция
);
Деинсталирането на hook-функция става чрез обръщение към API-функция
BOOL UnhookWindowsHookEx(
HHOOK hhook // манипулатор на hook-функция която да се деинсталира
);
Специфични особености при работа с hook функции инсталирани за цялата система (т.е. разположени в DLL):
-
автоматично се "прикрепват" към всички процеси, работещи в системата, включително и тези, които са стартирани преди инсталирането на съответния hook
-
кода на hook функцията се изпълнява в контекста на съответния процес
3Задачи 3.1DLL и hook функции -
Да се състави програма, която, докато е активна, да забранява обработката на някои клавиши от клавиатурата (например цифрите от 0 до 9 и трите специфични Windows бутона).
-
Обърнете внимание на съобщенията, които програмата от Задача 1 извежда; отбележете, че броячът на използванията не работи. Отстранете проблема.
-
Да се състави програма, която, докато е активна, да забранява обработката на съобщението WM_COMMAND за всички процеси в системата.
3.2Използване на недокументирани функции -
Съставете Win32 програма, основният прозорец на която да е прозрачен.
-
Да допуснем, че програмата от Задача 4 работи с много прозорци. Ако трябва всички те да са прозрачни, може би не е удобно при създаването на всеки да се изпълняват съответните действия. Решете поставения проблем с инсталиране на подходяща hook-функция.
4Указания 4.1Към Задача 1
За първата задача трябва да се инсталира hook-функция от тип WH_KEYBOARD. За да могат да се следят всички процеси в системата е необходимо hook-функцията да бъде в DLL.
Работното пространство (workspace) за упражнението трябва да включва два отделни проекта. Добре е да се започне с DLL-a.
-
След стартиране на средата се избира се File|New. От групата Project се избира Win32 Dynamic Link Library
-
От последващия диалог се избира Simple DLL. Да допуснем, че му се задава име MYDLL. Това име ще се използва и за създадените .LIB и .DLL файлове – т.е. при успешно построяване на проекта ще се получат файлове MYDLL.LIB и MYDLL.DLL. Средата генерира минимален работещ, но безсмислен DLL. В него трябва да се дефинират няколко функции – самите hook-функции, както и такива за инсталирането и деинсталирането им. Последните трябва и да се експортират, за да могат да се използват от други модули.
-
Примерното съдържание на source файл, реализиращ Задача 1 би могло да бъде:
#include "stdafx.h"
HHOOK hKbdHook=NULL;
HINSTANCE instDll=NULL;
LONG refCnt=0;
LRESULT CALLBACK KbdHookProc(int code,WPARAM wParam,LPARAM lParam)
// Ако code е <0, функцията трябва веднага да предаде управлението по веригата.
// wParam съдържа кода на клавиша
// Ако функцията върне <>0, клавишът се игнорира
// Ако върне 0 клавишът се обработва веднага, избягвайки всички други
// hook- функции по веригата
{
if (code<0) return CallNextHookEx(hKbdHook,code,wParam,lParam);
if ( (wParam==VK_LWIN) ||
(wParam==VK_RWIN) ||
(wParam==VK_APPS) ) return 1;
if (((wParam>='0')&&(wParam<='9'))||(wParam==' ')) return 1;
else return CallNextHookEx(hKbdHook,code,wParam,lParam);
}
__declspec ( dllexport ) BOOL SetKeyboardHook()
{
if (instDll==NULL) return FALSE;
hKbdHook=SetWindowsHookEx(WH_KEYBOARD,(HOOKPROC)KbdHookProc,instDll,0);
return (hKbdHook!=NULL);
}
__declspec ( dllexport ) BOOL UnSetKeyboardHook()
{
return UnhookWindowsHookEx(hKbdHook);
}
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call,
LPVOID lpReserved )
{
TCHAR comp[1024]="";
TCHAR user[1024]="";
TCHAR proc[1024]="";
TCHAR msg[2048]={0};
DWORD szcomp=sizeof(comp)-1;
DWORD szuser=sizeof(user)-1;
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
instDll=(HINSTANCE)hModule;
refCnt++;
GetComputerName(comp,&szcomp);
GetUserName(user,&szuser);
GetModuleFileName(NULL,proc,sizeof(proc)-1);
wsprintf(msg,"Attaching to %s\\\\%s\nProcess:%s,cnt=%i",comp,user,proc,refCnt);
MessageBox(NULL,msg,"",MB_OK);
break;
case DLL_THREAD_ATTACH:break;
case DLL_THREAD_DETACH:break;
case DLL_PROCESS_DETACH:
refCnt--;
GetComputerName(comp,&szcomp);
GetUserName(user,&szuser);
GetModuleFileName(NULL,proc,sizeof(proc)-1);
wsprintf(msg,"Detaching from%s\\\\%s\nProcess:%s,cnt=%i",comp,user,proc,refCnt);
MessageBox(NULL,msg,"",MB_OK);
break;
default:return FALSE;
}
return TRUE;
}
-
Използването на така дефинираните и експортирани от DLL-a функции става в друг проект – изпълнима програма. За целта отново може да се генерира "типична Hello World програма".
-
При създаването на новия проект – Win32 Application, трябва да се смени радиобутонът "Create new workspace" с "Add to current workspace"
-
В програмата трябва да се добавят:
-
В началото, при дефинициите:
BOOL SetKeyboardHook();
BOOL UnSetKeyboardHook();
if (!SetKeyboardHook())
{
MessageBox(NULL,"Can't set hook!","Error",MB_ICONSTOP|MB_OK);
return 0;
}
-
При обработката на WM_DESTROY:
UnSetKeyboardHook();
-
За да може програмата да се свърже с DLL-a, е необходимо да се укаже на свързващия редактор къде се намира съответния LIB-файл. Това може да стане поне по два начина:
-
От меню Project|Settings се избират настройките за Link. В Object/Library settings се добавя ..\Debug\MYDLL.LIB Това е свързано с начина, по който Visual C++ строи проектите. За всеки проект съществуват две конфигурации – развойна и финална. Изходните файлове, свързани с първата се намират в поддиректория Debug, а тези, свързани с втората – в директория Release. По подразбиране се работи с Debug версията на проекта. Тя позволява лесна настройка на програмата. Идеята е едва след като програмата се настрои и тества, да се построи Release версия, която да се разпространява. В Release версията са включени настройки за оптимизация, премахната е debug-информацията от EXE-то и др.
ИЛИ
-
От прозореца на Workspace се избира File View. Щраква се десен бутон върху името на проекта с програмата и се избира Add files to project. От диалога за отваряне на файл се избират библиотечни файлове (Files of type: Library files (*.lib)) и се намира и добавя MYLIB.LIB или както там е името на конкретната статична библиотека.
4.2Към задача 2
В дадения по-горе готов source е реализиран брояч на използванията, но без да са взети мерки броячът (refCnt) да е общ за всички копия на DLL-a. Вижте точка 2.1 по-горе за указания как да стане това.
4.3Към задача 3
Към съществуващия source на DLL-a добавете hook функция от тип WH_GETMESSAGE. Към извикващата програма също трябва да се добавят един-два реда.
4.4Към задача 4
В по-новите версии на Windows (2000, XP) съществува функция (в KERNEL32.DLL):
BOOL SetLayeredWindowAttributes(HWND hWnd, COLORREF crKey,
BYTE bAlpha, DWORD dwFlags)
Тази функция може да се използва за задаване на прозрачност на прозорец. В този случай параметърът crKey може да е 0, а dwFlags трябва да е 2. bAlpha задава степента на прозрачност от 0 до 255. Например:
SetLayeredWindowAttributes(hWnd,0,128,2); // задава 50% прозр.
За да работи функцията, прозорецът трябва да е с включен разширен стил WS_EX_LAYERED (дефиниран като 0x00080000)
Функцията обаче е недокументирана, поне за VC 6.0 (не е включена в никой хедер, нито в KERNEL32.LIB) Така или иначе, даже да е включена, директното свързване към нея не е желателно, ако програмата трябва да работи и на Windows 9x (там просто тази функция я няма). Затова извикайте функцията по следния модел:
typedef BOOL (WINAPI *PSLWA)(HWND, COLORREF, BYTE, DWORD);
... LoadLibrary("USER32");
... GetProcAddress(...,"SetLayeredWindowAttributes");
...
... FreeLibrary(hu32);
Сподели с приятели: |