Apelarea unei funcții dintr-un dll.

Android/iOS

Încă de la naștere (sau puțin mai târziu), sistemul de operare Windows a folosit DLL-uri (Dynamic Link Libraries), care conțineau implementări ale celor mai frecvent utilizate funcții. Succesorii Windows - NT și Windows 95, precum și OS/2 - depind și ei de DLL pentru o mare parte din funcționalitatea lor.

  • Să ne uităm la o serie de aspecte ale creării și utilizării DLL-urilor:
  • cum să legați static DLL-urile;
  • cum să încărcați dinamic DLL-uri;
  • cum se creează DLL-uri;

cum să creați extensii MFC DLL.

Folosind DLL-uri

Este aproape imposibil să creezi o aplicație Windows care să nu folosească DLL-uri. DLL conține toate funcțiile API-ului Win32 și nenumărate alte funcții ale sistemelor de operare Win32.

În general, DLL-urile sunt pur și simplu colecții de funcții colectate în biblioteci. Cu toate acestea, spre deosebire de verii lor statici (fișiere .lib), DLL-urile nu sunt legate direct la executabile folosind un linker. Fișierul executabil conține doar informații despre locația lor. În momentul execuției programului, întreaga bibliotecă este încărcată. Acest lucru permite diferitelor procese să partajeze aceleași biblioteci în memorie. Această abordare vă permite să reduceți memoria necesară pentru mai multe aplicații care utilizează multe biblioteci partajate, precum și să controlați dimensiunea fișierelor EXE.

Cu toate acestea, dacă biblioteca este folosită de o singură aplicație, este mai bine să o faceți una obișnuită, statică. Desigur, dacă funcțiile incluse în acesta vor fi utilizate doar într-un singur program, puteți pur și simplu să introduceți fișierul text sursă corespunzător în acesta.

Cel mai adesea, proiectul este conectat la DLL static, sau implicit, la momentul link-ului. Încărcarea DLL-urilor în timpul execuției programului este controlată de sistemul de operare. Cu toate acestea, DLL-urile pot fi încărcate fie explicit, fie dinamic în timp ce aplicația rulează.

Import biblioteci Când conectați o DLL în mod static, numele fișierului .lib este determinat printre alte opțiuni Link Editor pe linia de comandă sau pe fila Link din caseta de dialog Project Settings din Developer Studio. Cu toate acestea, fișierul .lib folosit- aceasta nu este o bibliotecă statică obișnuită. Astfel de fișiere .lib sunt numite importa biblioteci(import biblioteci). Ele nu conțin codul bibliotecii în sine, ci doar legături către toate funcțiile exportate din fișierul DLL în care este stocat totul. Ca rezultat, bibliotecile de import tind să fie mai mici ca dimensiune decât fișierele DLL. Vom reveni la cum să le creăm mai târziu. Acum să ne uităm la alte probleme legate de includerea implicită a bibliotecilor dinamice.

Negocierea interfeței

Când utilizați biblioteci proprii sau terțe, va trebui să acordați atenție potrivirii apelului de funcție cu prototipul său.

Dacă lumea ar fi perfectă, atunci programatorii nu ar trebui să-și facă griji cu privire la potrivirea interfețelor cu funcții atunci când conectează biblioteci - ar fi toate la fel. Cu toate acestea, lumea este departe de a fi perfectă și multe programe mari sunt scrise folosind diferite biblioteci fără C++.

În mod implicit, Visual C++ se conformează interfețelor de funcții conform regulilor C++. Aceasta înseamnă că parametrii sunt împinși în stivă de la dreapta la stânga, iar apelantul este responsabil pentru eliminarea acestora din stivă la ieșirea din funcție și extinderea numelui acesteia. Manipularea numelor permite linkerului să facă distincția între funcțiile supraîncărcate, de exemplu. funcții cu aceleași nume, dar liste diferite de argumente. Cu toate acestea, vechea bibliotecă C nu are funcții extinse cu nume.

Deși toate celelalte reguli pentru apelarea unei funcții în C sunt identice cu regulile pentru apelarea unei funcții în C++, numele funcțiilor nu sunt extinse în bibliotecile C. Sunt precedate doar de un caracter de subliniere (_).

Dacă trebuie să conectați o bibliotecă C la o aplicație C++, toate funcțiile din această bibliotecă vor trebui declarate ca externe în format C:

Extern "C" int MyOldCFunction(int myParam);

Declarațiile funcțiilor bibliotecii sunt de obicei plasate în fișierul antet al bibliotecii, deși anteturile majorității bibliotecilor C nu sunt concepute pentru a fi utilizate în proiecte C++. În acest caz, trebuie să creați o copie a fișierului antet și să includeți modificatorul extern „C” în declarația tuturor funcțiilor de bibliotecă utilizate. Modificatorul extern „C” poate fi aplicat și unui bloc întreg, la care vechiul fișier antet C este conectat folosind directiva #tinclude. Astfel, în loc să modificați fiecare funcție separat, vă puteți descurca cu doar trei linii:

