Funkcie z dll. Používanie rozhraní pri práci s DLL. Dynamické načítavanie a vykladanie knižníc DLL

  • 31.10.2019

Použitie DLL(dynamická knižnica) je široko používaná v programovaní Windows. DLL vlastne časť kódu spustiteľného súboru s príponou DLL. Každý program môže volať DLL.

Výhoda DLL je nasledujúci:

  • Opätovné použitie kódu.
  • Zdieľanie kódu medzi aplikáciami.
  • Rozdelenie kódu.
  • Zlepšite spotrebu zdrojov v systéme Windows.

Vytvorenie knižnice DLL

Na jedálnom lístku súbor vyberte Nový -> Iný . V dialógovom okne na karte Nový vyberte Sprievodca DLL. Automaticky sa vytvorí modul – prázdna šablóna pre budúcnosť DLL.

Syntax DLL

Aby sa stavalo DLL, vyberte Projekt -> Vytvoriť Názov projektu .

Viditeľnosť funkcií a procedúr

Funkcie a procedúry môžu byť lokálne a exportované z DLL.

Miestne

Miestne funkcie a postupy je možné použiť interne DLL. Sú viditeľné iba vo vnútri knižnice a zvonku ich nemôže vyvolať žiadny program.

vyvezené

Exportované funkcie a postupy je možné použiť vonku DLL. Iné programy môžu volať takéto funkcie a procedúry.

Vyššie uvedený zdrojový kód používa exportovanú funkciu. Názov funkcie nasleduje za rezervovaným slovom exportov.

Načítanie DLL

V Delphi existujú dva typy sťahovania DLL:

Statické zaťaženie

Keď spustíte aplikáciu, automaticky sa načíta. Zostáva v pamäti počas trvania programu. Veľmi jednoduché použitie. Stačí pridať slovo externé po deklarácii funkcie alebo procedúry.

Funkcia SummaTotal(faktor: integer): integer; Registrovať; externý "Example.dll";

Ak DLL nenájdený, program bude pokračovať v práci.

načítať do pamäte podľa potreby. Jeho implementácia je zložitejšia, pretože si ho musíte načítať a vyložiť z pamäte sami. Pamäť sa využíva ekonomickejšie, takže aplikácia beží rýchlejšie. Programátor sám musí zabezpečiť, aby všetko fungovalo správne. Na to potrebujete:

  • Uveďte typ opísanej funkcie alebo postupu.
  • Nahrajte knižnicu do pamäte.
  • Získajte adresu funkcie alebo procedúry v pamäti.
  • Zavolajte funkciu alebo procedúru.
  • Uvoľnite knižnicu z pamäte.

Deklarácia typu popisujúca funkciu

typ TSumaTotal = funkcia(faktor: integer): integer;

Načítava sa knižnica

dll_instance:= LoadLibrary("Example_dll.dll");

Získanie ukazovateľa na funkciu

@SummaTotal:= GetProcAddress(dll_instance, "SummaTotal");

Volanie funkcie

Label1.Caption:= IntToStr(SummaTotal(5));

Uvoľnenie knižnice z pamäte

FreeLibrary(dll_instance);

Dynamický hovor DLL vyžaduje viac práce, ale je jednoduchšie spravovať prostriedky v pamäti. Ak musíte použiť DLL v programe, potom je vhodnejšie statické načítanie. Nezabudnite použiť blok skús...okrem a skús...konečne aby sa predišlo chybám pri vykonávaní programu.

Export riadkov

Vytvorené DLL pomocou Delphi, možno použiť aj v programoch napísaných v iných programovacích jazykoch. Z tohto dôvodu nemôžeme použiť žiadny typ údajov. Typy, ktoré existujú v Delphi, nemusia existovať v iných jazykoch. Je vhodné použiť natívne dátové typy z Linuxu alebo Windowsu. náš DLL môže byť použitý iným programátorom, ktorý používa iný programovací jazyk.

Môže byť použité linky a dynamické polia v DLL napísaný v Delphi, ale na to musíte zahrnúť modul ShareMem do sekcie používa v DLL a program, ktorý ho bude používať. Okrem toho musí byť toto vyhlásenie prvé v sekcii používa každý súbor projektu.

typy reťazec, ako vieme, C, C++ a iné jazyky neexistujú, takže je vhodné použiť namiesto toho PChar.

Príklad DLL

knižnica Example_dll; používa SysUtils, Classes; var ( Deklarovať premenné ) k1: celé číslo; k2: celé číslo; ( Deklarujte funkciu ) function SummaTotal(faktor: integer): integer; Registrovať; počiatočný faktor:= faktor * k1 + faktor; faktor:= faktor * k2 + faktor; vysledok:= faktor; koniec; (Exportujeme funkciu pre ďalšie použitie programom) exportuje SummaTotal; ( Inicializácia premenných ) begin k1:= 2; k2:= 5; koniec.

Príklad volania funkcie z knižnice DLL

jednotka Unit1; rozhranie používa Windows, správy, SysUtils, varianty, triedy, grafiku, ovládacie prvky, formuláre, dialógy, StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); súkromné ​​(Súkromné ​​vyhlásenia) verejné (Verejné vyhlásenia) koniec; typ TSummaTotal = funkcia(faktor: integer): integer; var Form1: TForm1; implementácia ($R *.dfm) procedure TForm1.Button1Click(Sender: TObject); var dll_instance: Handle; SummaTotal: TSummaTotal; begin dll_instance:= LoadLibrary("Example_dll.dll"); @SummaTotal:= GetProcAddress(dll_instance, "SummaTotal"); Label1.Caption:= IntToStr(SummaTotal(5)); FreeLibrary(dll_instance); koniec; koniec.

Ako asi viete, knižnice s dynamickými odkazmi (DLL) používajú pri deklarovaní exportovaných objektov konvencie jazyka C, zatiaľ čo C++ používa pri kompilácii trochu iný systém pomenovania, takže nemôžete iba exportovať funkcie - metódy triedy C++. a potom ich použiť v kód klientskej aplikácie (ďalej len klient znamená aplikáciu využívajúcu DLL). Dá sa to však urobiť pomocou rozhraní, ktoré sú dostupné pre knižnicu DLL aj pre klientsku aplikáciu. Táto metóda je veľmi výkonná a zároveň elegantná. klient vidí iba abstraktné rozhranie a skutočná trieda, ktorá implementuje všetky funkcie, môže byť čokoľvek. Technológia COM (Component Object Model) od Microsoftu je postavená na podobnej myšlienke (samozrejme plus dodatočná funkcionalita): neskorá väzba (za spustenia programu).

Ak ste niekedy pracovali s DLL, už viete, že DLL má špeciálnu funkciu DllMain(). Táto funkcia je podobná funkcii WinMain alebo main() v tom zmysle, že je akýmsi vstupným bodom do knižnice DLL. Operačný systém automaticky volá túto funkciu pri načítaní a uvoľnení knižnice DLL. Zvyčajne sa táto funkcia nepoužíva na nič iné.

Existujú dva spôsoby prepojenia DLL s projektom – skoré (počas kompilácie programu) a neskoré (počas vykonávania programu) prepojenie. Metódy sa líšia v tom, ako sa načíta knižnica DLL a ako sa volajú funkcie implementované a exportované z knižnice DLL.

Včasná väzba (počas kompilácie programu)

Pri tejto metóde prepojenia operačný systém automaticky načíta knižnicu DLL počas spúšťania programu. Vyžaduje sa však, aby súbor .lib (súbor knižnice) zodpovedajúci tejto knižnici DLL bol zahrnutý do vývojového projektu. Tento súbor definuje všetky exportované objekty DLL. Deklarácie môžu obsahovať bežné C funkcie alebo triedy. Všetko, čo klient musí urobiť, je použiť tento súbor .lib a zahrnúť hlavičkový súbor DLL - a OS automaticky načíta túto knižnicu DLL. Ako vidíte, táto metóda vyzerá veľmi jednoducho. všetko je transparentné. Mali by ste si však všimnúť, že kód klienta je potrebné prekompilovať vždy, keď sa zmení kód DLL a podľa toho sa vygeneruje nový súbor .lib. Je len na vás, či je to pre vašu aplikáciu vhodné. DLL môže deklarovať funkcie, ktoré chce exportovať, dvoma spôsobmi. Štandardnou metódou je použitie súborov .def. Takýto súbor .def je jednoducho zoznamom funkcií exportovaných z knižnice DLL.

