Създаване и използване на динамични библиотеки

Материал от PIE.Wiki

Не всички негови функции са достъпни за процеса, заредил DLL, а само изрично предоставените от самия DLL за „външния свят“ – т.нар. изнесени. Функции, предназначени само за "вътрешна" употреба, няма смисъл да се експортират (въпреки че не са забранени). Колкото повече функции експортира DLL, толкова по-бавно се зарежда; следователно дизайнът на интерфейса (начинът, по който DLL взаимодейства с извикващия код) трябва да се вземе по-внимателно. Добрият интерфейс е интуитивен за програмиста, лаконичен и елегантен: както се казва, нито добавяне, нито отнемане. Невъзможно е да се дадат строги препоръки по този въпрос - умението идва с опит.

Съдържание

Създаване на DLL в CodeBlocks

За да създадете DLL проект в средата за разработка на CodeBlocks, когато създавате нов проект, изберете елемента „Библиотека за динамични връзки“. Създаденият проект ще съдържа два файла main.cpp и main.h.

За да експортирате функция от DLL, трябва да посочите ключовата дума __declspec(dllexport) преди нейното описание.

Входна точка на DLL

Всяка динамична библиотека има входна точка. Входна точка е функция със специфично име, която се извиква от операционната система при няколко специални случая. Името по подразбиране за тази функция е DllMain.

При извикване на функция системата предава следните параметри:

Използвайки тази функция, DLL може да прихване четири различни събития, чиито кодове са посочени в параметъраfdwReason.

DLL_PROCESS_ATTACHновият процес зарежда DLL автоматично или с функцията LoadLibrary.

DLL_PROCESS_DETACHПроцесът, използвал DLL, го прекратява или разтоварва с функцията FreeLibrary.

DLL_THREAD_ATTACHв един от използваните процесиDLL създаде нова нишка.

DLL_THREAD_DETACHНишка е прекъсната в един от процесите, използващи DLL.

Извикване на функции от DLL

Има два начина за зареждане на DLL: сявноиимплицитно свързване.

Симплицитно свързване, функциите на заредената DLL се добавят към секцията за импортиране на извикващия файл. Когато се стартира такъв файл, товарачът на операционната система анализира секцията за импортиране и включва всички посочени библиотеки. Поради своята простота, този метод е много популярен; но имплицитното свързване има определени недостатъци и ограничения:

  1. Всички свързани DLL файлове винаги се зареждат, дори ако програмата никога не извиква нито един от тях по време на цялата сесия;
  2. Ако поне една от необходимите DLL липсва (или DLL не експортира поне една от необходимите функции), зареждането на изпълнимия файл се прекъсва, дори ако липсата на тази DLL не е критична за изпълнението на програмата.
  3. DLL се търси в следния ред: в директорията, съдържаща извикващия файл; в текущата директория на процеса; в системната директория %Windows%System%; в главната директория %Windows%; в директориите, посочени в променливата PATH. Невъзможно е да зададете различен път за търсене (или по-скоро е възможно, но това ще изисква промени в системния регистър и тези промени ще засегнат всички процеси, изпълнявани в системата - което не е добре).

Изричното свързванеелиминира всички тези недостатъци - с цената на известна сложност на кода. Самият програмист ще трябва да се погрижи за зареждането на DLL и свързването на експортираните функции (без да забравяме за контрола на грешките, в противен случай в един момент всичко ще приключи със замразяване на системата). Но изричното свързване ви позволява да зареждате DLL файлове според нуждите идава възможност на програмиста самостоятелно да се справя със ситуации с липсващ DLL. Можете да отидете по-далеч - не задавайте изрично името на DLL в програмата, а сканирайте такава и такава директория за наличие на динамични библиотеки и свържете всички намерени към приложението. Ето как работи механизмът за поддръжка на плъгини в популярния файлов мениджър FAR (и не само в него).

По този начине целесъобразно да се използва имплицитно свързване само за свързване на динамични библиотеки, заредени във всяка сесия, които са жизненоважни за работата на приложението; във всички останали случаи се предпочита изрично свързване.

Зареждане на DLL с неявно свързване

За да извикате функция от DLL, тя трябва да бъде декларирана в извикващия код или катоexternal(т.е. като обикновена външна функция), или предшествана от ключовата дума__declspec(dllimport). Първият метод е по-популярен, но вторият все още е за предпочитане - в този случай компилаторът, разбирайки, че функцията се извиква от DLL, ще може да оптимизира съответно кода. Например:

Зареждане на DLL с изрично свързване

Изричното зареждане на динамични библиотеки се извършва от функцията

или неговия разширен аналог

получаваме указател към функциятаlpProcName, експортирана от този DLL. И двете функции връщатNULL, когато възникне грешка. След приключване на работата с динамичната библиотека тя трябва да бъде освободена чрез извикване на функцията

Разтоварване на динамични библиотеки от паметта

Когато заредената динамична библиотека вече не е необходима, тя може да бъде освободена чрез извикване на функцията

и му предаване на манипулатора на библиотеката, върнат преди това от функциятаLoadLibrary. Обърнете внимание - DLL може да бъде освободен, но не и разтоварен! Разтоварването на DLL от паметта дори не е гарантираноако всички процеси, които са го заредили преди това, са приключили работа с него.

Забавянето на разтоварването е предвидено специално - в случай че същата DLL е необходима отново след известно време от някакъв процес. Този трик оптимизира производителността на често използвани DLL файлове, но не е подходящ за рядко използвани DLL файлове, които се зареждат само веднъж за кратко време. Няма документирани начини за принудително разтоварване на динамична библиотека от паметта; и тези, които са - работят с ядрото на ниско ниво и не могат да се похвалят с преносимост. Затова няма да ги разглеждаме тук. В допълнение, тактиката за освобождаване и разтоварване на DLL файлове се прилага по различен начин във всяка версия на Windows: Microsoft, опитвайки се да намери най-добрата стратегия, постоянно променя този алгоритъм; и поради това отказва да го документира. Невъзможно е да не се обърне внимание на едно много важно обстоятелство: динамичната библиотека не притежава никакви ресурси - те притежават, независимо от метода на свързване, процеса, който я е заредил. Динамичната библиотека може да отваря файлове, да разпределя памет и т.н., но паметта няма да бъде автоматично освободена след извикването наFreeLibraryи файловете няма да бъдат затворени сами - всичко това ще се случи едва след като процесът приключи, но не и преди! Естествено, ако самият програмист не освободи всички ненужни ресурси ръчно, използвайкиCloseHandle,FreeMemoryи подобни функции.