Extern „C” ( #include „MyCLib.h”)

Programele pentru versiunile mai vechi de Windows au folosit și convențiile de apelare a funcției PASCAL pentru funcțiile API Windows. Programele noi ar trebui să utilizeze modificatorul winapi, care se convertește în _stdcall. Deși aceasta nu este o interfață de funcție standard C sau C++, este ceea ce este folosit pentru a apela funcții API Windows. Cu toate acestea, de obicei toate acestea sunt deja luate în considerare în anteturile standard Windows.

Încărcarea unui DLL implicit

Când o aplicație pornește, încearcă să găsească toate fișierele DLL atașate implicit aplicației și să le plaseze în zona RAM ocupată de proces. Sistemul de operare caută fișiere DLL în următoarea secvență.

  • Directorul în care se află fișierul EXE.
  • Directorul curent al procesului.
  • Directorul de sistem Windows.

Dacă DLL-ul nu este găsit, aplicația afișează o casetă de dialog care indică absența acestuia și căile căutate. Procesul se oprește apoi.

Dacă se găsește biblioteca necesară, aceasta este plasată în memoria RAM a procesului, unde rămâne până la sfârșitul procesului. Aplicația poate accesa acum funcțiile conținute în DLL.

Încărcarea și descărcarea dinamică a DLL-urilor

În loc să se conecteze în mod dinamic Windows la DLL atunci când aplicația este încărcată pentru prima dată în memorie, puteți conecta programul la modulul bibliotecii în timpul execuției (cu această metodă, nu este necesar să utilizați biblioteca de import când creați aplicația). Mai exact, puteți determina ce DLL-uri sunt disponibile pentru utilizator sau puteți permite utilizatorului să aleagă ce DLL-uri vor fi încărcate. În acest fel, puteți utiliza DLL-uri diferite care implementează aceleași funcții pentru a efectua acțiuni diferite. De exemplu, o aplicație concepută pentru transferul independent de date va putea decide în timpul execuției dacă să încarce un DLL pentru protocolul TCP/IP sau pentru un alt protocol.

Încărcarea unui DLL obișnuit

Primul lucru pe care trebuie să-l faceți când încărcați dinamic un DLL este să plasați modulul bibliotecii în memoria de proces. Această operație se realizează folosind funcția ::LoadLibrary, care are un singur argument - numele modulului încărcat. Fragmentul de program corespunzător ar trebui să arate astfel:

HINSTANȚĂ hMyDll;

Windows consideră că extensia de fișier bibliotecă standard este .dll, cu excepția cazului în care specificați o extensie diferită. Dacă numele fișierului include și o cale, atunci numai acea cale va fi folosită pentru a căuta fișierul. În caz contrar, Windows va căuta fișierul în același mod ca și DLL-urile incluse implicit, pornind de la directorul din care este încărcat fișierul exe și continuând în funcție de valoarea PATH.

Când Windows găsește fișierul, calea sa completă va fi comparată cu calea DLL-urilor deja încărcate de proces. Dacă se găsește o identitate, în loc să descărcați o copie a aplicației, este returnat un handle către biblioteca deja inclusă.

Dacă fișierul este găsit și biblioteca este încărcată cu succes, funcția ::LoadLibrary returnează mânerul său, care este folosit pentru a accesa funcțiile bibliotecii.

Înainte de a utiliza funcțiile bibliotecii, trebuie să obțineți adresa acestora. Pentru a face acest lucru, trebuie mai întâi să utilizați directiva typedef pentru a defini tipul unui indicator de funcție și a defini o variabilă de acest tip nou, de exemplu:

// tipul PFN_MyFunction va declara un pointer către o funcție // care duce un pointer către un buffer de caractere și produce o valoare de tip int typedef int (WINAPI *PFN_MyFunction)(char *);

::PFN_MyFunction pfnMyFunction;

Apoi ar trebui să obțineți un descriptor de bibliotecă, cu ajutorul căruia puteți determina adresele funcțiilor, de exemplu, adresa unei funcții numită MyFunction:

HMyDll=::LoadLibrary(„MyDLL”); pfnMyFunction=(PFN_MyFunction)::GetProcAddress(hMyDll,"MyFunction");:: int iCode=(*pfnMyFunction)("Bună ziua");

Adresa funcției este determinată folosind funcția

::GetProcAddress

, ar trebui să i se transmită numele bibliotecii și numele funcției. Acesta din urmă trebuie transmis în forma în care este exportat din DLL. De asemenea, puteți face referire la o funcție prin numărul ordinal cu care este exportată (în acest caz, trebuie folosit un fișier def pentru a crea biblioteca, despre care va fi discutat mai târziu)::

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

După terminarea lucrului cu biblioteca de legături dinamice, aceasta poate fi descărcată din memoria procesului folosind funcția::

FreeLibrary ::FreeLibrary(hMyDll); Se încarcă extensiile MFC în biblioteci dinamice De asemenea, puteți face referire la o funcție prin numărul ordinal cu care este exportată (în acest caz, trebuie folosit un fișier def pentru a crea biblioteca, despre care va fi discutat mai târziu): Când încărcați extensii MFC pentru DLL-uri (discutate în detaliu mai jos) în loc de funcții LoadLibraryŞi sunt utilizate funcții AfxLoadLibrary

Şi

Încărcarea dinamică se aplică și resurselor DLL pe care MFC le folosește pentru a încărca resursele aplicației standard. Pentru a face acest lucru, mai întâi trebuie să apelați funcția ::FreeLibrary(hMyDll);și plasați DLL-ul în memorie. Apoi, folosind funcția AfxSetResourceHandle trebuie să pregătiți fereastra programului pentru a primi resurse din biblioteca nou încărcată. În caz contrar, resursele vor fi încărcate din fișierele atașate fișierului executabil al procesului. Această abordare este convenabilă dacă trebuie să utilizați seturi diferite de resurse, de exemplu pentru diferite limbi.

Comentariu. Folosind funcția ::FreeLibrary(hMyDll); De asemenea, puteți încărca fișiere executabile în memorie (nu le lansați pentru execuție!). Mânerul modulului executabil poate fi folosit atunci când se apelează funcții GăsițiResurse Se încarcă extensiile MFC în biblioteci dinamice LoadResource pentru a căuta și descărca resurse pentru aplicații. Modulele sunt, de asemenea, descărcate din memorie folosind funcția De asemenea, puteți face referire la o funcție prin numărul ordinal cu care este exportată (în acest caz, trebuie folosit un fișier def pentru a crea biblioteca, despre care va fi discutat mai târziu):.

Exemplu de DLL obișnuit și metode de încărcare

Aici este codul sursă al unei biblioteci de linkuri dinamice numită MyDLL și conține o funcție MyFunction, care afișează pur și simplu un mesaj.

Mai întâi, o constantă macro este definită în fișierul antet EXPORT. Folosirea acestui cuvânt cheie atunci când definiți o funcție într-o bibliotecă de legături dinamice îi spune linkerului că funcția este disponibilă pentru utilizare de către alte programe, ceea ce face ca aceasta să fie inclusă în biblioteca de import. În plus, o astfel de funcție, la fel ca o procedură de fereastră, trebuie definită folosind constanta SUNĂ DIN NOU:

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

Fișierul bibliotecă este, de asemenea, oarecum diferit de fișierele C obișnuite pentru Windows. Conține în loc de o funcție WinMain exista o functie DllMain. Această funcție este folosită pentru a efectua inițializarea, care va fi discutată mai târziu. Pentru ca biblioteca să rămână în memorie după ce este încărcată, astfel încât funcțiile sale să poată fi apelate, valoarea sa returnată trebuie să fie 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,"Function from DLL",MB_OK); întoarcere 1;

După traducerea și conectarea acestor fișiere, apar două fișiere - MyDLL.dll (biblioteca de linkuri dinamice în sine) și MyDLL.lib (biblioteca sa de import).

Exemplu de conexiune DLL implicită după aplicație

Să prezentăm acum codul sursă al unei aplicații simple care utilizează funcția MyFunction din biblioteca MyDLL.dll:

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

Acest program arată ca un program Windows obișnuit, care este exact ceea ce este. Cu toate acestea, trebuie remarcat faptul că în textul său sursă, pe lângă apelarea funcției MyFunction din biblioteca DLL, este inclus și fișierul antet al acestei biblioteci MyDLL.h. De asemenea, este necesar să vă conectați la acesta în etapa de compunere a aplicării biblioteca de import MyDLL.lib (procesul de conectare implicit a unui DLL la un modul executabil).

Este extrem de important să înțelegeți că codul MyFunction în sine nu este inclus în fișierul MyApp.exe. În schimb, conține pur și simplu un link către fișierul MyDLL.dll și un link către funcția MyFunction care se află în acel fișier. Fișierul MyApp.exe necesită fișierul MyDLL.dll pentru a rula.

Fișierul antet MyDLL.h este inclus în fișierul sursă al programului MyApp.c în același mod în care fișierul windows.h este inclus acolo. Includerea bibliotecii de import MyDLL.lib pentru conectare este aceeași cu includerea tuturor bibliotecilor de import Windows acolo. Când MyApp.exe rulează, se conectează la MyDLL.dll în același mod ca toate bibliotecile standard de legături dinamice Windows.

