1Цели на упражнението



Дата10.04.2017
Размер84.04 Kb.

Упражнение 7, стр. от

Упражнение 7

Създаване и използване на собствен DLL

1Цели на упражнението


  1. Запознаване с технологията за разработване и използване на собствени библиотеки с динамично свързване - DLL

  2. Запознаване с нови функции от 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 функции


  1. Да се състави програма, която, докато е активна, да забранява обработката на някои клавиши от клавиатурата (например цифрите от 0 до 9 и трите специфични Windows бутона).

  2. Обърнете внимание на съобщенията, които програмата от Задача 1 извежда; отбележете, че броячът на използванията не работи. Отстранете проблема.

  3. Да се състави програма, която, докато е активна, да забранява обработката на съобщението WM_COMMAND за всички процеси в системата.

3.2Използване на недокументирани функции


  1. Съставете Win32 програма, основният прозорец на която да е прозрачен.

  2. Да допуснем, че програмата от Задача 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();

  • В InitInstance(...) :

if (!SetKeyboardHook())

{

MessageBox(NULL,"Can't set hook!","Error",MB_ICONSTOP|MB_OK);

return 0;

}

  • При обработката на WM_DESTROY:

UnSetKeyboardHook();

  • За да може програмата да се свърже с DLL-a, е необходимо да се укаже на свързващия редактор къде се намира съответния LIB-файл. Това може да стане поне по два начина:

  1. От меню Project|Settings се избират настройките за Link. В Object/Library settings се добавя ..\Debug\MYDLL.LIB Това е свързано с начина, по който Visual C++ строи проектите. За всеки проект съществуват две конфигурации – развойна и финална. Изходните файлове, свързани с първата се намират в поддиректория Debug, а тези, свързани с втората – в директория Release. По подразбиране се работи с Debug версията на проекта. Тя позволява лесна настройка на програмата. Идеята е едва след като програмата се настрои и тества, да се построи Release версия, която да се разпространява. В Release версията са включени настройки за оптимизация, премахната е debug-информацията от EXE-то и др.

ИЛИ

  1. От прозореца на 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);


База данных защищена авторским правом ©obuch.info 2016
отнасят до администрацията

    Начална страница