//================================================= =========== // súbor .def KNIŽNICA myfirstdll.dll POPIS "Moja prvá knižnica DLL" EXPORTUJE MyFunction //=================== = ======================================= // hlavička DLL, ktorá sa má zahrnúť do bool kódu klienta MyFunction(int parms); //================================================= =========== // implementácia funkcie v DLL bool MyFunction(int parms) ( // urob čokoľvek............ )

Myslím, že je bezpečné povedať, že v tomto príklade sa exportuje iba jedna funkcia MyFunction. Druhý spôsob deklarovania exportovaných objektov je špecifický, ale oveľa výkonnejší: exportovať môžete nielen funkcie, ale aj triedy a premenné. Pozrime sa na úryvok kódu vygenerovaný pri vytváraní VisualC++ AppWizard DLL Komentáre zahrnuté vo výpise stačia na pochopenie toho, ako to celé funguje.

//================================================= =========== // Hlavička knižnice DLL, ktorá sa má zahrnúť do kódu klienta /* Nasledujúci blok ifdef je štandardná metóda vytvárania makra, ktorá uľahčuje exportovanie z knižnice DLL. Všetky súbory tejto knižnice DLL sú skompilované so špecifickým kľúčom MYFIRSTDLL_EXPORTS. Tento kľúč nie je definovaný pre žiadny z projektov, ktoré používajú túto knižnicu DLL. Každý projekt, ktorý obsahuje tento súbor, teda vidí funkcie MYFIRSTDLL_API ako importované z knižnice DLL, zatiaľ čo samotná knižnica DLL vidí rovnaké funkcie ako pri exporte. */ #ifdef MYFIRSTDLL_EXPORTS #define MYFIRSTDLL_API __declspec(dllexport) #else #define MYFIRSTDLL_API __declspec(dllimport) #endif // Trieda je exportovaná z triedy test2.dll MYFIRSTDLL_API (tu public:/CMFirstDy:void) môžete pridať svoje vlastné metódy. ); extern MYFIRSTDLL_API int nMyFirstDll; MYFIRSTDLL_API int fnMyFunction(void);

Kľúč MYFIRSTDLL_EXPORTS je definovaný v čase kompilácie DLL, takže kľúčové slovo __declspec(dllexport) je nahradené pred deklaráciami exportovaných objektov. A keď je kód klienta skompilovaný, tento kľúč je nedefinovaný a objekty majú predponu __declspec(dllimport), takže klient vie, ktoré objekty sa importujú z knižnice DLL.

V oboch prípadoch všetko, čo klient potrebuje urobiť, je pridať súbor myfirstdll.lib do projektu a zahrnúť hlavičkový súbor, ktorý deklaruje objekty, ktoré sa majú importovať z knižnice DLL, a potom tieto objekty (funkcie, triedy a premenné) presne použiť. ako keby boli definované a implementované lokálne v projekte. Teraz sa pozrime na inú metódu použitia knižnice DLL, ktorá je často pohodlnejšia a výkonnejšia.

Neskorá väzba (keď je program spustený)

Pri použití neskorej väzby sa DLL nenačíta automaticky pri spustení programu, ale priamo v kóde tam, kde je to potrebné. Nie je potrebné používať žiadne súbory .lib, takže pri zmene knižnice DLL nie je potrebné prekompilovať klientsku aplikáciu. Toto prepojenie je účinné práve preto, že VY rozhodujete, kedy a ktorú knižnicu DLL načítať. Napríklad píšete hru, ktorá používa DirectX a OpenGL. Do spustiteľného súboru môžete jednoducho zahrnúť všetok potrebný kód, ale potom bude jednoducho nemožné nič pochopiť. Alebo môžete vložiť kód DirectX do jednej knižnice DLL a kód OpenGL do inej a staticky ich prepojiť do projektu. Teraz je však všetok kód vzájomne závislý, takže ak napíšete novú knižnicu DLL obsahujúcu kód DirectX, budete musieť prekompilovať aj spustiteľný súbor. Jediná výhoda je, že sa nemusíte starať o načítanie (aj keď neviem, či je to výhoda, ak načítate obe knižnice DLL, pričom zaberáte pamäť, keď skutočne potrebujete iba jednu z nich). Nakoniec, podľa môjho názoru, najlepší nápad je nechať spustiteľný súbor rozhodnúť, ktorú DLL načítať pri spustení. Napríklad, ak program zistil, že systém nepodporuje akceleráciu OpenGL, potom je lepšie načítať DLL s kódom DirectX, inak načítať OpenGL. Preto neskoré prepojenie šetrí pamäť a znižuje závislosť medzi DLL a spustiteľným súborom. V tomto prípade sa však na exportované objekty vzťahuje obmedzenie – exportovať možno iba funkcie v štýle C. Triedy a premenné nemožno načítať, ak program používa neskorú väzbu. Pozrime sa, ako obísť toto obmedzenie pomocou rozhraní.

DLL navrhnutá pre neskoré viazanie zvyčajne používa súbor .def na definovanie objektov, ktoré chce exportovať. Ak nechcete použiť súbor .def, môžete jednoducho použiť predponu __declspec(dllexport) pred exportovanými funkciami. Obe metódy robia to isté. Klient načíta knižnicu DLL odoslaním názvu súboru DLL do funkcie Win32 LoadLibrary() Táto funkcia vráti popisovač HINSTANCE, ktorý sa používa na prácu s knižnicou DLL a je potrebný na uvoľnenie knižnice DLL z pamäte, keď už nie je potrebná. Po načítaní knižnice DLL môže klient pomocou funkcie GetProcAddress() získať ukazovateľ na ľubovoľnú funkciu, pričom ako parameter použije názov požadovanej funkcie.

//================================================= =========== // súbor .def KNIŽNICA myfirstdll.dll POPIS "Moja prvá knižnica DLL" EXPORTUJE MyFunction //=================== = ======================================= /* Implementácia funkcie v knižnici DLL */ bool MyFunction( int parms) ( //urobte niečo............ ) //============================ ================================= //Kód klienta /* Deklarácia funkcie je skutočne potrebná iba pre definujte parametre. Deklarácie funkcií sú zvyčajne obsiahnuté v hlavičkovom súbore, ktorý sa dodáva s knižnicou DLL. Kľúčové slovo extern C v deklarácii funkcie hovorí kompilátoru, aby použil konvencie pomenovania premenných C. */ extern "C" bool MyFunction(int parms); typedef bool (*MYFUNCTION)(int parms); MYFUNCTION pfnMyFunc=0; //ukazovateľ na MyFunction HINSTANCE hMyDll = ::LoadLibrary("myfirstdll.dll"); if(hMyDll != NULL) ( //Určite adresu funkcie pfnMyFunc= (MYFUNCTION)::GetProcAddress(hMyDll, "MyFunction"); //Ak je neúspešné, uvoľnite DLL if(pfnMyFunc== 0) ( :: FreeLibrary(hMyDll) ; return; ) //Zavolajte funkciu bool result = pfnMyFunc(parms); //Uvoľnite knižnicu DLL, ak ju už nepotrebujeme::FreeLibrary(hMyDll); )