Exemplu de încărcare dinamică a unui DLL de către o aplicație

Să prezentăm acum codul sursă complet al unei aplicații simple care utilizează funcția MyFunction din biblioteca MyDLL.dll, folosind încărcarea dinamică a bibliotecii:

#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) returnează 1; PFN_fNPFMFnction; Funcție)G etProcAddress( hMyDll ,"MyFunction"); int iCode=(*pfnMyFunction)("Salut");

Crearea unui DLL

Cea mai ușoară modalitate de a crea un nou proiect DLL este să utilizați vrăjitorul AppWizard, care efectuează multe dintre operațiuni în mod automat. Pentru DLL-uri simple, cum ar fi cele discutate în acest capitol, trebuie să selectați tipul de proiect Win32 Dynamic-Link Library. Noului proiect i se vor oferi toate setările necesare pentru a crea DLL. Fișierele sursă vor trebui adăugate la proiect manual.

Dacă intenționați să profitați din plin de funcționalitatea MFC, cum ar fi documentele și vizualizările, sau intenționați să creați un server de automatizare OLE, este mai bine să selectați tipul de proiect MFC AppWizard (dll). În acest caz, pe lângă alocarea parametrilor proiectului pentru conectarea bibliotecilor dinamice, vrăjitorul va face o muncă suplimentară. Legăturile necesare către bibliotecile MFC și fișierele sursă care conțin o descriere și o implementare în DLL a unui obiect de clasă de aplicație derivat din CWinApp.

Uneori este convenabil să creați mai întâi un proiect precum MFC AppWizard (dll) ca aplicație de testare, apoi să creați DLL-ul ca parte a acestuia. Ca rezultat, DLL-ul va fi creat automat dacă este necesar.

Funcția DllMain

Majoritatea DLL-urilor sunt pur și simplu colecții de funcții esențial independente care sunt exportate și utilizate de aplicații. Pe lângă funcțiile destinate exportului, fiecare DLL are o funcție DllMain. Această funcție este concepută pentru a inițializa și curăța un DLL. A înlocuit funcțiile LibMainŞi WEP, folosit în versiunile anterioare de Windows. Structura celei mai simple funcții DllMain ar putea arăta astfel, de exemplu:

