7. Вывод информации в окно. Механизм перерисовки окна. Понятие области обновления окна. Операции с областью обновления окна



страница8/21
Дата28.11.2016
Размер2.21 Mb.
Просмотров5934
Скачиваний1
1   ...   4   5   6   7   8   9   10   11   ...   21

Уведомление DLL_PROCESS_ATTACH

Система вызывает DllMain с этим значением параметра fdwReason сразу после того, как DLL спроецирована на адресное пространство процесса. А это происходит, только когда образ DLL-файла проецируется в первый раз. Если затем поток вызовет LoadLibrary(Ex) для уже спроецированной DLL, система просто увеличит счетчик числа пользователей этой DLL; так что DllMain вызывается со значением DLL_PROCESS_ATTACH лишь раз.

Обрабатывая DLL_PROCESS_ATTACH, библиотека должна выполнить в процессе инициализацию, необходимую ее функциям. Например, в DLL могут быть функции, которым нужна своя куча (создаваемая в адресном пространстве процесса). В этом случае DllMain могла бы создать такую кучу, вызвав HeapCreate при обработке уведомления DLL_PROCESS_ATTACH, а описатель созданной кучи сохранить в глобальной переменной, доступной функциям DLL.

При обработке уведомления DLL_PROCESS_ATTACH значение, возвращаемое функцией DllMain, указывает, корректно ли прошла инициализация DLL. Например, если вызов HeapCreate закончился благополучно, следует вернуть TRUE. А если кучу создать не удалось — FALSE. Для любых других значений fdwReason — DLL_PROCESS_DETACH, DLL_THREAD_ATTACH или DLL_THREAD_DETACH — значение, возвращаемое DllMain, системой игнорируется.

Уведомление DLL_PROCESS_DETACH

При отключении DLL от адресного пространства процесса вызывается ее функция DllMain со значением DLL_PROCESS_DETACH в параметре fdwReason. Обрабатывая это значение, DLL должна провести очистку в данном процессе. Например, вызвать HeapDestroy, чтобы разрушить кучу, созданную ею при обработке уведомления DLL_PROCESS_ATTACH. Обратите внимание: если функция DllMain вернула FALSE, получив уведомление DLL_PROCESS_ATTACH, то ее нельзя вызывать с уведомлением DLL_PROCESS_DETACH. Если DLL отключается из-за завершения процесса, то за выполнение кода DllMain отвечает поток, вызвавший ExitProcess (обычно это первичный поток приложения). Когда Ваша входная функция возвращает управление стартовому коду из библиотеки C/C++, тот явно вызывает ExitProcess и завершает процесс.

Если DLL отключается в результате вызова FreeLibrary или FreeLibraryAndExitThread, код DllMain выполняется потоком, вызвавшим одну из этих функций. В случае обращения к FreeLibrary управление не возвращается, пока DllMain не закончит обработку уведомления DLL_PROCESS_DETACH. Учтите также, что DLL может помешать завершению процесса, если, например, ее DllMain входит в бесконечный цикл, получив уведомление DLL_PROCESS_DETACH. Операционная система уничтожает процесс только после того, как все DLL-модули обработают уведомление DLL_PROCESS_DETACH.

Если процесс завершается в результате вызова TerminateProcess, система не вызывает DllMain со значением DLL_PROCESS_DETACH. А значит, ни одна DLL, спроецированная на адресное пространство процесса, не получит шанса на очистку до завершения процесса. Последствия могут быть плачевны — вплоть до потери данных. Вызывайте TerminateProcess только в самом крайнем случае!

Уведомление DLL_THREAD_ATTACH

Когда в процессе создается новый поток, система просматривает все DLL, спроецированные в данный момент на адресное пространство этого процесса, и в каждой из таких DLL вызывает DllMain со значением DLL_THREAD_ATTACH. Тем самым она уведомляет DLL модули о необходимости инициализации, связанной с данным потоком. Только что созданный поток отвечает за выполнение кода в функциях DllMain всех DLL. Работа его собственной (стартовой) функции начинается лишь после того, как все DLL модули обработают уведомление DLL_THREAD_ATTACH.