Ako vidíte, kód je celkom priamočiary. A teraz sa pozrime, ako sa dá realizovať práca s „triedami“. Ako už bolo spomenuté, ak sa použije neskorá väzba, neexistuje priamy spôsob importu tried z knižnice DLL, takže musíme implementovať „funkčnosť“ triedy s rozhraním obsahujúcim všetky verejné funkcie s výnimkou konštruktora a deštruktora. Rozhranie bude bežnou štruktúrou C/C++ obsahujúcou iba virtuálne abstraktné členské funkcie. Aktuálna trieda v DLL bude dediť z tejto štruktúry a bude implementovať všetky funkcie definované v rozhraní. Teraz, aby sme mali prístup k tejto triede z klientskej aplikácie, všetko, čo musíme urobiť, je exportovať funkcie štýlu C zodpovedajúce inštancii triedy a naviazať ich na rozhranie, ktoré sme definovali, aby ich klient mohol používať. Na implementáciu tejto metódy sú potrebné dve ďalšie funkcie, z ktorých jedna vytvorí rozhranie a druhá vymaže rozhranie po dokončení práce s ním. Príklad implementácie tejto myšlienky je uvedený nižšie.

//================================================= =========== // súbor .def LIBRARY myinterface.dll POPIS "implementuje rozhranie I_MyInterface EXPORTUJE GetMyInterface FreeMyInterface //===================== ====================================== // Súbor hlavičky používaný v knižnici DLL a klientovi, // ktorý deklaruje rozhranie // I_MyInterface.h struct I_MyInterface ( virtual bool Init(int parms)=0; virtual bool Release()=0; virtual void DoStuff() =0; ); /* Deklarácie exportovaných funkcií Dll a ukazovateľ funkcie definície typov pre jednoduché načítanie a prácu s funkciami Všimnite si predponu extern "C", ktorá hovorí kompilátoru, že sa používajú funkcie v štýle C */ extern "C" ( HRESULT GetMyInterface(I_MyInterface ** pInterface); typedef HRESULT (*GETINTERFACE )(I_MyInterface ** pInterface); HRESULT FreeMyInterface(I_MyInterface ** pInterface); typedef HRESULT (*FREEINTERFACE)(I_MyInterface ** pInterface); ) //================= ============================= ============== //Implementácia rozhrania v Dll // trieda MyInterface.h CMyClass: public I_MyInterface ( public: bool Init(int parms); boolRelease(); void DoStuff(); CMyClass(); ~CMyClass(); //akýkoľvek ďalší členovia triedy............ private: //akýkoľvek členovia triedy............ ); //================================================= =========== // Exportované funkcie, ktoré vytvárajú a ničia rozhranie // Dllmain.h HRESULT GetMyInterface(I_MyInterface ** pInterface) ( if(!*pInterface) ( *pInterface= new CMyClass; return S_OK ; ) return E_FAIL; ) HRESULT FreeMyInterface(I_MyInterface ** pInterface) ( if(!*pInterface) return E_FAIL; delete *pInterface; *pInterface= 0; return S_OK; ) //========== = ================================================= // Kód klienta //Deklarácie rozhrania a volania funkcií GETINTERFACE pfnInterface=0;//ukazovateľ na funkciu GetMyInterface I_MyInterface * pInterface =0;//ukazovateľ na štruktúru MyInterface HINSTANCE hMyDll = ::LoadLibrary("myinterface.dll"); if(hMyDll != NULL) ( //Určite adresu funkcie pfnInterface= (GETINTERFACE)::GetProcAddress(hMyDll, "GetMyInterface"); //Uvoľnite DLL, ak predchádzajúca operácia zlyhala if(pfnInterface == 0) ( ::FreeLibrary (hMyDll); return; ) //Zavolajte funkciu HRESULT hr = pfnInterface(&pInterface); //Uvoľnite v prípade neúspešného pokusu if(FAILED(hr)) ( ::FreeLibrary(hMyDll); return; ) //Rozhranie je načítané, môžete volať funkcie pInterface->Init(1); pInterface->DoStuff(); pInterface->Release(); //Rozhranie uvoľnenia FREEINTERFACE pfnFree = (FREEINTERFACE)::GetProcAddress(hMyDll,"FreeMyInterface"); ak (pfnFree! = 0) pfnFree(&hMyDll); //Uvoľnenie knižnice DLL::FreeLibrary(hMyDll); )

Tieto informácie sú dostatočné na to, aby ste pocítili všetky výhody používania rozhraní. Šťastné programovanie!

Existujú tri spôsoby, ako načítať DLL:

a) implicitné;

c) oneskorené.

Zvážte implicitné načítanie DLL. Ak chcete vytvoriť aplikáciu, ktorá je navrhnutá na implicitné načítanie knižnice DLL, musíte mať:

Knižnica obsahuje súbor s popismi použitých objektov z DLL (funkčné prototypy, deklarácie tried a typov). Tento súbor používa kompilátor.

LIB súbor so zoznamom importovaných identifikátorov. Tento súbor je potrebné pridať do nastavení projektu (do zoznamu knižníc, ktoré používa linker).

Projekt je zostavený obvyklým spôsobom. Pomocou objektových modulov a súboru LIB a tiež pri zohľadnení odkazov na importované identifikátory linker (linker, linker) vygeneruje bootovateľný modul EXE. V rámci tohto modulu linker tiež umiestni sekciu importu, v ktorej sú uvedené názvy všetkých požadovaných modulov DLL. Pre každú knižnicu DLL sekcia importu uvádza, na ktoré funkcie a názvy premenných sa odkazuje v kóde spustiteľného súboru. Tieto informácie použije zavádzač operačného systému.

Čo sa stane vo fáze vykonávania klientskej aplikácie? Po spustení modulu EXE vykoná zavádzač operačného systému nasledujúce operácie:

1. Vytvorí virtuálny adresný priestor pre nový proces a premietne doň spustiteľný modul;

2. Analyzuje sekciu importu, identifikuje všetky požadované knižnice DLL a tiež ich premietne do adresného priestoru procesu.

DLL môže importovať funkcie a premenné z inej knižnice DLL. To znamená, že môže mať vlastnú sekciu importu, pre ktorú musíte zopakovať rovnaké kroky. V dôsledku toho môže inicializácia procesu trvať pomerne dlho.

Po namapovaní modulu EXE a všetkých modulov DLL do adresného priestoru procesu je jeho primárne vlákno pripravené na spustenie a aplikácia sa spustí.

Nevýhodou implicitného načítania je povinné načítanie knižnice, aj keď sa jej funkcie nevolajú, a teda povinná požiadavka na prítomnosť knižnice pri prepájaní.

Explicitné načítanie DLL eliminuje nevýhody uvedené vyššie tým, že kód je o niečo zložitejší. O načítanie DLL a pripojenie exportovaných funkcií sa musí postarať sám programátor. Ale explicitné načítanie vám umožňuje načítať DLL podľa potreby a umožňuje programu zvládnuť situácie, ktoré nastanú, keď DLL chýba.

V prípade explicitného načítania prebieha proces práce s knižnicou DLL v troch fázach:

1. Stiahnite si DLL pomocou funkcie Načítať knižnicu(alebo jeho rozšírený náprotivok LoadLibraryEx). V prípade úspešného načítania funkcia vráti hLib handle typu HMODULE , ktorý umožňuje ďalší prístup k tejto DLL.

2. Volania funkcií GetProcAddress získať ukazovatele na požadované funkcie alebo iné objekty. Ako prvý parameter je funkcia GetProcAddress prijíma deskriptor hLib, ako druhý parameter - adresu reťazca s názvom importovanej funkcie. Výsledný ukazovateľ potom používa klient. Napríklad, ak ide o ukazovateľ na funkciu, potom sa zavolá požadovaná funkcia.

3. Keď už načítaná dynamická knižnica nie je potrebná, odporúča sa ju uvoľniť volaním funkcie voľná knižnica. Uvoľnenie knižnice neznamená, že ju operačný systém okamžite odstráni z pamäte. Oneskorenie vykládky sa poskytuje pre prípad, keď je po určitom čase znova potrebná rovnaká knižnica DLL nejakým procesom. Ak sa však vyskytnú problémy s pamäťou RAM, systém Windows najskôr odstráni uvoľnené knižnice z pamäte.