BOOL WINAPI DllMain (HANDLE hInst, DWORD dwReason, LPVOID IpReserved) ( BOOL bAllWentWell=TRUE; comutator (dwReason) (caz DLL_PROCESS_ATTACH: // Inițializarea procesului. break; caz DLL_THREAD_ATTACH: // Întreruperea firului de discuție / Inițializare DLLETACHREAD_up / Curățare caz DLL_THREAD: THREAD/. Structuri de fire de execuție DLL_PROCESS_DETACH: // Ștergerea structurilor de proces ) if(bAllWentWell) returnează TRUE;

Funcţie DllMain numit în mai multe cazuri. Motivul apelării este determinat de parametrul dwReason, care poate lua una dintre următoarele valori.

Prima dată când un proces încarcă un DLL, funcția este apelată DllMain cu dwReason egal cu DLL_PROCESS_ATTACH. De fiecare dată când un proces creează un fir nou, DllMainO este apelat cu dwReason egal cu DLL_THREAD_ATTACH (cu excepția primului fir, deoarece în acest caz dwReason este egal cu DLL_PROCESS_ATTACH).

După ce procesul a terminat de lucrat cu DLL, funcția DllMain apelat cu parametrul dwReason egal cu DLL_PROCESS_DETACH. Când un fir este distrus (cu excepția primului), dwReason va fi egal cu DLL_THREAD_DETACH.

Toate operațiunile de inițializare și curățare pentru procesele și firele de execuție de care are nevoie DLL-ul trebuie să fie efectuate pe baza valorii dwReason, așa cum se arată în exemplul anterior. Inițializarea procesului este de obicei limitată la alocarea resurselor partajate între fire, cum ar fi încărcarea fișierelor partajate și inițializarea bibliotecilor. Inițializarea firului de execuție este folosită pentru a configura moduri specifice numai unui fir dat, de exemplu, pentru a inițializa memoria locală.

Un DLL poate conține resurse care nu sunt deținute de aplicația care o apelează. Dacă funcțiile DLL funcționează pe resurse DLL, ar fi evident util să stocați un handle hInst undeva în afara drumului și să îl folosiți atunci când încărcați resurse din DLL. Pointerul IpReserved este rezervat utilizării interne în Windows. Prin urmare, cererea nu ar trebui să-l revendice. Nu poți decât să-i verifici valoarea. Dacă DLL-ul a fost încărcat dinamic, acesta va fi NULL. Când se încarcă static, acest indicator va fi diferit de zero.

Dacă are succes, funcția DllMain ar trebui să returneze TRUE. Dacă apare o eroare, este returnat FALSE și acțiunea ulterioară este terminată.

Comentariu. Dacă nu vă scrieți propria funcție DllMain(), compilatorul va folosi versiunea standard, care pur și simplu returnează TRUE.

Exportarea funcțiilor din DLL-uri

Pentru ca o aplicație să acceseze funcțiile dinamice ale bibliotecii, fiecare funcție trebuie să ocupe un rând în tabelul cu funcțiile DLL exportate. Există două moduri de a adăuga o funcție la acest tabel în timpul compilării.

metoda __declspec(dllexport).

Puteți exporta o funcție dintr-un DLL prefixând descrierea acesteia cu modificatorul __declspec (dllexport). În plus, MFC include mai multe macrocomenzi care definesc __declspec (dllexport), inclusiv AFX_CLASS_EXPORT, AFX_DATA_EXPORT și AFX_API_EXPORT.

Metoda __declspec nu este folosită la fel de des ca a doua metodă, care funcționează cu fișiere de definire a modulelor (.def) și permite un control mai bun asupra procesului de export.

Fișiere de definire a modulului

Sintaxa pentru fișierele .def din Visual C++ este destul de simplă, în principal pentru că opțiunile complexe utilizate în versiunile anterioare de Windows nu se mai aplică în Win32. După cum va fi clar din următorul exemplu simplu, fișierul .def conține numele și descrierea bibliotecii, precum și o listă de funcții exportate:

MyDLL.def BIBLIOTECĂ „MyDLL” DESCRIERE „MyDLL - exemplu de bibliotecă DLL” EXPORTĂ MyFunction @1

În linia de export a funcției, puteți indica numărul său de serie plasând simbolul @ în fața acestuia. Acest număr va fi folosit atunci când contactați GetProcAddress(). De fapt, compilatorul atribuie numere de secvență tuturor obiectelor exportate. Cu toate acestea, modul în care face acest lucru este oarecum imprevizibil, cu excepția cazului în care atribuiți aceste numere în mod explicit.

Puteți utiliza parametrul NONAME în șirul de export. Împiedică compilatorul să includă numele funcției în tabelul de export DLL:

MyFunction @1 NONAME

Uneori, acest lucru poate economisi mult spațiu în fișierul DLL. Aplicațiile care folosesc biblioteca de import pentru a lega implicit DLL-urile nu vor „observa” diferența, deoarece conectarea implicită utilizează numerele de secvență automat. Aplicațiile care încarcă DLL-uri în mod dinamic vor trebui să fie transmise GetProcAddress numărul de ordine, nu numele funcției.

Când utilizați cele de mai sus, fișierul def care descrie funcțiile DLL exportate poate să nu fie, de exemplu, astfel:

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

un astfel de: extern "C" int CALLBACK MyFunction(char *str);

Exportul claselor

Crearea unui fișier .def pentru a exporta chiar și clase simple dintr-o bibliotecă dinamică poate fi destul de dificilă. Va trebui să exportați în mod explicit fiecare funcție care poate fi utilizată de o aplicație externă.

Dacă te uiți la fișierul de alocare a memoriei implementat în clasă, vei observa câteva caracteristici foarte neobișnuite. Rezultă că există constructori și destructori impliciti, funcții declarate în macro-urile MFC, în special _DECLARE_MESSAGE_MAP, precum și funcții care sunt scrise de programator.

Deși este posibil să exportați fiecare dintre aceste funcții în mod individual, există o modalitate mai ușoară. Dacă utilizați macromodificatorul AFX_CLASS_EXPORT într-o declarație de clasă, compilatorul însuși se va ocupa de exportul funcțiilor necesare care permit aplicației să folosească clasa conținută în DLL.

Spre deosebire de bibliotecile statice, care devin în esență parte a codului aplicației, bibliotecile de link dinamic din versiunile pe 16 biți ale Windows au tratat memoria puțin diferit. Sub Win 16, memoria DLL a fost localizată în afara spațiului de adrese de activitate. Plasarea bibliotecilor dinamice în memoria globală a făcut posibilă partajarea acestora între diferite sarcini.

În Win32, un DLL se află în zona de memorie a procesului care îl încarcă. Fiecare proces primește o copie separată a memoriei DLL „globale”, care este reinițializată de fiecare dată când o încarcă un nou proces. Aceasta înseamnă că biblioteca dinamică nu poate fi partajată în memoria partajată, așa cum a fost în Winl6.

Cu toate acestea, efectuând o serie de manipulări complicate pe segmentul de date al unui DLL, este posibil să se creeze o zonă de memorie partajată pentru toate procesele care utilizează DLL.

Să presupunem că avem o matrice de numere întregi care ar trebui să fie utilizate de toate procesele care încarcă un anumit DLL. Acesta poate fi programat după cum urmează:

#pragma data_seg(".myseg") int sharedlnts ;

// alte variabile publice #pragma data_seg() #pragma comment(lib, "msvcrt" "-SECTION:.myseg,rws");

Toate variabilele declarate între directivele #pragma data_seg() sunt plasate în segmentul.myseg. Directiva #pragma comment() nu este un comentariu obișnuit. Acesta indică bibliotecii C runtime să marcheze noua partiție ca citită, scrisă și partajabilă.

Compilare DLL completă

Dacă proiectul de bibliotecă dinamică este creat folosind AppWizard și fișierul .def este modificat în consecință, acest lucru este suficient. Dacă creați fișiere de proiect manual sau prin alte mijloace fără ajutorul AppWizard, ar trebui să includeți parametrul /DLL pe linia de comandă a editorului de linkuri. Acest lucru va crea un DLL în loc de un executabil independent.

Dacă există o linie LIBRART în fișierul .def, nu trebuie să specificați în mod explicit parametrul /DLL pe linia de comandă a editorului de legături.

MFC are o serie de comportamente speciale în ceea ce privește utilizarea bibliotecilor MFC de către biblioteca dinamică. Următoarea secțiune este dedicată acestei probleme.

DLL și MFC

Programatorul nu este obligat să folosească MFC atunci când creează biblioteci dinamice. Cu toate acestea, utilizarea MFC deschide o serie de posibilități foarte importante. Există două niveluri de utilizare a structurii MFC într-un DLL. Prima este o bibliotecă dinamică obișnuită bazată pe MFC,(DLL MFC obișnuit). Poate folosi MFC, dar nu poate trece pointeri către obiecte MFC între DLL și aplicații. Al doilea nivel este implementat în extensii MFC dinamice(DLL extensii MFC). Utilizarea acestui tip de bibliotecă dinamică necesită un efort suplimentar de configurare, dar permite ca pointerii către obiecte MFC să fie schimbate liber între DLL și aplicație.

DLL-uri MFC obișnuite

DLL-urile MFC obișnuite vă permit să utilizați MFC în biblioteci dinamice. Cu toate acestea, aplicațiile care accesează astfel de biblioteci nu trebuie neapărat să fie construite pe MFC. DLL-urile obișnuite pot folosi MFC în orice mod, inclusiv prin crearea de noi clase în DLL bazate pe clase MFC și exportarea lor în aplicații.

Cu toate acestea, DLL-urile obișnuite nu pot schimba pointeri către clase derivate din MFC cu aplicații.

Dacă aplicația dvs. trebuie să schimbe pointeri către obiecte ale claselor MFC sau derivate ale acestora cu un DLL, trebuie să utilizeze extensia DLL descrisă în secțiunea următoare.

Arhitectura DLL-urilor obișnuite este concepută pentru a fi utilizată de alte medii de programare, cum ar fi Visual Basic și PowerBuilder.

Când creați un DLL MFC obișnuit folosind AppWizard, un proiect nou de tip MFC AppWizard (dll). În prima casetă de dialog a aplicației Expert, trebuie să selectați unul dintre modurile pentru bibliotecile dinamice obișnuite: „DLL obișnuit cu MFC legat statistic” sau „DLL obișnuit folosind DLL MFC partajat”. Primul asigură conexiunea statică, iar al doilea - conexiunea dinamică a bibliotecilor MFC. Mai târziu, puteți schimba modul de conexiune MFC în DLL utilizând caseta combinată din fila General din caseta de dialog Setări proiect.

Gestionarea informațiilor despre starea MFC

Fiecare modul de proces MFC conține informații despre starea sa. Astfel, informațiile de stare ale unui DLL sunt diferite de informațiile de stare ale aplicației care a apelat-o. Prin urmare, orice funcții exportate din bibliotecă care sunt accesate direct din aplicații trebuie să spună MFC ce informații de stare să folosească. Într-o DLL MFC obișnuită care utilizează biblioteci dinamice MFC, înainte de a apela orice rutină MFC, trebuie să plasați următoarea linie la începutul funcției exportate:

AFX_MANAGE_STATE(AfxGetStaticModuleState()) ;

Această instrucțiune specifică utilizarea informațiilor de stare adecvate în timpul execuției funcției care apelează această subrutină.

Extensii MFC dinamice

MFC vă permite să creați DLL-uri care sunt percepute de aplicații nu ca un set de funcții separate, ci ca extensii ale MFC. Cu acest tip de DLL, puteți crea clase noi care derivă din clasele MFC și le puteți utiliza în aplicațiile dvs.

Pentru a permite ca pointerii către obiecte MFC să fie schimbate liber între o aplicație și un DLL, trebuie să creați o extensie MFC dinamică. DLL-urile de acest tip se conectează la bibliotecile dinamice MFC în același mod ca orice aplicație care utilizează extensia dinamică MFC.

Pentru a crea o nouă extensie MFC dinamică, cel mai simplu mod este să utilizați aplicația Expert pentru a atribui tipul de proiect MFC AppWizard (dll) iar la pasul 1 activați modul „MFC Extension DLL”. Ca rezultat, noului proiect i se vor atribui toate atributele necesare extensiei dinamice MFC. În plus, va fi creată o funcție DllMain pentru un DLL, care efectuează o serie de operațiuni specifice pentru a inițializa extensia DLL. Vă rugăm să rețineți că bibliotecile dinamice de acest tip nu conțin și nu ar trebui să conțină obiecte derivate din CWinApp.

Inițializarea extensiilor dinamice

Pentru a se potrivi în cadrul MFC, extensiile dinamice MFC necesită o configurare inițială suplimentară. Operațiile corespunzătoare sunt efectuate de funcție DllMain. Să ne uităm la un exemplu de această funcție creată de expertul AppWizard.

Static 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 Initializing!\n") ; // Extension DLL one-time Extension initialization AfLLMMInit , hinstance) ; // Inserați acest DLL în lanțul de resurse new CDynLinkLibrary(MyExtDLL) else if (dwReason == DLL_PROCESS_DETACH) ( TRACED("MYEXT.DLL Terminating!\n") ; ) return 1; Cea mai importantă parte a acestei funcții este apelul AfxInitExtensionModule

. Aceasta este inițializarea unei biblioteci dinamice, permițându-i să funcționeze corect ca parte a structurii MFC. Argumentele acestei funcții sunt descriptorul de bibliotecă DLL transmis către DllMain și structura AFX_EXTENSION_MODULE, care conține informații despre biblioteca dinamică conectată la MFC. Nu este nevoie să inițializați structura AFX_EXTENSION_MODULE în mod explicit. Cu toate acestea, trebuie declarat. Constructorul se va ocupa de inițializare CDynLinkLibrary Nu este nevoie să inițializați structura AFX_EXTENSION_MODULE în mod explicit. Cu toate acestea, trebuie declarat. Constructorul se va ocupa de inițializare. Constructorul său nu numai că va inițializa structura AFX_EXTENSION_MODULE, dar va adăuga și o nouă bibliotecă la lista de DLL-uri cu care poate lucra MFC.

Se încarcă extensii MFC dinamice

Începând cu versiunea 4.0, MFC vă permite să încărcați și să descărcați dinamic DLL-uri, inclusiv extensii. Pentru a efectua corect aceste operațiuni pe DLL-ul creat în funcția sa DllMainîn momentul deconectării de la proces, trebuie să adăugați un apel AfxTermExtensionModule. Ultima funcție i se trece structura AFX_EXTENSION_MODULE deja folosită mai sus ca parametru. Pentru a face acest lucru în text DllMain trebuie să adăugați următoarele rânduri.

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

De asemenea, rețineți că noul DLL este o extensie dinamică și trebuie să fie încărcat și descărcat dinamic folosind funcții LoadLibraryŞi sunt utilizate funcții, nu ::FreeLibrary(hMyDll);Şi De asemenea, puteți face referire la o funcție prin numărul ordinal cu care este exportată (în acest caz, trebuie folosit un fișier def pentru a crea biblioteca, despre care va fi discutat mai târziu):.

Exportarea funcțiilor din extensii dinamice

Să ne uităm acum la modul în care funcțiile și clasele dintr-o extensie dinamică sunt exportate într-o aplicație. Deși puteți adăuga manual toate numele extinse în fișierul DEF, este mai bine să utilizați modificatori pentru declarațiile de clase și funcții exportate, cum ar fi AFX_EXT_CLASS și AFX_EXT_API, de exemplu:

Clasa AFX_EXT_CLASS CMyClass: public CObject (// Declarația dvs. de clasă ) void AFX_EXT_API MyFunc() ;

Există trei moduri de a încărca un DLL:

a) implicit;

c) amânat.

Să ne uităm la încărcarea DLL implicită. Pentru a construi o aplicație proiectată pentru încărcare implicită DLL, trebuie să aveți:

Biblioteca include fișiere cu descrieri ale obiectelor utilizate din DLL (prototipuri de funcție, declarații de clasă și tip). Acest fișier este folosit de compilator.

Fișier LIB cu o listă de identificatori importați. Acest fișier trebuie adăugat la setările proiectului (la lista de biblioteci utilizate de linker).

Proiectul este compilat în mod obișnuit. Folosind module obiect și un fișier LIB, precum și luând în considerare legăturile către identificatorii importați, linkerul (linker, link editor) generează un modul EXE de pornire. În acest modul, linkerul plasează și o secțiune de import care listează numele tuturor modulelor DLL necesare. Pentru fiecare DLL, secțiunea de import specifică ce nume de funcții și variabile sunt referite în codul fișierului executabil. Aceste informații vor fi folosite de încărcătorul sistemului de operare.

Ce se întâmplă în timpul fazei de execuție a aplicației client? După lansarea modulului EXE, încărcătorul sistemului de operare efectuează următoarele operații:

1. Creează un spațiu de adrese virtuale pentru un nou proces și proiectează un modul executabil pe acesta;

2. Analizează secțiunea de import, identificând toate modulele DLL necesare și, de asemenea, proiectându-le în spațiul de adrese ale procesului.

Un DLL poate importa funcții și variabile dintr-un alt DLL. Aceasta înseamnă că poate avea propria sa secțiune de import, pentru care trebuie să repetați aceiași pași. Ca urmare, poate dura destul de mult timp pentru a inițializa procesul.

Odată ce modulul EXE și toate modulele DLL sunt mapate la spațiul de adrese al procesului, firul său principal este gata pentru execuție, iar aplicația începe să ruleze.

Dezavantajele încărcării implicite sunt încărcarea obligatorie a bibliotecii, chiar dacă funcțiile acesteia nu sunt apelate și, în consecință, cerința obligatorie a prezenței bibliotecii la conectare.

Încărcarea explicită a unui DLL elimină dezavantajele menționate mai sus, în detrimentul unei anumite complexități a codului. Programatorul însuși trebuie să se ocupe de încărcarea DLL-ului și de conectarea funcțiilor exportate. Dar încărcarea explicită vă permite să încărcați DLL-uri după cum este necesar și permite programului să gestioneze situațiile care apar în absența unui DLL.

În cazul încărcării explicite, procesul de lucru cu DLL are loc în trei etape:

1. Încărcați DLL folosind funcția ::FreeLibrary(hMyDll);(sau analogul său extins LoadLibraryEx). Dacă încărcarea are succes, funcția returnează un descriptor hLib de tip HMODULE, care permite accesul suplimentar la acest DLL.

2. Apeluri de funcții GetProcAddress pentru a obține indicatorii către funcțiile necesare sau alte obiecte. Ca prim parametru funcția GetProcAddress primește descriptorul hLib, iar ca al doilea parametru adresa șirului cu numele funcției importate. Pointerul rezultat este apoi folosit de client. De exemplu, dacă acesta este un indicator de funcție, atunci funcția dorită este apelată.

3. Când nu mai este necesară o bibliotecă dinamică încărcată, se recomandă eliberarea ei apelând funcția FreeLibrary. Eliberarea unei biblioteci nu înseamnă că sistemul de operare o va elimina imediat din memorie. Întârzierea de descărcare este prevăzută pentru cazul în care același DLL este nevoie din nou de un proces după ceva timp. Dar dacă există probleme cu RAM, Windows elimină mai întâi bibliotecile eliberate din memorie.

Să ne uităm la încărcarea leneșă a unui DLL. Un DLL cu încărcare întârziată este un DLL legat implicit care nu este încărcat până când codul accesează un identificator exportat de la acesta. Astfel de DLL-uri pot fi utile în următoarele situații:

Dacă o aplicație folosește mai multe DLL-uri, inițializarea poate dura mult timp pentru ca încărcătorul să mapeze toate DLL-urile în spațiul de adrese al procesului. DLL-urile cu încărcare leneră rezolvă această problemă prin distribuirea încărcării DLL-ului pe parcursul execuției aplicației.

Dacă o aplicație este proiectată să funcționeze în diferite versiuni ale sistemului de operare, atunci unele dintre funcții pot apărea numai în versiunile ulterioare ale sistemului de operare și să nu fie utilizate în versiunea curentă. Dar dacă programul nu apelează o anumită funcție, atunci nu are nevoie de DLL și poate continua să funcționeze în siguranță. Când te referi la un inexistent

Funcția poate fi configurată să emită un avertisment adecvat utilizatorului.

Pentru a implementa metoda de încărcare lenenă, trebuie să adăugați în plus la lista bibliotecilor de linker nu numai biblioteca de import necesară MyLib.lib, ci și biblioteca de import de sistem delayimp.lib. În plus, trebuie să adăugați indicatorul / delayload:MyLib.dll la opțiunile de linker.

Aceste setări determină linker-ul să efectueze următoarele operații:

Implementați o funcție specială în modulul EXE
delayLoadHelper;

Eliminați MyLib.dll din secțiunea de import al modulelor executabile, astfel încât încărcătorul sistemului de operare să nu încerce să încarce implicit această bibliotecă în timpul fazei de pornire a aplicației;

Adăugați o nouă secțiune de import amânat la fișierul EXE cu o listă de funcții importate din MyLib.dll;

Convertiți apelurile de funcție din DLL în apeluri
delayLoadhelper.

În etapa de execuție a aplicației, un apel de funcție de la un DLL este implementat prin apelarea delayLoadHelper. Această funcție, folosind informații din secțiunea de import amânat, apelează mai întâi LoadLibrary și apoi GetprocAddress. După ce a primit adresa unei funcții DLL, delayLoadHelper se asigură că în viitor această funcție DLL este apelată direct. Rețineți că fiecare funcție din DLL este configurată individual prima dată când este apelată.

Utilizare DLL(biblioteca de linkuri dinamice) este utilizată pe scară largă în programarea Windows. DLL este de fapt parte din codul unui fișier executabil cu extensia DLL. Orice program poate apela DLL.

Avantaj DLL este după cum urmează:

  • Reutilizarea codului.
  • Partajați codul între aplicații.
  • Partajarea codului.
  • Consum îmbunătățit de resurse în Windows.

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) ( HINSTANCE hMyDll; if((hMyDll=LoadLibrary("MyDLL"))==NULL) returnează 1; PFN_fNPFMFnction; Funcție)G etProcAddress( hMyDll ,"MyFunction"); int iCode=(*pfnMyFunction)("Salut");