Если в момент проецирования DLL на адресное пространство процесса в нем выполняется несколько потоков, система не вызывает DllMain со значением DLL_ THREAD_ATTACH ни для одного из существующих потоков. Вызов DllMain с этим значением осуществляется, только если DLL проецируется на адресное пространство процесса в момент создания потока.

Обратите также внимание, что система не вызывает функции DllMain со значением DLL_THREAD_ATTACH и для первичного потока процесса. Любая DLL, проецируемая на адресное пространство процесса в момент его создания, получает уведомление DLL_PROCESS_ATTACH, а не DLL_THREAD_ATTACH.

Уведомление DLL_THREAD_DETACH

Лучший способ завершить поток — дождаться возврата из его стартовой функции, после чего система вызовет ExitThread и закроет поток. Эта функция лишь сообщает системе о том, что поток хочет завершиться, но система не уничтожает его немедленно. Сначала она просматривает все проекции DLL, находящиеся в данный момент в адресном пространстве процесса, и заставляет завершаемый поток вызвать DllMain в каждой из этих DLL со значением DLL_THREAD_DETACH. Тем самым она уведомляет DLL модули о необходимости очистки, связанной с данным потоком. Например, DLL версия библиотеки C/C++ освобождает блок данных, используемый для управления многопоточными приложениями.

Заметьте, что DLL может не дать потоку завершиться. Например, такое возможно, когда функция DllMain, получив уведомление DLL_THREAD_DETACH, входит в бесконечный цикл. А операционная система закрывает поток только после того, как все DLL заканчивают обработку этого уведомления.

Если поток завершается из-за того, что другой поток вызвал для него TerminateThread, система не вызывает DllMain со значением DLL_THREAD_DETACH. Следовательно, ни одна DLL, спроецированная на адресное пространство процесса, не получит шанса на выполнение очистки до завершения потока, что может привести к потере данных. Поэтому TerminateThread, как и TerminateProcess, можно использовать лишь в самом крайнем случае!

Если при отключении DLL еще выполняются какие-то потоки, то для них DllMain не вызывается со значением DLL_THREAD_DETACH. Вы можете проверить это при обработке DLL_PROCESS_DETACH и провести необходимую очистку.

Ввиду упомянутых выше правил не исключена такая ситуация: поток вызывает LoadLibrary для загрузки DLL, в результате чего система вызывает из этой библиотеки DllMain со значением DLL_PROCESS_ATTACH. (В этом случае уведомление DLL_ THREAD_ATTACH не посылается.) Затем поток, загрузивший DLL, завершается, что приводит к новому вызову DllMain — на этот раз со значением DLL_THREAD_DETACH. Библиотека уведомляется о завершении потока, хотя она не получала DLL_ THREAD_ATTACH, уведомляющего о его подключении. Поэтому будьте крайне осторожны при выполнении любой очистки, связанной с конкретным потоком. К счастью, большинство программ пишется так, что LoadLibrary и FreeLibrary вызываются одним потоком.


15. Понятие динамически-загружаемой библиотеки. Создание DLL-библиотеки. Использование DLL-библиотеки в программе методом динамический импорта процедур.

1 и 2 часть вопроса смотри в вопросе номер 14

Загрузка DLL-библиотеки в память:

HMODULE LoadLibrary(LPCTSTR lpFileName);

HMODULE LoadLibraryEx(LPCTSTR lpFileName, _Reserved_ HANDLE hFile, DWORD dwFlags);

HMODULE GetModuleHandle(LPCTSTR lpModuleName); GetModuleHandleEx.

DWORD GetModuleFileName(HMODULE hModule, LPTSTR lpFilename, DWORD nSize);

Освобождение DLL-библиотеки:

bool FreeLibrary(HMODULE hModule); FreeLibraryAndExitThread.

Получение адреса функции в DLL-библиотеке:

void* GetProcAddress(HMODULE hModule, LPCSTR lpProcName);

Применение:

typedef int TMin(int x, int y); // добавить __stdcall

TMin* pMin;

pMin = (TMin*)GetProcAddress(hModule, "_Min@8");

int a = pMin(10, 20);