Zvážte lenivé načítanie knižnice DLL. DLL s oneskoreným načítaním je implicitne prepojená knižnica DLL, ktorá sa nenačíta, kým kód nepristúpi k nejakému identifikátoru, ktorý je z nej exportovaný. Takéto knižnice DLL môžu byť užitočné v nasledujúcich situáciách:

Ak aplikácia používa viacero knižníc DLL, inicializácia môže trvať dlho, pretože načítavaču trvá mapovanie všetkých knižníc DLL do adresného priestoru procesu. Lenivé načítanie DLL riešia tento problém distribúciou načítania DLL počas vykonávania aplikácie.

Ak je aplikácia navrhnutá tak, aby fungovala v rôznych verziách operačného systému, niektoré funkcie sa môžu objaviť iba v neskorších verziách operačného systému a v aktuálnej verzii sa nebudú dať použiť. Ale ak program nezavolá konkrétnu funkciu, potom DLL nie je potrebná a môže bezpečne pokračovať v práci. Pri odvolávaní sa na neexistujúce

funkcie, môžete používateľovi zabezpečiť vydanie príslušného upozornenia.

Pre implementáciu metódy oneskoreného načítania je potrebné dodatočne pridať do zoznamu knižníc linkerov nielen požadovanú importnú knižnicu MyLib.lib, ale aj systémovú importnú knižnicu delayimp.lib. Okrem toho musíte v možnostiach linkera pridať príznak / delayload:MyLib.dll.

Uvedené nastavenia spôsobia, že linker vykoná nasledujúce operácie:

Implementujte špeciálnu funkciu v module EXE
delayLoadHelper;

Odstráňte MyLib.dll zo sekcie importu spustiteľného súboru, aby sa zavádzač operačného systému nepokúšal implicitne načítať túto knižnicu počas fázy načítania aplikácie;

Pridajte novú sekciu odloženého importu do súboru EXE so zoznamom funkcií importovaných z MyLib.dll;

Konvertujte volania funkcií z DLL na volania
delayLoadhelper.

Vo fáze vykonávania aplikácie je volanie funkcie z knižnice DLL implementované volaním delayLoadHelper. Táto funkcia, využívajúca informácie zo sekcie lenivého importu, najskôr zavolá LoadLibrary a potom GetprocAddress. Po prijatí adresy funkcie DLL sa delayLoadHelper postará o to, aby sa táto funkcia DLL v budúcnosti volala priamo. Všimnite si, že každá funkcia v knižnici DLL je pri prvom volaní nakonfigurovaná individuálne.

Existujú dva spôsoby načítania knižnice DLL: explicitné prepojenie a implicitné prepojenie.

Implicitné prepojenie pridáva funkcie načítanej knižnice DLL do sekcie importu volajúceho súboru. Keď sa takýto súbor spustí, zavádzač operačného systému analyzuje sekciu importu a zahŕňa všetky špecifikované knižnice. Vďaka svojej jednoduchosti je táto metóda veľmi populárna; ale jednoduchosť je jednoduchosť a implicitné prepojenie má určité nevýhody a obmedzenia:

všetky pripojené knižnice DLL sú vždy načítané, aj keď program počas celej relácie nikdy nezavolá žiadnu z nich;

ak chýba aspoň jedna z požadovaných knižníc DLL (alebo DLL neexportuje aspoň jednu požadovanú funkciu) - načítanie spustiteľného súboru sa preruší hlásením "Dynamic link library sa nepodarilo nájsť" (alebo niečo podobné) - aj keď absencia tejto knižnice DLL nie je kritická pre spustenie programu. Napríklad textový editor by mohol dobre fungovať v minimálnej konfigurácii – bez tlačového modulu, zobrazovania tabuliek, grafov, vzorcov a iných menších komponentov, ale ak sú tieto knižnice DLL načítané implicitným odkazom – páči sa vám to alebo nie, budete musieť „ťahajte“ ich so sebou.

DLL sa vyhľadávajú v nasledujúcom poradí: v adresári obsahujúcom volajúci súbor; v aktuálnom adresári procesu; v systémovom adresári %Windows%System%; v hlavnom adresári %Windows%; v adresároch špecifikovaných v premennej PATH. Nie je možné nastaviť inú cestu vyhľadávania (alebo skôr je to možné, ale bude to vyžadovať zmeny v systémovom registri a tieto zmeny ovplyvnia všetky procesy bežiace v systéme - čo nie je dobré).

Explicitné prepojenie odstraňuje všetky tieto nedostatky – za cenu určitej zložitosti kódu. O načítanie DLL a prepojenie exportovaných funkcií sa bude musieť postarať sám programátor (pričom nezabudnúť na kontrolu chýb, inak sa to v istom momente celé skončí zamrznutím systému). Explicitné prepojenie vám však umožňuje načítať DLL podľa potreby a dáva programátorovi príležitosť samostatne zvládnuť situácie s absenciou DLL. Môžete ísť ďalej - nenastavujte explicitne názov DLL v programe, ale skenujte ten a ten adresár na prítomnosť dynamických knižníc a pripojte všetky nájdené k aplikácii. Takto funguje mechanizmus podpory zásuvných modulov v populárnom správcovi súborov FAR (a nielen v ňom).

Operačný systém Windows od narodenia (alebo o niečo neskôr) používa knižnice DLL (Dynamic Link Library), ktoré obsahujú implementácie najčastejšie používaných funkcií. Nástupcovia Windows – NT a Windows 95 a OS/2 – tiež závisia od knižníc DLL, pokiaľ ide o ich funkcie.

Zvážte niekoľko aspektov vytvárania a používania knižníc DLL:

  • ako staticky prepojiť knižnice DLL;
  • ako dynamicky načítať knižnice DLL;
  • ako vytvoriť knižnice DLL;
  • ako vytvoriť rozšírenia MFC DLL.

Použitie DLL

Je takmer nemožné vytvoriť aplikáciu Windows, ktorá nepoužíva knižnice DLL. DLL obsahuje všetky funkcie Win32 API a nespočetné množstvo ďalších funkcií operačných systémov Win32.

Všeobecne povedané, DLL sú len kolekcie funkcií, ktoré sa zhromažďujú v knižniciach. Na rozdiel od ich statických príbuzných (súbory .lib) však knižnice DLL nie sú prepojené priamo so spustiteľnými súbormi pomocou linkera. Spustiteľný súbor obsahuje iba informácie o ich umiestnení. V čase vykonávania programu sa načíta celá knižnica. To umožňuje rôznym procesom zdieľať rovnaké knižnice v pamäti. Tento prístup vám umožňuje znížiť množstvo pamäte potrebnej pre viaceré aplikácie, ktoré používajú veľa zdieľaných knižníc, ako aj kontrolovať veľkosť súborov EXE.

Ak však knižnicu využíva len jedna aplikácia, je lepšie z nej spraviť bežnú, statickú. Samozrejme, ak sa funkcie v ňom obsiahnuté budú používať iba v jednom programe, môžete doň jednoducho vložiť príslušný zdrojový súbor.

Najčastejšie je projekt prepojený s knižnicou DLL staticky alebo implicitne v čase prepojenia. Načítanie knižnice DLL počas vykonávania programu je riadené operačným systémom. Knižnicu DLL však možno načítať buď explicitne alebo dynamicky, keď je aplikácia spustená.

Importovať knižnice

Pri statickom prepájaní knižnice DLL je názov súboru .lib definovaný medzi ostatnými možnosťami linkera na príkazovom riadku alebo na karte Link v dialógovom okne Nastavenia projektu Developer Studio. Použitý súbor .lib pri implicitnom prepojení DLL, nie je normálna statická knižnica. Takéto súbory .lib sa nazývajú importné knižnice(importovať knižnice). Neobsahujú samotný kód knižnice, ale iba odkazy na všetky funkcie exportované z DLL súboru, v ktorom je všetko uložené. V dôsledku toho majú knižnice importu tendenciu byť menšie ako súbory DLL. K metódam ich tvorby sa vrátime neskôr. A teraz sa pozrime na ďalšie problémy súvisiace s implicitným prepájaním dynamických knižníc.

Vyjednávanie rozhrania