Pe meniu Fişier selectați Nou -> Altele. În caseta de dialog din filă Nou selecta Expert DLL. Un modul va fi creat automat - un șablon gol pentru viitor DLL.

Sintaxa DLL

Pentru a construi DLL, selectați Proiect -> Construire Nume_proiect .

Vizibilitatea funcțiilor și procedurilor

Funcțiile și procedurile pot fi locale și exportate din DLL.

Local

Funcțiile și procedurile locale pot fi utilizate intern DLL. Sunt vizibile doar în interiorul bibliotecii și niciun program nu le poate apela din exterior.

Exportat

Funcțiile și procedurile exportate pot fi utilizate în exterior DLL. Alte programe pot apela astfel de funcții și proceduri.

Codul sursă de mai sus folosește funcția exportată. Numele funcției urmează un cuvânt rezervat Exporturi.

Se încarcă DLL-uri

Delphi are două tipuri de încărcare DLL:

Încărcare statică

Când porniți aplicația, se încarcă automat. Rămâne în memorie pe toată durata execuției programului. Foarte usor de folosit. Doar adăugați un cuvânt extern după declararea unei funcţii sau proceduri.

Funcția SummaTotal(factor: întreg): întreg; registru; extern „Example.dll”;

Dacă DLL nu va fi găsit, programul va continua să ruleze.