16. Понятие динамически-загружаемой библиотеки. Создание в DLL-библиотеке разделяемых между приложениями глобальных данных. Разделы импорта и экспорта DLL-библиотеки. Переадресация вызовов процедур DLL-библиотек к другим DLL-библиотекам. Исключение конфликта версий DLL.

1 часть в вопросе номер 14

Разделяемые данные – shared data:

#pragma section("mysection", read, write, shared)

__declspec(allocate("mysection")) int Number = 0;

Экспорт

Если модификатор __declspec(dllexport) указан перед переменной, прототипом функции или C++классом, компилятор Microsoft С/С++ встраивает в конечный OBJфайл дополнительную информацию. Она понадобится компоновщику при сборке DLL из OBJфайлов.

Обнаружив такую информацию, компоновщик создает LIB-файл со списком идентификаторов, экспортируемых из DLL. Этот LIB-файл нужен при сборке любого EXE-модуля, ссылающегося на такие идентификаторы. Компоновщик также вставляет в конечный DLL-файл таблицу экспортируемых идентификаторов — раздел экспорта, в котором содержится список (в алфавитном порядке) идентификаторов экспортируемых функций, переменных и классов. Туда же помещается относительный виртуальный адрес (relative virtual address, RVA) каждого идентификатора внутри DLL-модуля.

Воспользовавшись утилитой DumpBin.exe (с ключом -exports) из состава Microsoft Visual Studio, мы можем увидеть содержимое раздела экспорта в DLL-модуле.



Импорт

Разрешая ссылки на импортируемые идентификаторы, компоновщик создает в конечном EXE-модуле раздел импорта (imports section). В нем перечисляются DLL, необходимые этому модулю, и идентификаторы, на которые есть ссылки из всех используемых DLL.

Воспользовавшись утилитой DumpBin.exe (с ключом -imports), мы можем увидеть содержимое раздела импорта. Ниже показан фрагмент полученной с ее помощью таблицы импорта Calc.exe.

C:\WINNT\SYSTEM32>DUMPBIN >imports Calc.EXE

Microsoft (R) COFF Binary File Dumper Version 6.00.8168

Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

Dump of file calc.exe

File Type: EXECUTABLE IMAGE

Section contains the following imports:

SHELL32.dll

10010F4 Import Address Table

1012820 Import Name Table

FFFFFFFF time date stamp

FFFFFFFF Index of first forwarder reference

77C42983 7A ShellAboutW

MSVCRT.dll

1001094 Import Address Table

10127C0 Import Name Table

FFFFFFFF time date stamp

FFFFFFFF Index of first forwarder reference

78010040 295 memmove

78018124 42 _EH_prolog

78014C34 2D1 toupper

78010F6E 2DD wcschr

78010668 2E3 wcslen
ADVAPI32.dll

1001000 Import Address Table

101272C Import Name Table

FFFFFFFF time date stamp

FFFFFFFF Index of first forwarder reference

779858F4 19A RegQueryValueExA

77985196 190 RegOpenKeyExA

77984BA1 178 RegCloseKey

KERNEL32.dll

100101C Import Address Table

1012748 Import Name Table

FFFFFFFF time date stamp

FFFFFFFF Index of first forwarder reference

77ED4134 336 lstrcpyW

77ED33E8 1E5 LocalAlloc

77EDEF36 DB GetCommandLineW

77ED1610 15E GetProfileIntW

77ED4BA4 1EC LocalReAlloc

Header contains the following bound import information:

Bound to SHELL32.dll [36E449E0] Mon Mar 08 14:06:24 1999

Bound to MSVCRT.dll [36BB8379] Fri Feb 05 15:49:13 1999

Bound to ADVAPI32.dll [36E449E1] Mon Mar 08 14:06:25 1999

Bound to KERNEL32.dll [36DDAD55] Wed Mar 03 13:44:53 1999

Bound to GDI32.dll [36E449E0] Mon Mar 08 14:06:24 1999

Bound to USER32.dll [36E449E0] Mon Mar 08 14:06:24 1999

Summary


2000 .data

3000 .rsrc

13000 .text