Pri používaní natívnych knižníc alebo knižníc od tretích strán budete musieť venovať pozornosť tomu, aby sa volanie funkcie zhodovalo s jej prototypom.

Ak by bol svet dokonalý, potom by sa programátori nemuseli starať o zladenie funkčných rozhraní pri zahrnutí knižníc – všetky by boli rovnaké. Svet však nie je ani zďaleka dokonalý a mnoho veľkých programov je napísaných pomocou rôznych knižníc bez C++.

V predvolenom nastavení vo Visual C++ sa funkčné rozhrania zhodujú s pravidlami C++. To znamená, že parametre sa posúvajú do zásobníka sprava doľava a volajúci je zodpovedný za ich odstránenie zo zásobníka, keď sa funkcia ukončí a rozšíri svoj názov. Mangling mien umožňuje linkeru rozlišovať medzi preťaženými funkciami, t.j. funkcie s rovnakým názvom, ale rôznymi zoznamami argumentov. V starej knižnici C však nie sú žiadne funkcie s rozšírenými názvami.

Hoci všetky ostatné pravidlá volania funkcií C sú identické s pravidlami volania funkcií C++, knižnice C nerozširujú názvy funkcií. Pred nimi je len znak podčiarknutia (_).

Ak potrebujete prepojiť knižnicu C s aplikáciou C++, musíte deklarovať všetky funkcie z tejto knižnice ako externé vo formáte C:

Externé "C" int MyOldCFunction(int myParam);

Deklarácie funkcií knižnice sú zvyčajne umiestnené v hlavičkovom súbore tejto knižnice, hoci väčšina hlavičiek knižnice C nie je určená na použitie v projektoch C++. V tomto prípade musíte vytvoriť kópiu hlavičkového súboru a zahrnúť externý modifikátor "C" do deklarácie všetkých použitých knižničných funkcií. Externý modifikátor "C" je možné použiť aj na celý blok, ku ktorému je pomocou direktívy #tinclude pripojený starý hlavičkový súbor C. Namiesto individuálnej úpravy každej funkcie si teda vystačíte iba s tromi riadkami:

Externé "C" ( #include "MyCLib.h" )

Programy pre staršie verzie Windowsu tiež používali konvencie volania funkcie PASCAL pre funkcie Windows API. Novšie programy by mali používať modifikátor winapi, ktorý sa skonvertuje na _stdcall. Hoci nejde o štandardné funkčné rozhranie C alebo C++, je to rozhranie, ktoré sa používa na volanie funkcií Windows API. Toto všetko je však zvyčajne už zohľadnené v štandardných hlavičkách systému Windows.

Načítava sa implicitne prepojená knižnica DLL

Keď sa aplikácia spustí, pokúsi sa nájsť všetky knižnice DLL, ktoré sú implicitne prepojené s aplikáciou, a umiestniť ich do oblasti pamäte RAM obsadenej týmto procesom. Operačný systém vyhľadá súbory DLL v nasledujúcom poradí.

  • Adresár, v ktorom sa nachádza súbor EXE.
  • Aktuálny adresár procesu.
  • Systémový adresár Windows.

Ak sa knižnica DLL nenájde, aplikácia zobrazí dialógové okno s informáciou, že knižnica DLL chýba a cesty, ktoré boli vyhľadané. Proces sa potom ukončí.

Ak sa nájde požadovaná knižnica, umiestni sa do pamäte RAM procesu, kde zostane až do konca procesu. Aplikácia má teraz prístup k funkciám obsiahnutým v DLL.

Dynamické načítavanie a vykladanie knižníc DLL

Namiesto toho, aby sa systém Windows dynamicky pripájal na knižnicu DLL pri prvom načítaní aplikácie do pamäte RAM, môžete program prepojiť s modulom knižnice za behu (týmto spôsobom nemusíte počas vytvárania aplikácie používať importnú knižnicu). Predovšetkým môžete určiť, ktoré knižnice DLL sú dostupné používateľovi, alebo umožniť používateľovi vybrať si, ktoré knižnice DLL sa načítajú. Môžete teda použiť rôzne knižnice DLL, ktoré implementujú rovnaké funkcie, ktoré vykonávajú rôzne akcie. Napríklad aplikácia navrhnutá na nezávislú komunikáciu by sa mohla za behu rozhodnúť, či načítať DLL pre TCP/IP alebo iný protokol.

Načítavanie bežnej knižnice DLL

Prvá vec, ktorú treba urobiť pri dynamickom načítaní knižnice DLL, je vložiť modul knižnice do pamäte procesu. Táto operácia sa vykonáva pomocou funkcie ::Načítať knižnicu, ktorý má jediný argument – ​​názov modulu, ktorý sa má načítať. Príslušný fragment programu by mal vyzerať takto:

HINSTANCE hMyDll; :: if((hMyDll=:: LoadLibrary("MyDLL"))==NULL) ( /* sa nepodarilo načítať DLL */ ) else ( /* aplikácia môže používať funkcie DLL cez hMyDll */ )

Predvolená prípona súboru knižnice pre Windows je .dll, pokiaľ nezadáte inú príponu. Ak je v názve súboru zadaná aj cesta, na vyhľadanie súboru sa použije iba táto cesta. V opačnom prípade bude systém Windows hľadať súbor rovnakým spôsobom ako v prípade implicitne prepojených knižníc DLL, počnúc adresárom, z ktorého sa načítava súbor exe, a pokračuje podľa hodnoty PATH.

Keď systém Windows nájde súbor, jeho úplná cesta sa porovná s cestou knižníc DLL, ktoré už proces načítal. Ak sa nájde zhoda, namiesto načítania kópie aplikácie sa vráti popisovač k už prepojenej knižnici.

Ak sa súbor nájde a knižnica bola úspešne načítaná, funkcia ::Načítať knižnicu vráti svoj handle, ktorý sa používa na prístup k funkciám knižnice.

Pred použitím funkcií knižnice musíte získať ich adresu. Najprv použite smernicu typedef na definovanie typu ukazovateľa funkcie a definovanie premennej tohto nového typu, napríklad:

// typ PFN_MyFunction deklaruje ukazovateľ na funkciu //, ktorá vezme ukazovateľ do vyrovnávacej pamäte znakov a vráti hodnotu typu int typedef int (WINAPI *PFN_MyFunction)(char *); :: PFN_MyFunction pfnMyFunction;

Potom by ste mali získať deskriptor knižnice, pomocou ktorého môžete určiť adresy funkcií, napríklad adresu funkcie s názvom MyFunction:

HMyDll=::LoadLibrary("MyDLL"); pfnMyFunction=(PFN_MyFunction)::GetProcAddress(hMyDll,"MyFunction"); :: int iCode=(*pfnMyFunction)("Ahoj");

Adresa funkcie sa určí pomocou funkcie ::GetProcAddress, mal by byť odovzdaný názov knižnice a názov funkcie. Ten musí byť prenesený vo forme, v akej je exportovaný z DLL.

Na funkciu sa môžete odvolávať aj podľa sériového čísla, pod ktorým sa exportuje (v tomto prípade sa na vytvorenie knižnice musí použiť súbor def, o tom sa bude diskutovať neskôr):

PfnMyFunction=(PFN_MyFunction)::GetProcAddress(hMyDll, MAKEINTRESOURCE(1));

Po dokončení práce s dynamicky prepájanou knižnicou ju možno uvoľniť z pamäte procesu pomocou funkcie: voľná knižnica:

::FreeLibrary(hMyDll);

Načítavajú sa rozšírenia dynamickej knižnice MFC

Pri načítavaní rozšírení MFC pre knižnice DLL (podrobnejšie neskôr) namiesto funkcií Načítať knižnicu a voľná knižnica používajú sa funkcie AfxLoadLibrary a AfxFreeLibrary. Tie sú takmer totožné s funkciami Win32 API. Okrem toho zaručujú iba to, že štruktúry MFC inicializované rozšírením DLL neboli poškodené inými vláknami.

zdroje DLL

Dynamické načítanie sa vzťahuje aj na zdroje DLL používané MFC na načítanie štandardných aplikačných zdrojov. Aby ste to dosiahli, musíte najprv zavolať funkciu Načítať knižnicu a umiestnite DLL do pamäte. Potom použite funkciu AfxSetResourceHandle musíte pripraviť okno programu na príjem zdrojov z novo načítanej knižnice. V opačnom prípade sa zdroje načítajú zo súborov pripojených k spustiteľnému súboru procesu. Tento prístup je užitočný, ak potrebujete použiť rôzne sady zdrojov, napríklad pre rôzne jazyky.

Komentujte. Pomocou funkcie Načítať knižnicu môžete tiež načítať spustiteľné súbory do pamäte (nespúšťajte ich!). Rukoväť spustiteľného modulu potom možno použiť pri volaní funkcií FindResource a LoadResource na vyhľadanie a stiahnutie zdrojov aplikácie. Uvoľnite moduly z pamäte aj pomocou funkcie voľná knižnica.

Príklad bežnej knižnice DLL a spôsoby načítania

Tu je zdrojový kód pre dynamicky prepojenú knižnicu s názvom MyDLL, ktorá obsahuje jedinú funkciu, MyFunction, ktorá jednoducho zobrazí správu.

Najprv je v hlavičkovom súbore definovaná makro konštanta EXPORT. Použitie tohto kľúčového slova pri definovaní funkcie v dynamicky prepojenej knižnici informuje linker, že funkcia je dostupná na použitie inými programami, čo spôsobí jej pridanie do importnej knižnice. Okrem toho musí byť takáto funkcia, rovnako ako procedúra okna, definovaná pomocou konštanty ZAVOLAJ SPÄŤ:

MyDLL.h#define EXPORT extern "C" __declspec (dllexport) EXPORT int CALLBACK MyFunction(char *str);

Súbor knižnice sa tiež trochu líši od bežných súborov Windows C. V ňom namiesto funkcie winmain existuje funkcia DllMain. Táto funkcia sa používa na vykonanie inicializácie, o ktorej bude reč neskôr. Aby knižnica po načítaní zostala v pamäti, aby bolo možné volať jej funkcie, jej návratová hodnota musí byť TRUE:

MyDLL.c #include #include "MyDLL.h" int WINAPI DllMain(HINSTANCE hInstance, DWORD fdReason, PVOID pvReserved) ( return TRUE; ) EXPORT int CALLBACK MyFunction(char *str) ( MessageBox(NULL,str,"Funkcia z DLL",MB_OK); vrátiť 1 ;)

Po preložení a prepojení týchto súborov sa objavia dva súbory – MyDLL.dll (samotná dynamicky prepájaná knižnica) a MyDLL.lib (jej importná knižnica).

Príklad implicitného pripojenia DLL aplikáciou

Tu je zdrojový kód jednoduchej aplikácie, ktorá využíva funkciu MyFunction z knižnice MyDLL.dll:

#include #include "MyDLL.h" int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) ( int iCode=MyFunction("Ahoj"); return 0; )

Tento program vyzerá ako normálny program pre Windows, čo aj je. Treba si však uvedomiť, že okrem volania funkcie MyFunction z knižnice DLL obsahuje jej zdrojový text aj hlavičkový súbor tejto knižnice MyDLL.h. Je tiež potrebné pripojiť sa k nemu vo fáze budovania aplikácie importovať knižnicu MyDLL.lib (proces implicitného prepojenia DLL so spustiteľným súborom).

Je mimoriadne dôležité pochopiť, že samotný kód MyFunction nie je zahrnutý v súbore MyApp.exe. Namiesto toho je tam jednoducho odkaz na súbor MyDLL.dll a odkaz na funkciu MyFunction, ktorá je v tomto súbore. Súbor MyApp.exe vyžaduje na spustenie súbor MyDLL.dll.

Hlavičkový súbor MyDLL.h je súčasťou zdrojového súboru MyApp.c rovnako ako súbor windows.h. Zahrnutie importnej knižnice MyDLL.lib na prepojenie je rovnaké ako zahrnutie všetkých importovacích knižníc systému Windows. Keď je spustený MyApp.exe, prepojí sa s MyDLL.dll rovnako ako všetky štandardné knižnice dynamických odkazov Windows.

Príklad dynamického načítania knižnice DLL aplikáciou

Tu je úplný zdrojový kód jednoduchej aplikácie, ktorá využíva funkciu MyFunction z knižnice MyDLL.dll pomocou dynamického načítavania knižnice:

#include typedef int (WINAPI *PFN_MyFunction)(char *); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) ( HINSTANCE hMyDll; if((hMyDll=LoadLibrary("MyDLL"))==NULL) návrat 1; PFN_MyFunctionMyFunctionNhDpfnAddress MyFunction"); int iCode=(*pfnMyFunction)("Ahoj"); FreeLibrary(hMyDll); return 0; )

Vytvorenie knižnice DLL

Teraz, keď sme sa oboznámili s princípmi fungovania knižníc DLL v aplikáciách, pozrime sa, ako ich vytvoriť. Pri vývoji aplikácie by funkcie, ku ktorým pristupujú viaceré procesy, mali byť prednostne umiestnené v knižnici DLL. To umožňuje efektívnejšie využitie pamäte v systéme Windows.

Najjednoduchší spôsob vytvorenia nového projektu DLL je pomocou AppWizard, ktorý za vás urobí veľa práce automaticky. Pre jednoduché knižnice DLL, ako sú tie, o ktorých sa hovorí v tejto kapitole, musíte vybrať typ projektu Win32 Dynamic-Link Library. Nový projekt dostane všetky potrebné parametre na vytvorenie knižnice DLL. Zdrojové súbory bude potrebné pridať do projektu manuálne.

Ak však plánujete naplno využiť funkcie MFC, ako sú dokumenty a zobrazenia, alebo ak plánujete vytvoriť automatizačný server OLE, typ projektu MFC AppWizard (dll) je lepšou voľbou. V tomto prípade okrem priradenia parametrov k projektu na pripojenie dynamických knižníc vykoná sprievodca ďalšiu prácu. Potrebné odkazy na knižnice MFC a zdrojové súbory obsahujúce popis a implementáciu v knižnici DLL objektu triedy aplikácie odvodeného od CWinApp.

Niekedy je vhodné najprv vytvoriť projekt MFC AppWizard (dll) ako testovaciu aplikáciu a potom vytvoriť knižnicu DLL ako jeho súčasť. V dôsledku toho sa v prípade potreby automaticky vytvorí knižnica DLL.

Funkcia Dllmain

Väčšina knižníc DLL sú jednoducho kolekcie do značnej miery nezávislých funkcií, ktoré sa exportujú do aplikácií a používajú ich. Okrem funkcií určených na export má každá knižnica DLL aj funkciu DllMain. Táto funkcia je určená na inicializáciu a vyčistenie knižnice DLL. Nahradila funkcie LibMain a WEP používané v predchádzajúcich verziách systému Windows. Štruktúra najjednoduchšej funkcie DllMain môže vyzerať napríklad takto:

BOOL WINAPI DllMain (HANDLE hInst,DWORD dwReason, LPVOID IpReserved) ( BOOL bAllWentWell=TRUE; prepínač (dwReason) ( prípad DLL_PROCESS_ATTACH: // Inicializácia procesu. prerušenie; prerušenie prípadu DLL_THREAD_ATTACH: // Vyčistenie prípadu DLL_THREAD_ATTACH: // Inicializácia prípadu vlákna prerušenie štruktúr vlákien; case DLL_PROCESS_DETACH: // Vymazanie prerušenia štruktúr procesov; ) if(bAllWentWell) return TRUE; else return FALSE; )

Funkcia DllMain pri viacerých príležitostiach. Dôvod jeho volania je určený parametrom dwReason, ktorý môže nadobúdať jednu z nasledujúcich hodnôt.

Pri prvom načítaní knižnice DLL procesom sa zavolá funkcia DllMain s dwReason rovným DLL_PROCESS_ATTACH. Zakaždým, keď proces vytvorí nové vlákno, DllMainO sa zavolá s dwReason rovným DLL_THREAD_ATTACH (okrem prvého vlákna, pretože v tomto prípade sa dwReason rovná DLL_PROCESS_ATTACH).

Na konci procesu s DLL, funkcia DllMain volané s parametrom dwReason rovným DLL_PROCESS_DETACH. Keď je vlákno zničené (okrem prvého), dwReason sa bude rovnať DLL_THREAD_DETACH.

Všetky operácie inicializácie a čistenia procesov a vlákien, ktoré DLL potrebuje, sa musia vykonať na základe hodnoty dwReason, ako je znázornené v predchádzajúcom príklade. Inicializácia procesu je zvyčajne obmedzená na alokáciu zdrojov zdieľaných vláknami, ako je načítanie zdieľaných súborov a inicializácia knižníc. Inicializácia vlákna sa používa na nastavenie režimov špecifických pre vlákna, ako je inicializácia lokálnej pamäte.

DLL môže obsahovať prostriedky, ktoré nepatria do aplikácie, ktorá volá knižnicu. Ak funkcie DLL fungujú na zdrojoch DLL, bolo by samozrejme užitočné uložiť rukoväť hInst niekde v súkromí a použiť ju pri načítaní zdrojov z knižnice DLL. Ukazovateľ IpReserved je vyhradený na interné použitie systému Windows. Preto by si to aplikácia nemala nárokovať. Môžete len skontrolovať jeho hodnotu. Ak bola knižnica DLL dynamicky načítaná, bude mať hodnotu NULL. Pre statické načítanie nebude tento ukazovateľ nulový.

Po úspešnom dokončení funkcia DllMain musí vrátiť hodnotu TRUE. Ak sa vyskytne chyba, vráti sa FALSE a ďalšie akcie sa ukončia.

Komentujte. Ak nenapíšete svoju vlastnú funkciu DllMain(), kompilátor zahrnie štandardnú verziu, ktorá jednoducho vráti hodnotu TRUE.

Export funkcií z knižnice DLL

Aby mala aplikácia prístup k funkciám DLL, každá funkcia musí zaberať riadok v tabuľke exportovaných funkcií knižnice DLL. Existujú dva spôsoby, ako vložiť funkciu do tejto tabuľky v čase kompilácie.

metóda __declspec(dllexport).

Funkciu môžete exportovať z knižnice DLL tak, že jej predpíšete modifikátor __declspec(dllexport). Okrem toho MFC obsahuje niekoľko makier, ktoré definujú __declspec(dllexport), vrátane AFX_CLASS_EXPORT, AFX_DATA_EXPORT a AFX_API_EXPORT.

Metóda __declspec sa používa menej často ako druhá metóda, ktorá pracuje so súbormi s definíciou modulu (.def) a poskytuje vám väčšiu kontrolu nad procesom exportu.

Súbory definície modulu

Syntax pre súbory .def vo Visual C++ je pomerne jednoduchá, hlavne preto, že zložité možnosti používané v skorších verziách Windowsu už vo Win32 neplatia. Ako bude zrejmé z nasledujúceho jednoduchého príkladu, súbor .def obsahuje názov a popis knižnice, ako aj zoznam exportovaných funkcií:

MyDLL.def POPIS KNIŽNICE "MyDLL" "MyDLL je príklad DLL" EXPORTUJE MyFunction @1

V exportnom riadku funkcie môžete zadať jej poradové číslo tak, že jej predpíšete znak @. Toto číslo sa potom použije pri kontaktovaní GetProcAddress(). V skutočnosti kompilátor priraďuje poradové čísla všetkým exportovaným objektom. Spôsob, akým to robí, je však trochu nepredvídateľný, pokiaľ nie sú explicitne priradené tieto čísla.

V exportnom reťazci môžete použiť parameter NONAME. Zabraňuje kompilátoru zahrnúť názov funkcie do exportnej tabuľky knižnice DLL:

MyFunction @1 NONAME

To môže niekedy ušetriť veľa miesta v súbore DLL. Aplikácie, ktoré používajú importnú knižnicu na implicitné prepojenie DLL, si rozdiel „nevšimnú“, pretože implicitné prepojenie používa poradové čísla automaticky. Aplikácie, ktoré dynamicky načítavajú knižnice DLL, budú musieť prejsť GetProcAddress poradové číslo, nie názov funkcie.

Pri použití vyššie uvedeného nemusí byť def-súbor popisu exportovaných funkcií DLL napríklad takýto:

#define EXPORT extern "C" __declspec (dllexport) EXPORT int CALLBACK MyFunction(char *str); takto: extern "C" int CALLBACK MyFunction(char *str);

Export tried

Vytvorenie súboru .def na export aj jednoduchých tried z dynamickej knižnice môže byť dosť zložité. Budete musieť explicitne exportovať každú funkciu, ktorú môže použiť externá aplikácia.

Ak sa pozriete na súbor alokácie pamäte implementovaný v triede, môžete v ňom vidieť niekoľko veľmi nezvyčajných funkcií. Ukazuje sa, že existujú implicitné konštruktory a deštruktory, funkcie deklarované v makrách MFC, najmä _DECLARE_MESSAGE_MAP, ako aj funkcie, ktoré píše programátor.

Aj keď je možné exportovať každú z týchto funkcií jednotlivo, existuje jednoduchší spôsob. Ak v deklarácii triedy použijete modifikátor makra AFX_CLASS_EXPORT, kompilátor sa postará o export potrebných funkcií, ktoré aplikácii umožňujú použiť triedu obsiahnutú v DLL.

Memory DLL

Na rozdiel od statických knižníc, ktoré sa v podstate stávajú súčasťou kódu aplikácie, dynamické knižnice na 16-bitových verziách systému Windows narábali s pamäťou trochu inak. Pod Win 16 bola pamäť DLL umiestnená mimo adresného priestoru úlohy. Umiestnenie dynamických knižníc do globálnej pamäte poskytlo možnosť zdieľať ich medzi rôznymi úlohami.

Vo Win32 žije DLL v pamäti procesu, ktorý ju načítava. Každý proces je vybavený samostatnou kópiou „globálnej“ pamäte DLL, ktorá sa reinicializuje vždy, keď ju načíta nový proces. To znamená, že dynamickú knižnicu nemožno zdieľať v zdieľanej pamäti, ako to bolo vo Winl6.

A predsa, vykonaním množstva zložitých manipulácií s dátovým segmentom DLL je možné vytvoriť spoločnú pamäťovú oblasť pre všetky procesy využívajúce túto knižnicu.

Povedzme, že máme pole celých čísel, ktoré by mali používať všetky procesy, ktoré načítavajú túto knižnicu DLL. Dá sa to naprogramovať takto:

#pragma data_seg(".myseg") int sharedInts ; // ďalšie zdieľané premenné #pragma data_seg() #pragma comment(lib, "msvcrt" "-SECTION:.myseg,rws");

Všetky premenné deklarované medzi direktívami #pragma data_seg() sú umiestnené v segmente .myseg. Direktíva #pragma comment() nie je bežný komentár. Prikáže knižnici C runtime, aby označila nový oddiel ako čitateľný, zapisovateľný a zdieľateľný.

Kompletná kompilácia DLL

Ak je projekt dynamickej knižnice vytvorený pomocou AppWizard a zodpovedajúcim spôsobom sa upraví súbor .def, stačí to. Ak sú súbory projektu vytvorené ručne alebo iným spôsobom bez pomoci AppWizard, musí byť na príkazovom riadku linkera zahrnutá možnosť /DLL. Tým sa vytvorí DLL namiesto samostatného spustiteľného súboru.

Ak je v súbore .def riadok LIBRART, nemusíte explicitne špecifikovať možnosť /DLL na príkazovom riadku linkera.

MFC má množstvo špeciálnych režimov týkajúcich sa toho, ako dynamická knižnica používa knižnice MFC. Tejto problematike je venovaná ďalšia časť.

DLL a MFC

Programátor nemusí pri vytváraní dynamických knižníc používať MFC. Používanie MFC však otvára množstvo veľmi dôležitých možností.

Existujú dve úrovne použitia štruktúry MFC v knižnici DLL. Prvá je bežná dynamická knižnica založená na MFC, MFC DLL(bežná MFC DLL). Môže používať MFC, ale nemôže odovzdávať ukazovatele na objekty MFC medzi knižnicami DLL a aplikáciami. Druhá úroveň je implementovaná v dynamické rozšírenia MFC(DLL rozšírenia MFC). Použitie tohto druhu dynamickej knižnice vyžaduje určité dodatočné konfiguračné úsilie, ale umožňuje vám voľne vymieňať ukazovatele na objekty MFC medzi knižnicou DLL a aplikáciou.

Bežné MFC DLL

Bežné MFC DLL vám umožňujú používať MFC v dynamických knižniciach. Aplikácie, ktoré pristupujú k takýmto knižniciam, zároveň nemusia byť postavené na báze MFC. MFC v bežných knižniciach DLL môžete použiť akýmkoľvek spôsobom, vrátane vytvárania nových tried v knižnici DLL na základe tried MFC a ich exportovania do aplikácií.

Normálne knižnice DLL si však nemôžu vymieňať ukazovatele na triedy odvodené od MFC s aplikáciami.

Ak si aplikácia potrebuje vymieňať ukazovatele na objekty tried MFC alebo ich deriváty s knižnicou DLL, musí sa použiť rozšírenie DLL opísané v nasledujúcej časti.

Bežná architektúra DLL je navrhnutá na použitie v iných programovacích prostrediach, ako sú Visual Basic a PowerBuilder.

Pri vytváraní bežnej MFC DLL pomocou AppWizard sa vyberie nový projekt typu MFC AppWizard (dll). V prvom dialógovom okne sprievodcu aplikáciou musíte vybrať jeden z režimov pre bežné dynamické knižnice: "Bežná knižnica DLL s MFC štatisticky prepojená" alebo "Bežná knižnica DLL s použitím zdieľanej knižnice MFC DLL". Prvý poskytuje statické a druhý dynamické prepojenie knižníc MFC. Neskôr môžete zmeniť režim pripojenia MFC na knižnicu DLL pomocou rozbaľovacieho poľa na karte Všeobecné v dialógovom okne Nastavenia projektu.

Správa informácií o stave MFC

Každý procesný modul MFC obsahuje informácie o svojom stave. Informácie o stave knižnice DLL sa teda líšia od informácií o stave aplikácie, ktorá ju zavolala. Preto všetky funkcie exportované z knižnice, ktoré sa volajú priamo z aplikácií, musia MFC povedať, aké informácie o stave má použiť. V normálnej MFC DLL, ktorá používa MFC DLL, pred volaním akéhokoľvek podprogramu MFC musí byť na začiatok exportovanej funkcie umiestnený nasledujúci riadok:

AFX_MANAGE_STATE(AfxGetStaticModuleState()) ;

Tento príkaz určuje použitie príslušných informácií o stave počas vykonávania funkcie, ktorá volala tento podprogram.

Dynamické rozšírenia MFC

MFC vám umožňuje vytvárať knižnice DLL, ktoré aplikácie vnímajú nie ako súbor samostatných funkcií, ale ako rozšírenia MFC. S týmto druhom knižnice DLL môžete vytvárať nové triedy odvodené od tried MFC a používať ich vo svojich aplikáciách.

Ak chcete povoliť bezplatnú výmenu ukazovateľov na objekty MFC medzi aplikáciou a knižnicou DLL, musíte vytvoriť dynamické rozšírenie MFC. DLL tohto typu sú prepojené s dynamickými knižnicami MFC rovnakým spôsobom ako ktorákoľvek aplikácia, ktorá používa dynamické rozšírenie MFC.

Ak chcete vytvoriť nové dynamické rozšírenie MFC, najjednoduchším spôsobom je použiť sprievodcu aplikáciou na priradenie typu projektu MFC AppWizard (dll) a v kroku 1 povoľte režim "MFC Extension DLL". To dá novému projektu všetky potrebné atribúty dynamického rozšírenia MFC. Okrem toho sa vytvorí funkcia DllMain pre knižnicu DLL, ktorá vykonáva množstvo špecifických operácií na inicializáciu rozšírenia DLL. Je potrebné poznamenať, že dynamické knižnice tohto typu neobsahujú a ani by nemali obsahovať objekty odvodené od CWinApp.

Inicializácia dynamických rozšírení

Aby „zapadli“ do rámca MFC, dynamické rozšírenia MFC vyžadujú dodatočné počiatočné nastavenie. Príslušné operácie vykonáva funkcia DllMain. Pozrime sa na príklad tejto funkcie, ktorú vytvoril AppWizard.

Statické AFX_EXTENSION_MODULE MyExtDLL = ( NULL, NULL ) ; extern "C" int APIENTRY DllMain(HINSTANCE hinstance, DWORD dwReason, LPVOID IpReserved) ( if (dwReason == DLL_PROCESS_ATTACH) ( TRACED("MYEXT.DLL Inicializuje sa!\n") ; // Jednorazová inicializácia rozšírenia DLL(Rozšírenie DLLitExduleModuleDLL AfxInDLL , hinstance) ; // Vložte túto knižnicu DLL do reťazca prostriedkov new CDynLinkLibrary(MyExtDLL); ) else if (dwReason == DLL_PROCESS_DETACH) ( TRACED("MYEXT.DLL sa končí!\n") ; ) return 1; // ok )

Najdôležitejšou časťou tejto funkcie je volanie AfxInitExtensionModule. Toto je inicializácia dynamickej knižnice, ktorá jej umožňuje správne fungovať ako súčasť štruktúry MFC. Argumenty tejto funkcie sú deskriptor knižnice DLL odovzdaný do DllMain a štruktúra AFX_EXTENSION_MODULE obsahujúca informácie o dynamickej knižnici pripojenej k MFC.

Nie je potrebné explicitne inicializovať štruktúru AFX_EXTENSION_MODULE. Treba to však deklarovať. Konštruktor vykoná inicializáciu. CDynLinkLibrary. DLL potrebuje vytvoriť triedu CDynLinkLibrary. Jeho konštruktor nielenže inicializuje štruktúru AFX_EXTENSION_MODULE, ale tiež pridá novú knižnicu do zoznamu DLL, s ktorými môže MFC pracovať.

Načítavajú sa dynamické rozšírenia MFC

Počnúc verziou 4.0 vám MFC umožňuje dynamicky načítať a uvoľniť knižnice DLL vrátane rozšírení. Ak chcete správne vykonať tieto operácie na vytvorenej knižnici DLL, v jej funkcii DllMain v momente odpojenia od procesu je potrebné pridať hovor AfxTermExtensionModule. Vyššie použitá štruktúra AFX_EXTENSION_MODULE sa odovzdá ako parameter poslednej funkcii. K tomu v texte DllMain musíte pridať nasledujúce riadky.

If(dwReason == DLL_PROCESS_DETACH) ( AfxTermExtensionModule(MyExtDLL); )

Uvedomte si tiež, že nová knižnica DLL je dynamické rozšírenie a musí sa dynamicky načítavať a uvoľňovať pomocou funkcií AfxLoadLibrary a AfxFreeLibrary,ale nie Načítať knižnicu a voľná knižnica.

Exportovanie funkcií z dynamických rozšírení

Pozrime sa teraz na to, ako sa funkcie a triedy exportujú do aplikácie z dynamického rozšírenia. Aj keď je možné manuálne pridať všetky rozšírené názvy do súboru DEF, je lepšie použiť modifikátory pre deklarácie exportovaných tried a funkcií, ako sú AFX_EXT_CLASS a AFX_EXT_API, napríklad:

Trieda AFX_EXT_CLASS CMyClass: public CObject (// Vaša deklarácia triedy) void AFX_EXT_API MyFunc() ;