încărcat în memorie după cum este necesar. Implementarea sa este mai complexă deoarece trebuie să-l încărcați și să îl descărcați singur din memorie. Memoria este folosită mai puțin, astfel încât aplicația rulează mai repede. Programatorul însuși trebuie să se asigure că totul funcționează corect. Pentru a face acest lucru aveți nevoie de:

  • Declarați tipul funcției sau procedurii descrise.
  • Încărcați biblioteca în memorie.
  • Obțineți adresa unei funcții sau proceduri în memorie.
  • Apelați o funcție sau o procedură.
  • Descărcați biblioteca din memorie.

Declarație de tip care descrie o funcție

tip TSumaTotal = function(factor: integer): integer;

Se încarcă biblioteca

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

Obținem un pointer către funcție

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

Apelarea unei funcții

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

Descărcarea unei biblioteci din memorie

FreeLibrary(dll_instanță);

Apel dinamic DLL necesită mai multă muncă, dar este mai ușor de gestionat resursele din memorie. Dacă trebuie să utilizați DLLîn cadrul programului, atunci încărcarea statică este de preferat. Nu uitați să utilizați un bloc incearca...mai putinŞi incearca... in sfarsit pentru a evita erorile în timpul executării programului.

Exportați șiruri

Creat DLL folosind Delphi, poate fi folosit și în programe scrise în alte limbaje de programare. Din acest motiv, nu putem folosi niciun tip de date. Este posibil ca tipurile care există în Delphi să nu existe în alte limbi. Este recomandabil să utilizați tipuri de date native din Linux sau Windows. Noastre DLL poate fi folosit de un alt programator care folosește un alt limbaj de programare.

Poate fi folosit liniiŞi matrice dinamice V DLL, scris în Delphi, dar pentru aceasta trebuie să conectați modulul ShareMem la secţionare utilizări V DLLși programul care îl va folosi. În plus, acest anunț trebuie să fie primul din secțiune utilizări fiecare dosar de proiect.

Tipuri şir, după cum știm, C, C++ și alte limbaje nu există, așa că este recomandabil să folosiți în schimb PChar.

Exemplu DLL

biblioteca Exemplu_dll; folosește SysUtils, Classes; var (Declararea variabilelor) k1: întreg;

k2:întreg; (Declară o funcție) function SummaTotal(factor: integer): integer; registru; factor de început:= factor * k1 + factor;