Как видите, в разделе есть записи по каждой DLL, необходимой Calc.exe: Shell32.dll, MSVCRt.dll, AdvAPI32.dll, Kernel32.dll, GDI32.dll и User32.dll. Под именем DLL-модуля выводится список идентификаторов, импортируемых программой Calc.exe. Например, Calc.exe обращается к следующим функциям из Kernel32.dll: lstrcpyW, LocalAlloc, GetCommandLineW, GetProfileIntW и др.

Число слева от импортируемого идентификатора называется «подсказкой» (hint) и для нас несущественно. Крайнее левое число в строке для идентификатора сообщает адрес, по которому он размещен в адресном пространстве процесса. Такой адрес показывается, только если было проведено связывание (binding) исполняемого модуля.

Переадресация вызовов функций

Запись о переадресации вызова функции (function forwarder) — это строка в разделе экспорта DLL, которая перенаправляет вызов к другой функции, находящейся в другой DLL. Например, запустив утилиту DumpBin из Visual C++ для Kernel32.dll в Windows 2000, Вы среди прочей информации увидите и следующее.

C:\winnt\system32>DumpBin ��Exports Kernel32.dll

(часть вывода опущена)

. 360 167 HeapAlloc (forwarded to NTDLL.RtlAllocateHeap) 


. 361 168 HeapCompact (000128D9) 


. 362 169 HeapCreate (000126EF) 


. 363 16A HeapCreateTagsW (0001279E) 


. 364 16B HeapDestroy (00012750) 


. 365 16C HeapExtend (00012773) 


. 366 16D HeapFree (forwarded to NTDLL.RtlFreeHeap) 


. 367 16E HeapLock (000128ED) 


. 368 16F HeapQueryTagW (000127B8) 


. 369 170 HeapReAlloc (forwarded to NTDLL.RtlReAllocateHeap) 


. 370 171 HeapSize (forwarded to NTDLL.RtlSizeHeap) 
 (остальное тоже опущено)


Здесь есть четыре переадресованные функции. Всякий раз, когда Ваше приложение вызывает HeapAlloc, HeapFree, HeapReAlloc или HeapSize, его EXE модуль динамически связывается с Kernel32.dll. При запуске EXE модуля загрузчик загружает Kernel32.dll и, обнаружив, что переадресуемые функции на самом деле находятся в NTDLL.dll, загружает и эту DLL. Обращаясь к HeapAlloc, программа фактически вызывает функцию RtlAllocateHeap из NTDLL.dll. А функции HeapAlloc вообще нет!

При вызове HeapAlloc (см. ниже) функция GetProcAddress просмотрит раздел эк порта Kernel32.dll и, выяснив, что HeapAlloc — переадресуемая функция, рекурсивно вызовет сама себя для поиска RtlAllocateHeap в разделе экспорта NTDLL.dll. GetProcAddress(GetModuleHandle("Kernel32"), "HeapAlloc");

Вы тоже можете применять переадресацию вызовов функций в своих DLL. Самый

простой способ — воспользоваться директивой pragma: // переадресация к функции из DllWork

#pragma comment(linker, "/export:SomeFunc=DllWork.SomeOtherFunc")

Эта директива сообщает компоновщику, что DLL должна экспортировать функцию SomeFunc, которая на самом деле реализована как функция SomeOtherFunc в модуле DllWork.dll. Такая запись нужна для каждой переадресуемой функции.

1. Исключение конфликта версий DLL:

a. c:\myapp\myapp.exe загружает старую версию

b. c:\program files\common files\system\mydll.dll,

c. а должен загружать mydll.dll из своего же каталога.

d. n Создать пустой файл c:\myapp\myapp.exe.local.

e. Будет грузиться библиотека c:\myapp\mydll.dll.

f. n Создать каталог c:\myapp\myapp.exe.local.

g. Будет грузиться c:\myapp\myapp.exe.local\mydll.dll.

h. n Создать файл манифеста для приложения. В этом случае .local файлы будут игнорироваться.

Когда разрабатывались первые версии Windows, оперативная память и дисковое пространство были крайне дефицитным ресурсом, так что Windows была рассчитана на предельно экономное их использование — с максимальным разделением между потребителями. В связи с этим Microsoft рекомендовала размещать все модули, используемые многими приложениями (например, библиотеку C/C++ и DLL, относящиеся к MFC) в системном каталоге Windows, где их можно было легко найти.

