Системное_программирование. Практикум для студентов специальностей 140 01 02 Информационные системы и технологии
Скачать 1.66 Mb.
|
Блокировка физической памяти RAM Функция VirtualLockc заблокирует в оперативной памяти блок. // блокировка памяти case IDC_LOCK_MEMORY: if(pMemory != NULL) { if ( bLock == false) { VirtualQuery(pMemory,&memInfo,sizeof(MEMORY_BASIC_INF ORMATION)); if (memInfo.Protect != PAGE_NOACCESS) { if (VirtualLock( memInfo.BaseAddress, size)) // Указатель на базовый адрес региона и размер региона { SendDlgItemMessage(hDlg, IDC_LIST, LB_ADDSTRING, NULL, (LPARAM)"Блокировка памяти: "); sprintf(buf, _TEXT("%d%s%Xh"), size, " байт начиная с адреса : ", memInfo.BaseAddress ); SendDlgItemMessage(hDlg, IDC_LIST, LB_ADDSTRING, NULL, (LPARAM)buf); bLock = true; } 31 else MessageBox(hDlg, "Слишком большой размер блока памяти", "Ошибка", MB_ICONEXCLAMATION); } else MessageBox(hDlg, "Нет доступа к памяти", "Ошибка", MB_ICONEXCLAMATION); } else MessageBox(hDlg, "Память уже заблокирована", "Ошибка", MB_ICONEXCLAMATION); } else MessageBox(hDlg, " Память не выделена", "Ошибка", MB_ICONEXCLAMATION); return TRUE; Функция VirtualUnlock разблокирует в оперативной памяти блок. // разблокировка памяти case IDC_UNLOCK_MEMORY: if(pMemory != NULL) { if ( bLock ) { VirtualQuery(pMemory,&memInfo,sizeof(MEMORY_BASIC_INFORMATION)); if (memInfo.Protect != PAGE_NOACCESS) { if (VirtualUnlock( memInfo.BaseAddress, size)) // Указатель на базовый адрес региона и размер региона { SendDlgItemMessage(hDlg, IDC_LIST, LB_ADDSTRING, NULL, (LPARAM)"Разблокировка памяти: "); sprintf(buf, _TEXT("%d%s%Xh"), size, " байт начиная с адреса : ", memInfo.BaseAddress ); SendDlgItemMessage(hDlg, IDC_LIST, LB_ADDSTRING, NULL, (LPARAM)buf); bLock = false; } } else MessageBox(hDlg, "Нет доступа к памяти", "Ошибка", MB_ICONEXCLAMATION); } else MessageBox(hDlg, "Память не заблокирована", "Ошибка", MB_ICONEXCLAMATION); } else MessageBox(hDlg, "Память не выделена", "Ошибка", MB_ICONEXCLAMATION); return TRUE; 32 Стек потока В ряде случаев система сама резервирует некоторые регионы в адресном пространстве процесса. Это делается, например, для раз- мещения блоков переменных окружения процесса и его потоков, а также для размещения стека потока. Когда процесс создает поток, система резервирует регион адрес- ного пространства для стека потока и передает этому региону неко- торый объем физической памяти. По умолчанию система резерви- рует 1Мбайт адресного пространства и передает ему 2 страницы физической памяти. Регион стека и вся физическая память, пере- данная ему, имеют атрибут защиты PAGE_READWRITE. Непо- средственно перед тем, как приступить к исполнению потока, си- стема настраивает регистр потока – указатель стека так, чтобы он указывал на конец верхней страницы региона стека. Это страница, на которой поток начинает пользоваться своим стеком. Следующая страница недоступна и считается защитной (guard page). По мере разрастания дерева вызовов (одновременного обраще- ния ко все большему числу функций) потоку требуется все больший объем стека. Как только он обращается к следующей (защищенной) странице, система уведомляет о происшедшей попытке. Тогда си- стема в ответ на эту попытку передает память еще одной странице, располагая ее прямо под защитной и устанавливая для нее флаг за- щитной страницы. Благодаря такому механизму, размер стека пото- ка будет расти по мере необходимости. Кучи Куча (heap) представляет собой часть памяти, зарезервирован- ную для программы для использования в качестве временного за- поминающего устройства для структур данных, чей размер не мо- жет быть определен, пока программа не запущена. Программа мо- жет затребовать память из кучи для помещения туда подобных элементов, использовать эту память и освобождать ее. При инициализации процесса система создает кучу в его адрес- ном пространстве. Куча, предоставляемая процессу по умолчанию, необходима многим Win32 функциям. Поскольку приложение мо- 33 жет вызвать одновременно несколько таких функций, доступ к куче разрешается только по очереди. Функции работы с кучей позволяют процессу создавать свою собственную кучу. Далее процесс может использовать ряд функций для управления памятью кучи. Нет разницы между выделением па- мяти из собственной кучи и использованием других фукнций для выделения памяти. Функция HeapCreate создает объект собственной кучи. Интер- фейс работы с кучей приведен на рисунке 3.5. case IDC_CREATE_HEAP: if (hHeap == NULL) { hsize = GetDlgItemInt(hDlg, IDC_EDIT_HEAP, &bResult, FALSE); if ( bResult && hsize>0 ) { hHeap = HeapCreate( HEAP_NO_SERIALIZE, hsize, hsize); SendDlgItemMessage(hDlg, IDC_LIST2, LB_ADDSTRING, NULL, (LPARAM)" Создана новая куча "); lpHeap = NULL; } else MessageBox(hDlg, "Неверно задан размер", "Ошиб- ка", MB_ICONEXCLAMATION); } else MessageBox(hDlg, "Куча уже создана", "Ошибка", MB_ICONEXCLAMATION); return true; Рис. 3.5. Создание кучи 34 При создании функции задается как начальный размер, так и максимальный размер кучи. Начальный размер определяет число зафиксированных, доступных для чтения и записи страниц, разме- щенных в памяти. Максимальный размер определяет наибольшее количество зарезервированных страниц. Эти страницы создают не- прерывный блок в виртуальном адресном пространстве процесса, куда куча может расти. Дополнительным страницам автоматически передается физическая память из этого зарезервированного про- странства, если запросы функции HeapAlloc превышают текущий размер зафиксированных страниц, полагая, что физическая память доступна для этого. Страницы, которым однажды была передана физическая память, не могут вернуть ее, пока процесс не завершит- ся или куча не будет уничтожена вызовом функции HeapDestroy. case IDC_DESTROY_HEAP: if (hHeap != NULL) { HeapDestroy(hHeap); int count = SendDlgItemMessage(hDlg, IDC_LIST, LB_GETCOUNT, NULL, NULL); for (int i = 0; i< count; i++ ) SendDlgItemMessage(hDlg, IDC_LIST2, LB_DELETESTRING, 0, NULL); SendDlgItemMessage(hDlg, IDC_LIST2, LB_ADDSTRING, NULL, (LPARAM)" Новая куча не создана"); hHeap = NULL; } else MessageBox(hDlg, "Куча еще не создана", "Ошиб- ка", MB_ICONEXCLAMATION); return true; Память собственной кучи доступна только для создавшего ее про- цесса. Если DLL создает свою кучу, она делает это в адресном про- странстве вызывающего процесса, и куча будет доступна только ему. Память, распределенная функцией HeapAlloc, нельзя переме- щать. Поскольку система не может упаковать собственную кучу, куча может стать фрагментируемой. Память, запрошенная функци- ей HeapCreate, не обязательно будет непрерывной. Память, выде- ленная в пределах кучи фукнцией HeapAlloc – непрерывная. 35 Стандартные библиотечные функции языка Си Win32 приложения могут благополучно использовать возможно- сти управления памятью библиотеки программ этапа исполнения языка Си (malloc, free и т.д.) и Си++ (new, delete и т.д.). Библиотеч- ные функции Си не имеют тех проблем, которые могли возникнуть в 16-ти разрядной Windows. Управление памятью больше не про- блема, потому что система вольна управлять памятью, перемещая страницы физической памяти без задействования виртуальных ад- ресов. В связи с этим различие между ближними (near) и дальними (far) указателями больше не важно. Поэтому можно использовать стандартные библиотечные средства работы с памятью. При этом W in32 функции управления памятью предоставляют больше функ- циональных возможностей. 36 Лабораторная работа № 4 СОЗДАНИЕ И ИСПОЛЬЗОВАНИЕ DLL Цель работы: рассмотреть ряд аспектов создания и использования динамических библиотек DLL в операционной среде Win32. Изучаемые вопросы 1. Функция DllMain. Последовательность вызовов в многопоточ- ном приложении. Назначение и классификация диалоговых окон. 2. Экспорт/импорт функций. 3. Экспорт/импорт ресурсов. 4. Согласование интерфейсов. 5. Загрузка неявно подключаемой DLL. 6. Динамическая загрузка и выгрузка DLL. 7. Вызов функции по номеру. 8. Список динамических библиотек процесса. Постановка задачи Разработать многопоточное Win32-приложение и двe DLL-библио- теки, одна из которых загружается явно, а вторая неявно. Первая дина- мическая библиотека содержит код обработки информации (например, код доступа к системной информации файловых систем FAT или NTFS), вторая – ресурсы типа диалог. Индивидуальное задание полу- чить у преподавателя. Теоретические сведения Функция DllMain. Последовательность вызовов в многопоточном приложении. Большинство библиотек DLL – просто коллекции практически независимых друг от друга функций, экспортируемых в приложе- ния и используемых в них. Кроме функций, предназначенных для экспортирования, в каждой библиотеке DLL есть функция DllMain. Эта функция предназначена для инициализации и очистки DLL. 37 Структура простейшей функции DllMain может выглядеть, например, так: BOOL APIENTRY DllMain(HMODULE hModule,// дескриптор модуля DLL DWORD ul_reason_for_call, // причина вызова функции LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: // Инициализация процесса. case DLL_THREAD_ATTACH: // Инициализация потока. case DLL_THREAD_DETACH: // Очистка структур потока. case DLL_PROCESS_DETACH: // Очистка структур процесса. break; } return TRUE; } Функция DllMain вызывается в нескольких случаях. Причина ее вызова определяется параметром ul_reason_for_call, который может принимать одно из следующих значений. При первой загрузке биб- лиотеки DLL процессом вызывается функция DllMain с dwReason, равным DLL_PROCESS_ATTACH. Каждый раз при создании про- цессом нового потока DllMain вызывается с ul_reason_for_call, рав- ным DLL_THREAD_ATTACH (кроме первого потока, потому что в этом случае ul_reason_for_call равен DLL_PROCESS_ATTACH). По окончании работы процесса с DLL функция DllMain вызывает- ся с параметром ul_reason_for_call, равным DLL_PROCESS_DETACH. При уничтожении потока (кроме первого) ul_reason_for_call будет равен DLL_THREAD_DETACH. В случае успешного завершения функция DllMain должна воз- вращать TRUE. В случае возникновения ошибки возвращается FALSE, и дальнейшие действия прекращаются. Экспорт/импорт функций Чтобы приложение могло обращаться к функциям динамической библиотеки, каждая из них должна занимать строку в таблице экс- портируемых функций DLL. 38 Для того, чтобы сделать функцию или переменную экспортиру- емой, нужно определить их с модификатором extern “C” или квали- фикатором _declspec(dllexport). Так же для определения экспортируемых из Dll функций и пере- менных может использоваться файл определения модуля, который имеет расширение .def. Такой def-файл содержит имя и описание библиотеки, а также список экспортируемых функций. Например, LIBRARY "InfoDLL" EXPORTS ProcesInfo @1 ModuleInfo @2 В строке экспорта функции можно указать ее порядковый номер, поставив перед ним символ @. Этот номер будет затем использо- ваться при обращении к GetProcAddress (). Когда используется статическая компоновка, то необходимо включать в файл проекта приложения соответствующий lib-файл, содержащий нужную вам библиотеку объектных модулей. Такая библиотека содержит исполняемый код модулей, который на этапе статической компоновки включается в exe-файл загрузочного моду- ля. Если же используется динамическая компоновка, в загрузочный exe- файл приложения записывается не исполнимый код функций, а ссылка на соответствующую DLL-библиотеку и функцию внутри нее. Как мы уже говорили, эта ссылка может быть организована с использованием либо имени функции, либо ее порядкового номера в DLL-библиотеке. Для того, чтобы редактор связей мог создать ссылку, в файл про- екта приложения вы должны включить так называемую библиотеку импорта (import library). Эта библиотека создается автоматически системой разработки Microsoft Visual C++. Экспорт/импорт ресурсов Динамическая загрузка применима и к ресурсам DLL. Для этого сначала необходимо вызвать функцию LoadLibrary: hDllResource=LoadLibraryEx("DllResource.dll",0, LOAD_LIBRARY_AS_DATAFILE); 39 Устанавливаем флаг LOAD_LIBRARY_AS_DATAFILE,так как DLL содержит только ресурсы и никаких функций и а также его нужно указывать для загрузки EXE. Для экспортирования ресурсов требуется переименовать resource.h, а затем добавить данный файл к коду нашего приложе- ния, например, как приведено ниже. #include "DllResource.h" Согласование интерфейсов При использовании собственных библиотек или библиотек неза- висимых разработчиков придется обратить внимание на согласова- ние вызова функции с ее прототипом. По умолчанию в Visual C++ интерфейсы функций согласуются по правилам C++. Это значит, что параметры заносятся в стек спра- ва налево, вызывающая программа отвечает за их удаление из стека при выходе из функции и расширении ее имени. Расширение имен (name mangling) позволяет редактору связей различать перегружен- ные функции, т.е. функции с одинаковыми именами, но разными списками аргументов. Однако в старой библиотеке С функции с расширенными именами отсутствуют. Хотя все остальные правила вызова функции в С идентичны правилам вызова функции в C++, в библиотеках С имена функций не расширяются. К ним только добавляется впереди символ под- черкивания (_). Если необходимо подключить библиотеку на С к приложению на C++, все функции из этой библиотеки придется объявить как внеш- ние в формате С: extern "С" int MyOldCFunction(int myParam); Объявления функций библиотеки обычно помещаются в файле заголовка этой библиотеки, хотя заголовки большинства библиотек С не рассчитаны на применение в проектах на C++. В этом случае необходимо создать копию файла заголовка и включить в нее мо- дификатор extern "C" к объявлению всех используемых функций библиотеки. Модификатор extern "C" можно применить и к целому блоку, к которому с помощью директивы #include подключен файл 40 старого заголовка С. Таким образом, вместо модификации каждой функции в отдельности можно обойтись всего тремя строками: extern "С" { #include "MyCLib.h" } В программах для старых версий Windows использовались также соглашения о вызове функций языка PASCAL для функций Windows API. В новых программах следует использовать модифи- катор WINAPI, преобразуемый в _stdcall. Хотя это и не стандарт- ный интерфейс функций С или C++, но именно он используется для обращений к функциям Windows API. Однако обычно все это уже учтено в стандартных заголовках Windows. Загрузка явно подключаемой DLL При запуске приложение пытается найти все файлы DLL, неявно подключенные к приложению, и поместить их в область оператив- ной памяти, занимаемую данным процессом. Поиск файлов DLL операционной системой осуществляется в следующей последова- тельности: 1) Каталог, в котором находится ЕХЕ-файл; 2) Текущий каталог процесса; 3) Системный каталог Windows. Если библиотека DLL не обнаружена, система выводит диалого- вое окно с сообщением о ее отсутствии и путях, по которым осу- ществлялся поиск. Объект-процесс не создается. Если нужная библиотека найдена, она помещается в оператив- ную память процесса, где и остается до его окончания. Теперь при- ложение может обращаться к функциям, содержащимся в DLL. Для неявного подключения библиотеки нужно в папку прило- жения поместить lib- файл и заголовочный файл. А так же, подклю- чить к линковщику скопированный lib-файл. #include "UsageDll.h" #pragma comment(lib, "UsageDll.lib") 41 Динамическая загрузка и выгрузка DLL Для экономии ресурсов и увеличения быстродействия програм- мы можно использовать динамическую загрузку и выгрузку DLL во время выполнения программы. Первое, что необходимо сделать при динамической загрузке DLL, – это поместить модуль библиотеки в память процесса. Дан- ная операция выполняется с помощью функции LoadLibrary, име- ющей единственный аргумент – имя загружаемого модуля. Соот- ветствующий фрагмент программы должен выглядеть так: hInfoDLL=LoadLibrary("InfoDLL.dll"); if(!hInfoDLL) { MessageBox(NULL,"Невозможно загрузить InfoDll.dll!","Ошибка!",MB_OK); TerminateThread(hTh,0); TerminateThread(hTh1,0); } После завершения работы с библиотекой динамической компо- новки, ее можно выгрузить из памяти процесса с помощью функции FreeLibrary: if (!FreeLibrary(hInfoDLL)) { MessageBox(NULL,"Невозможно выгрузить InfoDll.dll"," Oшибка!",MB_OK); } Вызов функции по номеру Для того чтобы вызвать функцию из библиотеки, зная ее иден- тификатор, необходимо получить значение дальнего указателя на эту функцию, вызвав функцию GetProcAddress(HINSTANCE hLibrary, LPCSTR lpszProcName). Через параметр hLibrary необхо- димо передать функции идентификатор DLL-библиотеки, получен- ный ранее от функции LoadLibrary. Параметр lpszProcName является дальним указателем на строку, содержащую имя функции или ее порядковый номер, преобразо- ванный макрокомандой MAKEINTRESOURCE. Для упрощения вызова функции может осуществляться ее вызов не по имени, а по номеру. Для этого в строке экспорта 42 функции можно указать ее порядковый номер, поставив перед ним символ @. LIBRARY "InfoDLL" EXPORTS ProcesInfo @1 ModuleInfo @2 Этот номер будет затем использоваться при обращении к GetProcAddress , как показано ниже: ProcesInfo = (MYPROC) GetProcAddress(hInfoDLL, MAKEINTRESOURCE(1)); ModuleInfo = (MYPROC) GetProcAddress(hInfoDLL,MAKEINTRESOURCE(2) ); На самом деле компилятор присваивает порядковые номера всем экспортируемым объектам. Однако способ, которым он это делает, отчасти непредсказуем, если не присвоить эти номера явно. В строке экспорта можно использовать параметр NONAME. Он запрещает компилятору включать имя функции в таблицу экспор- тирования DLL: MyFunction @1 NONAME Приложения, использующие библиотеку импортирования для неявного подключения DLL, не “заметят” разницы, поскольку при неявном подключении порядковые номера используются автомати- чески. |