factor:= factor * k2 + factor;

După cum probabil știți, bibliotecile de link-uri dinamice (DLL) folosesc convențiile limbajului C atunci când declară obiectele exportate, în timp ce C++ folosește un sistem ușor diferit pentru generarea de nume atunci când este compilat, așa că nu puteți doar exporta funcții - metode ale unei clase C++ și apoi utilizați le în codul aplicației client (în continuare, client înseamnă o aplicație care utilizează DLL). Cu toate acestea, acest lucru se poate face folosind interfețe care sunt disponibile atât pentru DLL, cât și pentru aplicația client. Această metodă este foarte puternică și în același timp elegantă, deoarece... clientul vede doar interfața abstractă, iar clasa reală care implementează toate funcțiile poate fi orice. Tehnologia COM (Component Object Model) de la Microsoft este construită pe o idee similară (plus funcționalitate suplimentară, desigur. Acest articol vă va arăta cum să utilizați abordarea „clasă” folosind o interfață asemănătoare COM la început (în stadiul de compilare). și legare tardivă (în timp ce programul rulează).

Dacă ați lucrat vreodată cu DLL-uri, atunci știți deja că DLL-urile au o funcție specială DllMain(). Această funcție este similară cu WinMain sau main(), prin aceea că este un fel de punct de intrare într-un DLL. Sistemul de operare apelează automat această funcție atunci când DLL-ul este încărcat și descărcat. De obicei, această funcție nu este folosită pentru nimic altceva.

Există două metode de conectare a unui DLL la un proiect - legarea timpurie (la compilarea programului) și întârziere (în timpul execuției programului). Metodele diferă în modul în care DLL-ul este încărcat și în modul în care sunt apelate funcțiile implementate și exportate din DLL.

Legarea timpurie (în timpul compilării programului)

Cu această metodă de conectare, sistemul de operare încarcă automat DLL-ul când programul pornește. Cu toate acestea, este necesar ca proiectul în curs de dezvoltare să includă un fișier .lib (fișier de bibliotecă) corespunzător acestui DLL. Acest fișier definește toate obiectele DLL exportate. Declarațiile pot conține funcții sau clase C obișnuite. Tot ce trebuie să facă clientul este să folosească acest fișier .lib și să includă fișierul antet DLL - iar sistemul de operare va încărca automat acest DLL. După cum puteți vedea, această metodă pare foarte ușor de utilizat, deoarece... totul este transparent. Cu toate acestea, trebuie să fi observat că codul clientului trebuie să fie recompilat ori de câte ori codul DLL este modificat și, în consecință, este generat un nou fișier .lib. Dacă acest lucru este convenabil pentru aplicația dvs., depinde de dvs. să decideți. Un DLL poate declara funcțiile pe care dorește să le exporte în două metode. Metoda standard este utilizarea fișierelor .def. Acest fișier .def este pur și simplu o listă a funcțiilor exportate din DLL.

//================================================== ================ ============ // Fișier .def BIBLIOTECĂ myfirstdll.dll DESCRIERE „Primul meu DLL” EXPORTĂ Funcția mea //=== ================ ==================================== ==================== // Antetul DLL care va fi inclus în codul clientului bool MyFunction(int parms); //================================================== ================ ============ // implementarea funcției în DLL bool MyFunction(int parms) ( // faceți tot ce este necesar ............ )

Cred că este de la sine înțeles că în acest exemplu este exportată o singură funcție, MyFunction. A doua metodă de declarare a obiectelor exportate este specifică, dar mult mai puternică: puteți exporta nu numai funcții, ci și clase și variabile. Să ne uităm la fragmentul de cod generat când VisualC++ AppWizard a creat DLL-ul Comentariile incluse în listă sunt suficiente pentru a înțelege cum funcționează totul.

//================================================== ================ ============ // Antetul DLL care ar trebui inclus în codul clientului /* Următorul bloc ifdef este o metodă standard pentru crearea unei macrocomenzi care facilitează exportul dintr-un DLL. Toate fișierele din acest DLL sunt compilate cu o cheie specifică MYFIRSTDLL_EXPORTS. Această cheie nu este definită pentru niciunul dintre proiectele care utilizează acest DLL. Astfel, orice proiect care include acest fișier vede funcțiile MYFIRSTDLL_API ca fiind importate din DLL, în timp ce DLL-ul în sine vede aceleași funcții ca și exportate. */ #ifdef MYFIRSTDLL_EXPORTS #define MYFIRSTDLL_API __declspec(dllexport) #else #define MYFIRSTDLL_API __declspec(dllimport) #endif // Clasa este exportată de la test2.clasa MYFIRSTDLL_API CMyFirst TOCMyDllFirst2. puteți adăuga propriile metode); extern MYFIRSTDLL_API int nMyFirstDll; MYFIRSTDLL_API int fnMyFunction(void);

În timpul compilării DLL, cheia MYFIRSTDLL_EXPORTS este definită, astfel încât cuvântul cheie __declspec(dllexport) este prefixat declarațiilor de obiect exportate.

Și când codul clientului este compilat, această cheie este nedefinită și obiectele sunt prefixate cu __declspec(dllimport), astfel încât clientul să știe ce obiecte sunt importate din DLL.

În ambele cazuri, tot ce trebuie să facă clientul este să adauge fișierul myfirstdll.lib în proiect și să includă un fișier antet care declară obiectele importate din DLL și apoi să folosească acele obiecte (funcții, clase și variabile) exact ca și cum ar fi acestea au fost definite și implementate la nivel local în proiect. Acum să ne uităm la o altă metodă de utilizare a DLL-urilor, care este adesea mai convenabilă și mai puternică.

Când se utilizează legarea tardivă, DLL-ul nu este încărcat automat la pornirea programului, ci direct în cod, acolo unde este necesar. Nu trebuie utilizate fișiere .lib, astfel încât aplicația client nu necesită recompilare atunci când DLL-ul se modifică. Această legătură este puternică tocmai pentru că TU decizi când și ce DLL să încarci. De exemplu, să presupunem că scrieți un joc care folosește DirectX și OpenGL. Puteți include pur și simplu tot codul necesar în fișierul executabil, dar atunci va fi pur și simplu imposibil să înțelegeți nimic. Sau puteți pune codul DirectX într-un DLL și codul OpenGL în altul și să le conectați static la proiect. Dar acum tot codul este interdependent, așa că dacă scrieți un nou DLL care conține cod DirectX, va trebui să recompilați și fișierul executabil. Singura comoditate este că nu trebuie să vă faceți griji cu privire la încărcare (deși nu se știe dacă aceasta este o comoditate dacă încărcați ambele DLL-uri, ocupând memorie și chiar aveți nevoie de unul dintre ele). În cele din urmă, după părerea mea, cea mai bună idee este să lăsați executabilul să decidă ce DLL să încarce la pornire. De exemplu, dacă programul stabilește că sistemul nu acceptă accelerarea OpenGL, atunci este mai bine să încărcați un DLL cu cod DirectX, altfel încărcați OpenGL. Prin urmare, conectarea tardivă economisește memorie și reduce dependența dintre DLL și executabil. Cu toate acestea, în acest caz, se impune o restricție asupra obiectelor exportate - doar funcțiile în stil C pot fi exportate. Clasele și variabilele nu pot fi încărcate dacă programul folosește legarea tardivă. Să vedem cum să ocolim această limitare folosind interfețe.

Un DLL proiectat pentru legarea tardivă utilizează de obicei un fișier .def pentru a defini obiectele pe care dorește să le exporte. Dacă nu doriți să utilizați un fișier .def, puteți utiliza pur și simplu prefixul __declspec(dllexport) înainte de funcțiile exportate. Ambele metode fac același lucru. Clientul încarcă DLL trecând numele fișierului DLL la funcția Win32 LoadLibrary() Această funcție returnează mânerul HINSTANCE, care este folosit pentru a lucra cu DLL și care este necesar pentru a descărca DLL-ul din memorie atunci când nu este. mai nevoie. După încărcarea DLL-ului, clientul poate obține un pointer către orice funcție folosind funcția GetProcAddress(), folosind numele funcției necesare ca parametru.

//================================================== ================ ============ // Fișier .def BIBLIOTECĂ myfirstdll.dll DESCRIERE „Primul meu DLL” EXPORTĂ Funcția mea //=== ================ ==================================== ==================== /* Implementarea funcției în DLL */ bool MyFunction(int parms) ( //do ceva......... ... ) //======================= ====================== ================ //Cod client /* Declarația funcției este într-adevăr necesară doar pentru a determina parametrii. Declarațiile de funcții sunt de obicei conținute într-un fișier antet furnizat împreună cu DLL. Cuvântul cheie extern C dintr-o declarație de funcție îi spune compilatorului să folosească convențiile de denumire a variabilelor C */ extern "C" bool MyFunction(int parms); typedef bool (*MYFUNCTION)(int parms); MYFUNCTION pfnMyFunc=0; //indicator la MyFunction HINSTANCE hMyDll = ::LoadLibrary("myfirstdll.dll"); if(hMyDll != NULL) ( //Determinați adresa funcției pfnMyFunc= (MYFUNCTION)::GetProcAddress(hMyDll, "MyFunction"); //Dacă nu reușește, descărcați DLL if(pfnMyFunc== 0) ( :: FreeLibrary(hMyDll) ) //Apelați funcția bool result = pfnMyFunc(parms //Descărcați DLL-ul dacă nu mai avem nevoie de el::FreeLibrary(hMyDll);

După cum puteți vedea, codul este destul de simplu. Acum să vedem cum poate fi implementat lucrul cu „clase”. După cum s-a menționat mai devreme, dacă se folosește legarea tardivă, nu există o modalitate directă de a importa clase din DLL, așa că trebuie să implementăm „funcționalitatea” clasei folosind o interfață care conține toate funcțiile publice, excluzând constructorul și destructorul. Interfața va fi o structură C/C++ obișnuită care conține doar funcții membre abstracte virtuale.

//================================================== ================ ============ // Fişier .def LIBRARY myinterface.dll DESCRIERE "implementează interfaţa I_MyInterface EXPORTĂ GetMyInterface FreeMyInterface //== === ================================================= ======= // Fișier antet folosit în Dll și client // care declară interfața // I_MyInterface.h struct I_MyInterface ( virtual bool Init(int parms)=0; virtual bool Release()=0; virtual void DoStuff() =0 /* Declarații ale Dll-urilor exportate și ale funcțiilor care definesc tipurile de indicatori de funcție pentru o încărcare ușoară și lucrul cu funcții. Rețineți prefixul extern „C”, care spune compilatorului că sunt utilizate funcții în stil 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) ============================================ // Implementarea interfeţei în Dll // Clasa MyInterface.h CMyClass: public I_MyInterface ( public: bool Init(int parms); = 0) pfnFree(&hMyDll);

Clasa actuală din DLL va moșteni din această structură și va implementa toate funcțiile definite în interfață. Acum, pentru a accesa această clasă dintr-o aplicație client, tot ce trebuie să facem este să exportăm funcțiile în stil C corespunzătoare instanței clasei și să le legăm la interfața pe care o definim astfel încât clientul să le poată utiliza. Pentru a implementa o astfel de metodă, avem nevoie de încă două funcții, dintre care una va crea interfața, iar a doua va șterge interfața după ce am terminat de lucrat cu ea. Un exemplu de implementare a acestei idei este prezentat mai jos.

Aceste informații sunt suficiente pentru a simți toată comoditatea utilizării interfețelor. Programare fericită!

Cu o legătură implicită, funcțiile DLL-ului încărcat sunt adăugate la secțiunea de import a fișierului de apelare. Când un astfel de fișier este lansat, încărcătorul sistemului de operare analizează secțiunea de import și include toate bibliotecile specificate. Datorită simplității sale, această metodă este foarte populară; dar simplitatea este simplitate, iar aspectul implicit are anumite dezavantaje și limitări:

toate DLL-urile conectate sunt întotdeauna încărcate, chiar dacă programul nu accesează niciodată niciunul dintre ele pe parcursul întregii sesiuni de operare;

dacă lipsește cel puțin unul dintre DLL-urile necesare (sau DLL-ul nu exportă cel puțin o funcție necesară) - încărcarea fișierului executabil este întreruptă de mesajul „Biblioteca de link-uri dinamice nu a putut fi găsită” (sau ceva de genul acesta) - chiar dacă absența acestui DLL nu este critică pentru executarea programului. De exemplu, un editor de text ar putea funcționa destul de bine într-o configurație minimă - fără un modul de imprimare, care să afișeze tabele, grafice, formule și alte componente minore, dar dacă aceste DLL-uri sunt încărcate prin linkuri implicite - vă place sau nu, va trebui să „trageți”-le împreună cu tine.

DLL-ul este căutat în următoarea ordine: în directorul care conține fișierul apelant; în directorul de proces curent; în directorul de sistem %Windows%System%; în directorul principal %Windows%; în directoarele specificate în variabila PATH. Este imposibil să setați o cale de căutare diferită (sau mai degrabă, este posibil, dar acest lucru va necesita efectuarea de modificări în registrul sistemului, iar aceste modificări vor afecta toate procesele care rulează în sistem - ceea ce nu este bine).

Legătura explicită elimină toate aceste dezavantaje - cu prețul unei anumite complexități a codului. Programatorul însuși va trebui să se ocupe de încărcarea DLL-ului și de conectarea funcțiilor exportate (fără a uita de controlul erorilor, altfel la un moment dat sistemul se va îngheța). Dar legătura explicită vă permite să încărcați DLL-uri după cum este necesar și oferă programatorului posibilitatea de a gestiona în mod independent situațiile în care DLL-ul lipsește. Puteți merge mai departe - nu setați în mod explicit numele DLL în program, ci scanați un anume director pentru prezența bibliotecilor dinamice și conectați toate cele găsite la aplicație. Exact așa funcționează mecanismul de suport pentru plug-in în popularul manager de fișiere FAR (și nu numai în acesta).

  • Serghei Savenkov

    un fel de recenzie „scurtă”... de parcă s-ar grăbi undeva