Однако со временем это вылилось в серьезную проблему: программы установки приложений то и дело перезаписывали новые системные файлы старыми или не полностью совместимыми. Из-за этого уже установленные приложения переставали работать. Но сегодня жесткие диски стали очень емкими и недорогими, оперативная память тоже значительно подешевела. Поэтому Microsoft сменила свою позицию на прямо противоположную: теперь она настоятельно рекомендует размещать все файлы приложения в своем каталоге и ничего не трогать в системном каталоге Windows. Тогда Ваше приложение не нарушит работу других программ, и наоборот.

С той же целью Microsoft ввела в Windows 2000 поддержку перенаправления DLL (DLL redirection). Она заставляет загрузчик операционной системы загружать модули сначала из каталога Вашего приложения и, только если их там нет, искать в других каталогах.

Чтобы загрузчик всегда проверял сначала каталог приложения, нужно всего лишь поместить туда специальный файл. Его содержимое не имеет значения и игнорируется — важно только его имя: оно должно быть в виде AppName.local. Так, если исполняемый файл Вашего приложения — SuperApp.exe, присвойте перенаправляющему файлу имя SuperApp.exe.local.

Функция LoadLibrary(Ex) проверяет наличие этого файла и, если он есть, загружает модуль из каталога приложения; в ином случае LoadLibrary(Ex) работает так же, как и раньше.

Перенаправление DLL исключительно полезно для работы с зарегистрированными COM объектами. Оно позволяет приложению размещать DLL с COM объектами в своем каталоге, и другие программы, регистрирующие те же объекты, не будут мешать его нормальной работе.

17. Понятие объекта ядра ОС Windows. Виды объектов ядра. Атрибуты защиты объекта ядра. Дескриптор защиты объекта ядра. Создание и удаление объектов ядра.

Что такое объект ядра

Создание, открытие и прочие операции с объектами ядра станут для Вас, как разработчика Windows-приложений, повседневной рутиной. Система позволяет создавать и оперировать с несколькими типами таких объектов, в том числе: маркерами доступа (access token objects), файлами (file objects), проекциями файлов (file-mapping objects), портами завершения ввода-вывода (I/O completion port objects), заданиями (job objects), почтовыми ящиками (mailslot objects), мьютексами (mutex objects), каналами (pipe objects), процессами (process objects), семафорами (semaphore objects), потоками (thread objects) и ожидаемыми таймерами (waitable timer objects). Эти объекты создаются Windows-функциями. Каждый объект ядра — на самом деле просто блок памяти, выделенный ядром и доступный только ему. Этот блок представляет собой структуру данных, в элементах которой содержится информация об объекте. Некоторые элементы (дескриптор защиты, счетчик числа пользователей и др.) присутствуют во всех объектах, но бо҆льшая их часть специфична для объектов конкретного типа. Например, у объекта «процесс» есть идентификатор, базовый приоритет и код завершения, а у объекта «файл» — смещение в байтах, режим разделения и режим открытия. Поскольку структуры объектов ядра доступны только ядру, приложение не может самостоятельно найти эти структуры в памяти и напрямую модифицировать их содержимое. Такое ограничение Microsoft ввела намеренно, чтобы ни одна программа не нарушила целостность структур объектов ядра. Это же ограничение позволяет Microsoft вводить, убирать или изменять элементы структур, не нарушая работы каких-либо приложений. Но вот вопрос: если мы не можем напрямую модифицировать эти структуры, то как же наши приложения оперируют с объектами ядра? Ответ в том, что в Windows предусмотрен набор функций, обрабатывающих структуры объектов ядра по строго определенным правилам. Мы получаем доступ к объектам ядра только через эти функции. Когда Вы вызываете функцию, создающую объект ядра, она возвращает описатель, идентифицирующий созданный объект. Описатель следует рассматривать как «непрозрачное» значение, которое может быть использовано любым потоком Вашего процесса. Этот описатель Вы передаете Windows-функциям, сообщая системе, какой объект ядра Вас интересует. Но об описателях мы поговорим позже (в этой главе). Для большей надежности операционной системы Microsoft сделала так, чтобы значения описателей зависели от конкретного процесса. Поэтому, если Вы передадите такое значение (с помощью какого-либо механизма межпроцессной связи) потоку другого процесса, любой вызов из того процесса со значением описателя, полученного в Вашем процессе, даст ошибку.



Учет пользователей объектов ядра

Объекты ядра принадлежат ядру, а не процессу. Иначе говоря, если Ваш процесс вызывает функцию, создающую объект ядра, а затем завершается, объект ядра может быть не разрушен. В большинстве случаев такой объект все же разрушается; но если созданный Вами объект ядра используется другим процессом, ядро запретит разрушение объекта до тех пор, пока от него не откажется и тот процесс. Ядру известно, сколько процессов использует конкретный объект ядра, поскольку в каждом объекте есть счетчик числа его пользователей. Этот счетчик — один из элементов данных, общих для всех типов объектов ядра. В момент создания объекта счетчику присваивается 1. Когда к существующему объекту ядра обращается другой процесс, счетчик увеличивается на 1. А когда какой-то процесс завершается, счетчики всех используемых им объектов ядра автоматически уменьшаются на 1. Как только счетчик какого-либо объекта обнуляется, ядро уничтожает этот объект.



Защита

Объекты ядра можно защитить дескриптором защиты (security descriptor), который описывает, кто создал объект и кто имеет права на доступ к нему. Дескрипторы защиты обычно используют при написании серверных приложений; создавая клиентское приложение, Вы можете игнорировать это свойство объектов ядра.

Почти все функции, создающие объекты ядра, принимают указатель на структуру SECURITY_ATTRIBUTES как аргумент, например:

HANDLE CreateFileMapping( HANDLE hFile, PSECURITY_ATTRIBUTES psa, DWORD flProtect, DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, PCTSTR pszName);

Большинство приложений вместо этого аргумента передает NULL и создает объект с защитой по умолчанию. Такая защита подразумевает, что создатель объекта и любой член группы администраторов получают к нему полный доступ, а все прочие к объекту не допускаются. Однако Вы можете создать и инициализировать структуру SECURITY_ATTRIBUTES, а затем передать ее адрес. Она выглядит так:

typedef struct _SECURITY_ATTRIBUTES {

DWORD nLength;

LPVOID lpSecurityDescriptor;

BOOL bInheritHandle;

} SECURITY_ATTRIBUTES;

Хотя структура называется SECURITY_ATTRIBUTES, лишь один ее элемент имеет отношение к защите — lpSecurityDescriptor. Если надо ограничить доступ к созданному Вами объекту ядра, создайте дескриптор защиты и инициализируйте структуру SECURITY_ATTRIBUTES следующим образом:

SECURITY_ATTRIBUTES sa;

sa.nLength = sizeof(sa); // используется для выяснения версий

sa.lpSecurityDescriptor = pSD; // адрес инициализированной SD

sa.bInheritHandle = FALSE; // об этом позже

HANDLE hFileMapping = CreateFileMapping(INVALID_HANDLE_VALUE, &sa, PAGE_READWRITE, 0, 1024, "MyFileMapping");

Желая получить доступ к существующему объекту ядра (вместо того чтобы создавать новый), необходимо указать, какие операции Вы намерены проводить над объектом.

Кроме объектов ядра Ваша программа может использовать объекты других типов — меню, окна, курсоры мыши, кисти и шрифты. Они относятся к объектам User или GDI. Новичок в программировании для Windows может запутаться, пытаясь отличить объекты User или GDI от объектов ядра. Как узнать, например, чьим объектом — User или ядра — является данный значок? Выяснить, не принадлежит ли объект ядру, проще всего так: проанализировать функцию, создающую объект. Практически у всех функций, создающих объекты ядра, есть параметр, позволяющий указать атрибуты защиты, — как у CreateFileMapping. В то же время у функций, создающих объекты User или GDI, нет параметра типа PSECURITY_ATTRIBUTES.




Поделитесь с Вашими друзьями:
1   ...   4   5   6   7   8   9   10   11   ...   21


База данных защищена авторским правом ©nethash.ru 2019
обратиться к администрации

войти | регистрация
    Главная страница


загрузить материал