LA CAMPANA

C'è chi ha letto questa notizia prima di te.
Iscriviti per ricevere gli ultimi articoli.
E-mail
Nome
Cognome
Come vuoi leggere La Campana?
Niente spam

Come probabilmente saprai, le librerie a collegamento dinamico (DLL) utilizzano le convenzioni del linguaggio C quando dichiarano oggetti esportati, mentre C++ utilizza un sistema di generazione dei nomi di compilazione leggermente diverso, quindi non puoi semplicemente esportare funzioni - metodi della classe C++ e quindi utilizzarli nel codice dell'applicazione client (di seguito, per client si intende l'applicazione che utilizza la DLL). Tuttavia, questa operazione può essere eseguita utilizzando le interfacce disponibili sia per la DLL che per l'applicazione client. Questo metodo è molto potente ed elegante allo stesso tempo. il client vede solo l'interfaccia astratta e la classe effettiva che implementa tutte le funzioni può essere qualsiasi cosa. La tecnologia COM di Microsoft (Componente Modello oggetto) si basa su un'idea simile (oltre a funzionalità aggiuntive, ovviamente). Questo articolo ti mostrerà come utilizzare un approccio di "classe" con un'interfaccia simile a COM per l'associazione anticipata (in fase di compilazione) e tardiva (in fase di esecuzione).

Se hai mai lavorato con una DLL, allora sai già che una DLL ha una funzione speciale DllMain(). Questa funzione è simile a WinMain, o main(), nel senso che è una sorta di punto di ingresso della DLL. Il sistema operativo chiama automaticamente questa funzione se la DLL viene caricata e scaricata. Di solito questa funzione non viene utilizzata per nient'altro.

Esistono due metodi per collegare una DLL a un progetto: collegamento in anticipo (in fase di compilazione) e in ritardo (in fase di esecuzione). I metodi differiscono nel modo in cui viene caricata la DLL e nel modo in cui vengono chiamate le funzioni implementate ed esportate dalla DLL.

Associazione anticipata (in fase di compilazione)

Con questo metodo di collegamento, il sistema operativo carica automaticamente la DLL durante l'avvio del programma. Tuttavia, è necessario che il progetto in fase di sviluppo includa un file .lib (file di libreria) corrispondente a questa DLL. Questo file definisce tutti gli oggetti DLL esportati. Le dichiarazioni possono contenere funzioni o classi C regolari. Tutto ciò che il client deve fare è utilizzare questo file .lib e includere il file di intestazione della DLL e il sistema operativo caricherà automaticamente questa DLL. Come puoi vedere, questo metodo sembra molto facile da usare, dal momento che tutto è trasparente. Tuttavia, dovresti aver notato che il codice client deve essere ricompilato ogni volta che il codice DLL cambia e di conseguenza viene generato un nuovo file .lib. Sta a te decidere se è conveniente per la tua applicazione. Una DLL può dichiarare le funzioni che vuole esportare con due metodi. Il metodo standard consiste nell'utilizzare i file .def. Questo file .def è solo un elenco delle funzioni esportate dalla DLL.

// =============================================== ============= // LIBRERIA di file .def myfirstdll.dll DESCRIZIONE "La mia prima DLL" ESPORTA MyFunction // ================== = ========================================= // Intestazione DLL che verrà inclusa nel codice client bool MyFunction (int parms); // =============================================== ============ // implementazione della funzione nella DLL bool MyFunction (int parms) (// fai tutto il necessario ............)

Penso che sia possibile non dirlo in questo esempio viene esportata una sola MyFunction. Il secondo metodo per dichiarare gli oggetti esportati è specifico, ma molto più potente: puoi esportare non solo funzioni, ma anche classi e variabili. Diamo un'occhiata allo snippet di codice generato quando è stata creata la DLL AppWizard di VisualC ++ I commenti inclusi nell'elenco sono abbastanza per capire come funziona il tutto.

// =============================================== ============= // Intestazione DLL da includere nel codice client / * Blocco ifdef successivo - metodo standard creare una macro che renda più semplice l'esportazione delle DLL. Tutti i file di questa DLL vengono compilati con una chiave specifica MYFIRSTDLL_EXPORTS. Questa chiave non è definita per nessuno dei progetti che utilizzano questa DLL. Pertanto, qualsiasi progetto che include questo file vede le funzioni MYFIRSTDLL_API come importate da una DLL, mentre la DLL stessa vede queste stesse funzioni come esportate. * / #ifdef MYFIRSTDLL_EXPORTS #define MYFIRSTDLL_API __declspec (dllexport) #else #define MYFIRSTDLL_API __declspec (dllimport) #endif // La classe viene esportata dai metodi di classe test2.dll MYFIRSTDLL_Fll_API (CMyFid).); extern MYFIRSTDLL_API int nMyFirstDll; MYFIRSTDLL_API int fnMyFunction (void);

Durante la compilazione della DLL viene definita la chiave MYFIRSTDLL_EXPORTS, quindi la parola chiave __declspec (dllexport) viene sostituita prima delle dichiarazioni degli oggetti esportati. E quando il codice client viene compilato, questa chiave non è definita e gli oggetti sono preceduti da __declspec (dllimport) in modo che il client sappia quali oggetti vengono importati dalla DLL.

In entrambi i casi, tutto ciò che il client deve fare è aggiungere il file myfirstdll.lib al progetto e includere un file di intestazione che dichiari gli oggetti importati dalla DLL, quindi utilizzare questi oggetti (funzioni, classi e variabili) esattamente come se sono stati definiti e implementati localmente nel progetto. Ora diamo un'occhiata a un altro metodo di utilizzo delle DLL, che è spesso più conveniente e potente.

Rilegatura tardiva (mentre il programma è in esecuzione)

Quando si utilizza il collegamento tardivo, la DLL non viene caricata automaticamente all'avvio del programma, ma direttamente nel codice dove è necessaria. Non è necessario utilizzare alcun file .lib, quindi l'applicazione client non deve essere ricompilata quando la DLL cambia. Questo collegamento è potente proprio perché TU decidi quando e quale DLL caricare. Ad esempio, stai scrivendo un gioco che utilizza DirectX e OpenGL. Puoi semplicemente includere tutto il codice necessario nel file eseguibile, ma poi sarà semplicemente impossibile capire qualcosa. Oppure puoi inserire il codice DirectX in una DLL e il codice OpenGL in un'altra e collegarli staticamente al tuo progetto. Ma ora tutto il codice è interdipendente, quindi se scrivi una nuova DLL contenente codice DirectX, dovrai ricompilare anche l'eseguibile. L'unica comodità è che non devi preoccuparti del caricamento (anche se non è noto se questa sia una comodità se carichi entrambe le DLL, occupando memoria e in realtà ne hai solo bisogno). E infine, secondo me, migliore ideaè lasciare che l'eseguibile decida quale DLL caricare all'avvio. Ad esempio, se il programma determina che il sistema non supporta l'accelerazione OpenGL, è meglio caricare DLL con codice DirectX, altrimenti caricare OpenGL. Pertanto, l'associazione tardiva consente di risparmiare memoria e riduce la dipendenza tra la DLL e l'eseguibile. Tuttavia, in questo caso, viene imposta una restrizione sugli oggetti esportati: è possibile esportare solo le funzioni di tipo C. Le classi e le variabili non possono essere caricate se il programma utilizza l'associazione tardiva. Vediamo come aggirare questa limitazione utilizzando le interfacce.

Una DLL progettata per il collegamento tardivo utilizza in genere un file .def per definire quali oggetti desidera esportare. Se non vuoi usare un file .def, puoi semplicemente usare il prefisso __declspec (dllexport) davanti alle funzioni esportate. Entrambi i metodi fanno la stessa cosa. Il client carica la DLL passando il nome del file DLL alla funzione Win32 LoadLibrary().Questa funzione restituisce l'handle HINSTANCE, che viene utilizzato per lavorare con la DLL e che è necessario per scaricare la DLL dalla memoria quando non è più necessario. Dopo aver caricato la DLL, il client può ottenere un puntatore a qualsiasi funzione utilizzando la funzione GetProcAddress(), utilizzando il nome della funzione richiesta come parametro.

// =============================================== ============= // LIBRERIA di file .def myfirstdll.dll DESCRIZIONE "La mia prima DLL" ESPORTA MyFunction // ================== = ========================================= / * Implementazione della funzione in DLL * / bool MyFunction (int parms) (// fai qualcosa ............) // ====================== == ==================================== // Codice cliente / * La dichiarazione della funzione è davvero necessaria solo per per definire i parametri. Le dichiarazioni di funzione sono generalmente contenute in un file di intestazione fornito con la DLL. La parola chiave extern C nella dichiarazione della funzione dice al compilatore di usare le convenzioni di denominazione C * / extern "C" bool MyFunction (int parms); typedef bool (* MYFUNCTION) (int parms); MYFUNCTION pfnMyFunc = 0; // puntatore a MyFunction HINSTANCE hMyDll = :: LoadLibrary ("myfirstdll.dll"); if (hMyDll! = NULL) (// Determina l'indirizzo della funzione pfnMyFunc = (MYFUNCTION) :: GetProcAddress (hMyDll, "MyFunction"); // Se non riesce, scarica la DLL if (pfnMyFunc == 0) (:: FreeLibrary (hMyDll); return;) // Chiama la funzione bool result = pfnMyFunc (parms); // Scarica la DLL se non ne abbiamo più bisogno :: FreeLibrary (hMyDll);)

Come puoi vedere, il codice è piuttosto semplice. Ora vediamo come si può implementare il lavoro con le "classi". Come affermato in precedenza, se viene utilizzata l'associazione tardiva, non esiste un modo diretto per importare classi da una DLL, quindi è necessario implementare la "funzionalità" della classe utilizzando un'interfaccia che contenga tutte le funzioni pubbliche tranne il costruttore e il distruttore. L'interfaccia sarà una normale struttura C/C++ contenente solo funzioni membro astratte virtuali. La classe effettiva nella DLL erediterà da questa struttura e implementerà tutte le funzionalità definite nell'interfaccia. Ora, per accedere a questa classe dall'applicazione client, tutto ciò che devi fare è esportare le funzioni in stile C corrispondenti all'istanza della classe e associarle all'interfaccia che abbiamo definito in modo che il client possa utilizzarle. Per implementare un tale metodo, sono necessarie altre due funzioni, una delle quali creerà l'interfaccia e la seconda eliminerà l'interfaccia dopo aver finito di lavorarci. Di seguito viene fornito un esempio di implementazione di questa idea.

// ================================================ ============ // LIBRERIA file .def myinterface.dll DESCRIZIONE "implementa l'interfaccia I_MyInterface ESPORTA GetMyInterface FreeMyInterface // ================== = ========================================= // File di intestazione utilizzato in Dll e client, // che dichiara l'interfaccia // I_MyInterface.h struct I_MyInterface (virtual bool Init (int parms) = 0; virtual bool Release () = 0; virtual void DoStuff () = 0;); / * Dichiarazioni delle funzioni esportate Dll e definizioni di tipo per puntatori a funzione per un facile caricamento e manipolazione delle funzioni Nota il prefisso extern "C", che dice al compilatore di usare funzioni in stile 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);) // =========== === =============================== ============== // Implementazione dell'interfaccia in Dll // MyInterface.h class CMyClass: public I_MyInterface (public: bool Init (int parms); bool Rilascio (); void DoStuff (); CMyClass (); ~ CMyClass (); // qualsiasi altro membro della classe ............ privato: // qualsiasi altro membro della classe ............); // ================================================ ============ // Funzioni esportate che creano e distruggono un'interfaccia // 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;) // ========== ================================================== // Codice client // Dichiarazioni interfaccia e chiamate di funzione GETINTERFACE pfnInterface = 0; // puntatore alla funzione GetMyInterface I_MyInterface * pInterface = 0; // puntatore alla struttura MyInterface HINSTANCE hMyDll = :: LoadLibrary ("myinterface.dll"); if (hMyDll! = NULL) (// Determina l'indirizzo della funzione pfnInterface = (GETINTERFACE) :: GetProcAddress (hMyDll, "GetMyInterface"); // Scarica la DLL se l'operazione precedente non è riuscita if (pfnInterface == 0) ( :: FreeLibrary (hMyDll); return;) // Chiama la funzione HRESULT hr = pfnInterface (& pInterface); // Scarica se non ha successo if (FAILED (hr)) (:: FreeLibrary (hMyDll); return;) // Il l'interfaccia è caricata, puoi chiamare le funzioni pInterface-> Init (1); pInterface-> DoStuff (); pInterface-> Release (); // Libera l'interfaccia FREEINTERFACE pfnFree = (FREEINTERFACE) :: GetProcAddress (hMyDll, "FreeMyInterface" ); if (pfnFree! = 0) pfnFree (& hMyDll); // Scarica DLL :: FreeLibrary (hMyDll); )

Queste informazioni sono sufficienti per farti sentire tutta la comodità di utilizzare le interfacce. Buona programmazione!

Fin dalla nascita (o poco dopo), il sistema operativo Windows utilizzava delle DLL (Dynamic Link Library), che contenevano implementazioni delle funzioni più comunemente utilizzate. Gli eredi di Windows - NT e Windows 95, così come OS / 2 - dipendono anche dalle DLL per fornire gran parte delle loro funzionalità.

Diamo un'occhiata a una serie di aspetti della creazione e dell'utilizzo delle DLL:

  • Come collegare staticamente le DLL
  • come caricare dinamicamente le DLL;
  • come creare DLL;
  • Come creare estensioni DLL MFC.

Utilizzo della DLL

Quasi impossibile da creare Applicazione Windows che non usa le DLL. Una DLL contiene tutte le funzioni API Win32 e innumerevoli altre funzioni dei sistemi operativi Win32.

In generale, le DLL sono semplicemente raccolte di funzioni assemblate in librerie. Tuttavia, a differenza dei loro cugini statici (file .lib), le DLL non sono direttamente collegate agli eseguibili utilizzando il linker. Il file eseguibile contiene solo informazioni sulla loro posizione. Al momento dell'esecuzione del programma viene caricata l'intera libreria. Ciò consente a processi diversi di condividere le stesse librerie in memoria. Questo approccio consente di ridurre la quantità di memoria richiesta per più applicazioni che utilizzano molte librerie condivise, nonché di controllare la dimensione dei file EXE.

Tuttavia, se la libreria viene utilizzata da una sola applicazione, è meglio renderla normale e statica. Naturalmente, se le funzioni in esso contenute verranno utilizzate in un solo programma, sarà sufficiente inserirvi il file sorgente corrispondente.

Nella maggior parte dei casi, un progetto è collegato a una DLL in modo statico o implicito al momento del collegamento. Il sistema operativo controlla il caricamento della DLL durante l'esecuzione del programma. Tuttavia, una DLL può essere caricata in modo esplicito o dinamico nel corso di un'applicazione.

Importa librerie

Quando una DLL è collegata staticamente, il nome del file .lib è determinato tra gli altri parametri del linker in riga di comando o nella scheda Collegamento della finestra di dialogo Impostazioni progetto in Developer Studio. Tuttavia file .lib utilizzato da quando si collega implicitamente una DLL, non è una normale libreria statica. Tali file .lib sono chiamati importare librerie(importare librerie). Non contengono il codice della libreria in sé, ma solo riferimenti a tutte le funzioni esportate dal file DLL, in cui è memorizzato tutto. Di conseguenza, le librerie di importazione tendono ad essere più piccole dei file DLL. Torneremo più avanti sui metodi della loro creazione. Ora diamo un'occhiata ad altri problemi relativi al collegamento implicito delle librerie dinamiche.

Negoziazione dell'interfaccia

Quando si utilizzano librerie proprie o di terze parti, è necessario prestare attenzione a far corrispondere la chiamata di funzione al suo prototipo.

Se il mondo fosse perfetto, i programmatori non dovrebbero preoccuparsi di abbinare le interfacce delle funzioni quando collegano le librerie: sarebbero tutte uguali. Tuttavia, il mondo è tutt'altro che perfetto, e molti grandi programmi sono scritti utilizzando varie librerie senza C++.

Per impostazione predefinita, in Visual C ++, le interfacce delle funzioni concordano in base alle regole C ++. Ciò significa che i parametri vengono inseriti nello stack da destra a sinistra, il chiamante è responsabile della loro rimozione dallo stack quando la funzione esce ed espande il suo nome. La modifica del nome consente al linker di distinguere tra funzioni sovraccaricate, ad es. funzioni con lo stesso nome ma elenchi di argomenti diversi. Tuttavia, nella vecchia libreria C, non ci sono funzioni con nome esteso.

Sebbene tutte le altre regole per chiamare una funzione in C siano identiche a quelle per chiamare una funzione in C++, i nomi delle funzioni non vengono espansi nelle librerie C. Sono solo aggiunti con un carattere di sottolineatura (_) davanti.

Se devi connettere una libreria C a un'applicazione C++, tutte le funzioni di questa libreria dovranno essere dichiarate come esterne in formato C:

Extern "С" int MyOldCFunction (int myParam);

Le dichiarazioni delle funzioni di libreria vengono solitamente inserite nel file di intestazione della libreria, sebbene le intestazioni della maggior parte delle librerie C non siano destinate all'uso nei progetti C ++. In questo caso, è necessario creare una copia del file di intestazione e includervi il modificatore "C" esterno nella dichiarazione di tutte le funzioni di libreria utilizzate. Il modificatore extern "C" può essere applicato anche all'intero blocco, a cui è collegato il vecchio file di intestazione C tramite la direttiva #tinclude.Quindi, invece di modificare ciascuna funzione separatamente, puoi cavartela con solo tre righe:

Esterna "C" (#include "MyCLib.h")

Anche i programmi per le versioni precedenti di Windows utilizzavano le convenzioni di chiamata delle funzioni lingua PASCAL per le funzioni API di Windows... I programmi più recenti dovrebbero utilizzare il modificatore winapi convertito in _stdcall. Sebbene non sia un'interfaccia di funzione C o C ++ standard, viene utilizzata per chiamare le funzioni API di Windows. Tuttavia, di solito tutto questo è già preso in considerazione nelle intestazioni standard di Windows.

Caricamento di una DLL di collegamento implicito

All'avvio, l'applicazione cerca di trovare tutti i file DLL che sono implicitamente connessi all'applicazione e di collocarli nell'area di memoria occupata da questo processo. Il sistema operativo cerca i file DLL nella seguente sequenza.

  • La directory in cui si trova il file EXE.
  • La directory corrente del processo.
  • Directory di sistema di Windows.

Se la DLL non viene trovata, l'applicazione visualizza una finestra di dialogo con un messaggio sulla sua assenza e sui percorsi che sono stati cercati. Quindi il processo viene interrotto.

Se la libreria richiesta viene trovata, viene inserita nella RAM del processo, dove rimane fino al termine. L'applicazione può ora accedere alle funzioni contenute nella DLL.

Caricamento e scaricamento dinamico della DLL

Invece di avere un collegamento dinamico di Windows alla DLL quando l'applicazione viene caricata per la prima volta nella RAM, è possibile collegare il programma al modulo della libreria in fase di esecuzione (in questo modo, non è necessario utilizzare la libreria di importazione durante il processo di creazione dell'applicazione). In particolare, è possibile determinare quale DLL è disponibile per l'utente o consentire all'utente di scegliere quale DLL caricare. Pertanto, è possibile utilizzare DLL diverse che implementano le stesse funzioni ed eseguono azioni diverse. Ad esempio, un'applicazione pensata per trasferire dati in autonomia può decidere in fase di runtime se caricare la DLL per TCP/IP o un altro protocollo.

Caricamento di una DLL regolare

La prima cosa da fare quando si carica dinamicamente una DLL è posizionare il modulo della libreria nella memoria del processo. Questa operazione viene eseguita utilizzando la funzione :: LoadLibrary che ha un singolo argomento: il nome del modulo da caricare. Il frammento corrispondente del programma dovrebbe essere simile a questo:

HISTANCE hMyDll; :: if ((hMyDll = :: LoadLibrary ("MyDLL")) == NULL) (/ * Impossibile caricare la DLL * /) else (/ * l'applicazione può utilizzare le funzioni DLL tramite hMyDll * /)

Windows considera dll come l'estensione di file standard per una libreria, a meno che non si specifichi un'estensione diversa. Se il percorso è specificato anche nel nome del file, verrà utilizzato solo per la ricerca del file. In caso contrario, Windows cercherà il file allo stesso modo delle DLL connesse implicitamente, partendo dalla directory da cui viene caricato il file exe e proseguendo secondo il valore PATH.

Quando Windows trova il file, il suo percorso completo verrà confrontato con il percorso delle DLL già caricate da questo processo. Se viene trovata un'identità, invece di caricare una copia dell'applicazione, viene restituito un handle alla libreria già collegata.

Se il file viene trovato e la libreria è stata caricata correttamente, la funzione :: LoadLibrary restituisce il suo handle, che viene utilizzato per accedere alle funzioni di libreria.

Prima di utilizzare le funzioni della libreria, è necessario ottenere il loro indirizzo. Per fare ciò, usa prima la direttiva typedef per definire il tipo di un puntatore a funzione e definire una variabile di questo nuovo tipo, ad esempio:

// il tipo PFN_MyFunction dichiarerà un puntatore a una funzione // accettando un puntatore a un buffer di caratteri e producendo un valore int typedef int (WINAPI * PFN_MyFunction) (char *); :: PFN_MyFunction pfnMyFunction;

Quindi è necessario ottenere il descrittore di libreria, con il quale è possibile determinare gli indirizzi delle funzioni, ad esempio l'indirizzo di una funzione denominata MyFunction:

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

L'indirizzo della funzione viene determinato utilizzando la funzione :: GetProcAddress, dovrebbe essere passato il nome della libreria e il nome della funzione. Quest'ultimo deve essere trasferito nella forma in cui viene esportato dalla DLL.

Puoi anche fare riferimento alla funzione tramite l'ordinale con cui viene esportata (in questo caso, per creare la libreria deve essere utilizzato un file def, di questo verrà discusso in seguito):

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

Dopo aver terminato il lavoro con la libreria di collegamento dinamico, può essere scaricata dalla memoria del processo utilizzando la funzione :: FreeLibrary:

:: FreeLibrary (hMyDll);

Caricamento delle estensioni DLL MFC

Quando si caricano le estensioni MFC per le DLL (discusse in maggior dettaglio di seguito) invece delle funzioni Carica libreria e FreeLibrary vengono utilizzate le funzioni AfxLoadLibrary e AfxFreeLibrary... Questi ultimi sono quasi identici alle funzioni dell'API Win32. Inoltre solo garantiscono che le strutture MFC inizializzate dall'estensione DLL non vengano manomesse da altri thread.

Risorse DLL

Il caricamento dinamico si applica anche alle risorse DLL utilizzate da MFC per caricare le risorse dell'applicazione standard. Per fare ciò, devi prima chiamare la funzione Carica libreria e metti la DLL in memoria. Quindi utilizzando la funzione AfxSetResourceHandleè necessario preparare la finestra del programma per ricevere risorse dalla libreria appena caricata. In caso contrario, le risorse verranno caricate dai file collegati al file eseguibile del processo. Questo approccio è utile quando è necessario utilizzare diversi set di risorse, ad esempio per lingue diverse.

Commento. Utilizzo della funzione Carica libreria puoi anche caricare file eseguibili in memoria (non eseguirli per l'esecuzione!). Il descrittore eseguibile può quindi essere utilizzato quando si chiamano funzioni TrovaRisorsa e CaricaRisorsa per trovare e caricare le risorse dell'applicazione. Scarica moduli dalla memoria anche utilizzando la funzione FreeLibrary.

Un esempio di una DLL regolare e metodi di caricamento

Ecco il codice sorgente per una libreria di collegamento dinamico chiamata MyDLL che contiene una funzione, MyFunction, che visualizza semplicemente un messaggio.

Innanzitutto, viene definita una macrocostante nel file di intestazione ESPORTARE... Usando questo parola chiave definendo una funzione, la DLL consente di dire al linker che la funzione è disponibile per l'utilizzo da parte di altri programmi, facendo sì che venga aggiunta alla libreria di importazione. Inoltre, tale funzione, proprio come una procedura finestra, deve essere definita utilizzando la costante RICHIAMA:

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

Il file della libreria è anche leggermente diverso dai normali file C per Windows. Invece di una funzione WinMain c'è una funzione DllMain... Questa funzione viene utilizzata per eseguire l'inizializzazione, che verrà discussa in seguito. Affinché la libreria rimanga in memoria dopo averla caricata e per poter chiamare le sue funzioni, il suo valore restituito deve essere 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, "Funzione da DLL", MB_OK); ritorno 1;)

Dopo aver tradotto e collegato questi file, vengono visualizzati due file: MyDLL.dll (la stessa libreria di collegamento dinamico) e MyDLL.lib (la sua libreria di importazione).

Un esempio di una connessione DLL implicita da parte di un'applicazione

Diamo ora il codice sorgente di una semplice applicazione che utilizza la funzione MyFunction dalla libreria MyDLL.dll:

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

Questo programma sembra programmi regolari per Windows, che in realtà è. Tuttavia, va notato che il suo testo di origine, oltre a chiamare la funzione MyFunction dalla libreria DLL, include anche il file di intestazione di questa libreria, MyDLL.h. È anche necessario nella fase di creazione dell'applicazione per connettersi ad essa libreria di importazione MyDLL.lib (il processo di connessione implicita di una DLL a un modulo eseguibile).

È estremamente importante capire che il codice per la stessa MyFunction non è incluso nel file MyApp.exe. Invece, ha semplicemente un collegamento al file MyDLL.dll e un collegamento a MyFunction, che si trova in quel file. Il file MyApp.exe richiede l'esecuzione del file MyDLL.dll.

Il file di intestazione MyDLL.h è incluso nel file sorgente per il programma MyApp.c allo stesso modo in cui è incluso il file windows.h. L'inclusione della libreria di importazione MyDLL.lib per il collegamento è simile all'inclusione di tutte le librerie di importazione di Windows. Quando MyApp.exe è in esecuzione, si collega a MyDLL.dll proprio come tutte le DLL standard di Windows.

Un esempio di caricamento dinamico di una DLL da parte di un'applicazione

Diamo ora il codice sorgente completo di una semplice applicazione che utilizza la funzione MyFunction dalla libreria MyDLL.dll utilizzando il caricamento dinamico della libreria:

#includere typedef int (WINAPI * PFN_MyFunction) (char *); int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) (HINSTANCE hMyDll; if ((hMyDll = LoadLibrary ("MyDLL")) == NULL) return 1; pFN_MyFunctionFunction , "MyCode" = (t); in * pfnMyFunction) ("Hello"); FreeLibrary (hMyDll); return 0;)

Creazione DLL

Ora che sai come funzionano le DLL nelle applicazioni, diamo un'occhiata a come crearle. Quando si sviluppa un'applicazione, è consigliabile posizionare le funzioni a cui accedono più processi in una DLL. Ciò consente un uso più efficiente della memoria in Windows.

Il modo più semplice per creare un nuovo progetto DLL consiste nell'usare la Creazione guidata applicazione, che esegue automaticamente molte delle operazioni. Per DLL semplici come quelle discusse in questo capitolo, è necessario selezionare il tipo di progetto Win32 Dynamic-Link Library. Al nuovo progetto verranno assegnati tutti i parametri necessari per creare la DLL. I file sorgente dovranno essere aggiunti manualmente al progetto.

Se hai intenzione di sfruttare appieno funzionalità MFC, ad esempio documenti e visualizzazioni, o intende creare un server di automazione OLE, è preferibile scegliere il tipo di progetto Creazione guidata applicazione MFC (dll). In questo caso, oltre ad assegnare parametri al progetto per il collegamento di librerie dinamiche, la procedura guidata svolgerà del lavoro aggiuntivo. Il progetto aggiungerà i riferimenti necessari alle librerie MFC e ai file sorgente contenenti la descrizione e l'implementazione nella DLL dell'oggetto classe dell'applicazione derivato da CWinApp.

A volte è conveniente creare prima un progetto Creazione guidata applicazione MFC (dll) come un'applicazione di prova e quindi creare una DLL come suo componente. Di conseguenza, la DLL verrà creata automaticamente se necessario.

DllFunzione principale

La maggior parte delle DLL sono semplicemente raccolte di funzioni virtualmente indipendenti l'una dall'altra che vengono esportate e utilizzate nelle applicazioni. Oltre alle funzioni destinate all'esportazione, ogni DLL ha una funzione DllMain... Questa funzione è progettata per inizializzare e pulire le DLL. Ha sostituito le funzioni LibMain e WEP utilizzato nelle versioni precedenti di Windows. La struttura della funzione più semplice DllMain potrebbe assomigliare a questo:

BOOL WINAPI DllMain (HANDLE hInst, DWORD dwReason, LPVOID IpReserved) (BOOL bAllWentWell = TRUE; switch (dwReason) (case DLL_PROCESS_ATTACH: // Inizializzazione del processo. Break; case DLL_THREAD_ATTACH: // Inizializza DLL_ATTACH: // Inizializza DLL_ATTACH: // Inizializza DLL_ATTACH: // DLL_ETTH: break stream Structures.break; case DLL_PROCESS_DETACH: // Cancellazione strutture del processo.break;) if (bAllWentWell) restituisce TRUE; altrimenti restituisce FALSE;)

Funzione DllMain chiamato in più casi. Il motivo della chiamata è determinato dal parametro dwReason, che può essere uno dei seguenti valori.

La prima volta che la DLL viene caricata dal processo, viene chiamata la funzione DllMain con dwReason uguale a DLL_PROCESS_ATTACH. Ogni volta che il processo crea un nuovo thread, viene chiamato DllMainO con dwReason uguale a DLL_THREAD_ATTACH (tranne il primo thread, perché in questo caso dwReason è uguale a DLL_PROCESS_ATTACH).

Alla fine del processo con la DLL, la funzione DllMain chiamato con dwReason uguale a DLL_PROCESS_DETACH. Quando un thread (diverso dal primo) viene distrutto, dwReason sarà uguale a DLL_THREAD_DETACH.

Tutte le operazioni di inizializzazione e pulizia per processi e thread necessarie alla DLL devono essere eseguite in base al valore dwReason, come mostrato nell'esempio precedente. L'inizializzazione del processo è in genere limitata all'allocazione di risorse condivise dai thread, come il caricamento di file condivisi e l'inizializzazione delle librerie. L'inizializzazione del thread viene utilizzata per configurare modalità specifiche solo per questo thread, ad esempio per inizializzare la memoria locale.

Una DLL può includere risorse che non sono di proprietà dell'applicazione chiamante. Se le funzioni della DLL funzionano con le risorse della DLL, sarebbe ovviamente utile mantenere il descrittore hInst da qualche parte in un luogo segreto e utilizzarlo durante il caricamento delle risorse dalla DLL. Il puntatore IpReserved è riservato all'interno usando windows... Pertanto, l'applicazione non dovrebbe rivendicarlo. Puoi solo verificarne il significato. Se la DLL è stata caricata dinamicamente, sarà NULL. Quando caricato staticamente, questo puntatore non sarà null.

In caso di successo, la funzione DllMain deve restituire VERO. Se si verifica un errore, viene restituito FALSE e l'ulteriore azione viene terminata.

Commento. Se non si scrive la propria funzione DllMain(), il compilatore includerà la versione standard, che restituisce semplicemente TRUE.

Esportazione di funzioni da DLL

Affinché un'applicazione possa accedere alle funzioni della DLL, ciascuna di esse deve occupare una riga nella tabella delle funzioni esportate della DLL. Esistono due modi per aggiungere una funzione a questa tabella in fase di compilazione.

__Metodo Declspec (dllexport)

È possibile esportare una funzione da una DLL inserendo il modificatore __declspec (dllexport) all'inizio della sua descrizione. Inoltre, MFC include diverse macro che definiscono __declspec (dllexport), inclusi AFX_CLASS_EXPORT, AFX_DATA_EXPORT e AFX_API_EXPORT.

Il metodo __declspec viene utilizzato meno frequentemente del secondo metodo, che funziona con i file di definizione del modulo (.def) e consente un migliore controllo sul processo di esportazione.

File di definizione del modulo

La sintassi per i file .def in Visual C ++ è abbastanza semplice, principalmente perché i parametri complessi utilizzati in versioni precedenti Windows in Win32 non si applica più. Come risulterà chiaro dal seguente semplice esempio, il file .def contiene il nome e la descrizione della libreria, oltre all'elenco delle funzioni esportate:

MyDLL.def LIBRERIA "MyDLL" DESCRIZIONE "MyDLL - sample DLL" ESPORTA MyFunction @ 1

Nella riga di esportazione di una funzione, è possibile specificare il suo numero ordinale precedendolo con il simbolo @. Questo numero verrà quindi utilizzato quando si fa riferimento a OttieniIndirizzoProc(). Il compilatore, infatti, assegna numeri sequenziali a tutti gli oggetti esportati. Tuttavia, il modo in cui lo fa è alquanto imprevedibile a meno che questi numeri non vengano assegnati esplicitamente.

È possibile utilizzare il parametro NONAME nella riga di esportazione. Impedisce al compilatore di includere il nome della funzione nella tabella di esportazione della DLL:

MyFunction @ 1 NONAME

Questo a volte può far risparmiare molto spazio nel file DLL. Le applicazioni che utilizzano la libreria di importazione per collegare in modo implicito la DLL non "noteranno" la differenza perché il collegamento implicito utilizza automaticamente i numeri di sequenza. Le applicazioni che caricano le DLL in modo dinamico dovranno passare OttieniIndirizzoProc un numero di sequenza, non un nome di funzione.

Quando si utilizza quanto sopra, il file def che descrive le funzioni esportate della libreria DLL potrebbe essere, ad esempio, diverso da questo:

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

Esportazione classi

Creare un file .def per esportare anche classi semplici da una libreria dinamica può essere piuttosto complicato. Dovrai esportare esplicitamente ogni funzione che può essere utilizzata da un'applicazione esterna.

Se guardi il file di allocazione della memoria implementato nella classe, noterai alcune funzioni molto insolite in esso. Si scopre che esistono costruttori e distruttori impliciti, funzioni dichiarate nelle macro MFC, in particolare _DECLARE_MESSAGE_MAP, nonché funzioni scritte dal programmatore.

Sebbene sia possibile esportare ciascuna di queste funzioni singolarmente, esiste un modo più semplice. Se utilizzi il modificatore di macro AFX_CLASS_EXPORT nella dichiarazione della classe, il compilatore si occuperà dell'esportazione stessa. funzioni necessarie consentendo all'applicazione di utilizzare la classe contenuta nella DLL.

Memoria DLL

A differenza delle librerie statiche, che essenzialmente diventano parte del codice dell'applicazione, le librerie a collegamento dinamico nelle versioni a 16 bit di Windows trattavano la memoria in modo leggermente diverso. In Win 16, la memoria DLL si trovava all'esterno dello spazio degli indirizzi dell'attività. Il posizionamento delle librerie dinamiche nella memoria globale ha permesso di condividerle con vari compiti.

In Win32, una DLL si trova nell'area di memoria del processo che la carica. A ogni processo viene assegnata una copia separata della memoria "globale" della DLL, che viene reinizializzata ogni volta che viene caricata da un nuovo processo. Ciò significa che una libreria a collegamento dinamico non può essere condivisa nella memoria condivisa come era in Winl6.

Eppure, eseguendo alcune complesse manipolazioni sul segmento di dati della DLL, è possibile creare un'area di memoria condivisa per tutti i processi che utilizzano questa libreria.

Supponiamo che tu abbia un array di interi che dovrebbe essere usato da tutti i processi che caricano questa DLL. Questo può essere programmato come segue:

#pragma data_seg ("". myseg") int sharedlnts; // altre variabili uso comune#pragma data_seg() #pragma comment (lib, "msvcrt" "-SECTION: .myseg, rws");

Tutte le variabili dichiarate tra le direttive #pragma data_seg() vengono poste nel segmento .myseg. La direttiva #pragma comment() non è un commento normale. Indica alla libreria C di runtime di contrassegnare la nuova partizione come lettura/scrittura/condivisione.

Compilazione completa della DLL

Se il progetto DLL viene creato utilizzando AppWizard e il file .def viene modificato di conseguenza, questo è sufficiente. Se crei file di progetto manualmente o con altri mezzi senza utilizzare la Creazione guidata applicazione, devi includere l'opzione /DLL nella riga di comando del linker. Questo crea una DLL invece di un file eseguibile autonomo.

Se è presente una riga LIBRART nel file .def, non è necessario specificare l'opzione /DLL in modo esplicito nella riga di comando del linker.

Esistono numerose modalità speciali per MFC relative all'utilizzo della libreria a collegamento dinamico MFC. La sezione successiva è dedicata a questo problema.

DLL e MFC

Non è necessario che il programmatore utilizzi MFC durante la creazione di librerie a collegamento dinamico. Tuttavia, l'uso di MFC apre una serie di possibilità molto importanti.

Esistono due livelli di utilizzo della struttura MFC in una DLL. La prima è una normale libreria di collegamento dinamico basata su MFC, DLL MFC(DLL MFC normale). Può utilizzare MFC, ma non può passare puntatori a oggetti MFC tra DLL e applicazioni. Il secondo livello è implementato in estensioni MFC dinamiche(DLL estensioni MFC). L'utilizzo di questo tipo di libreria a collegamento dinamico richiede alcune operazioni di configurazione aggiuntive, ma consente di scambiare liberamente puntatori a oggetti MFC tra la DLL e l'applicazione.

DLL MFC comuni

Le DLL MFC regolari consentono di utilizzare MFC nelle librerie a collegamento dinamico. Tuttavia, non è necessario che le applicazioni che accedono a tali librerie siano basate su MFC. Nelle DLL normali è possibile utilizzare MFC nel modo desiderato, inclusa la creazione di nuove classi nella DLL basate sulle classi MFC e l'esportazione nelle applicazioni.

Tuttavia, le DLL regolari non possono scambiare puntatori alle classi derivate da MFC con le applicazioni.

Se l'applicazione deve scambiare puntatori a oggetti di classi MFC o loro derivati ​​con DLL, è necessario utilizzare l'estensione DLL descritta nella sezione successiva.

L'architettura DLL comune è progettata per essere utilizzata da altri ambienti di programmazione come Visual Basic e PowerBuilder.

Quando si crea una normale DLL MFC utilizzando AppWizard, un nuovo progetto di tipo Creazione guidata applicazione MFC (dll)... Nella prima finestra di dialogo dell'Application Wizard, è necessario selezionare una delle modalità per le normali librerie a collegamento dinamico: "DLL regolare con MFC collegato statisticamente" o "DLL normale che utilizza DLL MFC condivisa". Il primo prevede il collegamento statico e il secondo il collegamento dinamico delle librerie MFC. È possibile modificare in seguito la modalità di connessione MFC alla DLL utilizzando la casella combinata nella scheda Generale della finestra di dialogo Impostazioni progetto.

Gestione delle informazioni sullo stato MFC

Ciascun modulo di processo MFC contiene informazioni sul proprio stato. Pertanto, le informazioni sullo stato di una DLL sono diverse dalle informazioni sullo stato dell'applicazione chiamante. Pertanto, qualsiasi funzione esportata da una libreria a cui si accede direttamente dalle applicazioni deve indicare a MFC quali informazioni di stato utilizzare. In una normale DLL MFC che utilizza DLL MFC, posizionare la seguente riga all'inizio della funzione esportata prima di chiamare qualsiasi subroutine MFC:

AFX_MANAGE_STATE (AfxGetStaticModuleState ());

Questa istruzione specifica l'uso delle informazioni di stato appropriate durante l'esecuzione della funzione che chiama la subroutine.

Estensioni MFC dinamiche

MFC ti consente di creare DLL che le tue applicazioni non considerano come una raccolta di funzioni separate, ma come estensioni di MFC. Con questo tipo di DLL è possibile creare nuove classi che derivano dalle classi MFC e utilizzarle nelle proprie applicazioni.

Per consentire lo scambio libero dei puntatori all'oggetto MFC tra l'applicazione e la DLL, è necessario creare un'estensione MFC dinamica. Le DLL di questo tipo si connettono alle DLL MFC allo stesso modo di qualsiasi applicazione che utilizza l'estensione dinamica MFC.

Il modo più semplice per creare una nuova estensione MFC dinamica è utilizzare l'Application Wizard per impostare il tipo di progetto Creazione guidata applicazione MFC (dll) e nel passaggio 1 abilitare la modalità "DLL estensione MFC". Questo assegnerà tutti gli attributi richiesti dell'estensione dinamica MFC al nuovo progetto. Inoltre, verrà creata la funzione DllMain per una DLL che esegue una serie di operazioni specifiche per inizializzare un'estensione DLL. Si prega di notare che le librerie dinamiche di questo tipo non contengono e non devono contenere oggetti derivati ​​da CWinApp.

Inizializzazione delle estensioni dinamiche

Per "adattarsi" al framework MFC, le estensioni MFC dinamiche richiedono ulteriori configurazione iniziale... Le operazioni corrispondenti sono eseguite dalla funzione DllMain... Diamo un'occhiata a un esempio di questa funzione creata da AppWizard.

Statico 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"); // Inizializzazione unica della DLL dell'estensione AfxInit,ExtensionModule ( hinstance); // Inserisci questa DLL nella catena di risorse new CDynLinkLibrary (MyExtDLL);) else if (dwReason == DLL_PROCESS_DETACH) (TRACED ("MYEXT.DLL Terminating! \ n");) return 1; // ok)

La parte più importante di questa funzione è la chiamata AfxInitExtensionModule... Questa è l'inizializzazione della libreria di collegamento dinamico, che consente di funzionare correttamente come parte di un framework MFC. Gli argomenti di questa funzione sono il descrittore DLL passato a DllMain e la struttura AFX_EXTENSION_MODULE, che contiene informazioni sulla DLL MFC.

Non è necessario inizializzare esplicitamente la struttura AFX_EXTENSION_MODULE. Tuttavia, deve essere dichiarato. Il costruttore si occuperà dell'inizializzazione. CDynLinkLibrary... In DLL devi creare una classe CDynLinkLibrary... Il suo costruttore non solo inizializzerà la struttura AFX_EXTENSION_MODULE, ma aggiungerà anche la nuova libreria all'elenco di DLL con cui può funzionare MFC.

Caricamento di estensioni MFC dinamiche

A partire dalla versione 4.0 MFC consente di caricare e scaricare dinamicamente le DLL, comprese le estensioni. Per eseguire correttamente queste operazioni sulla DLL in fase di creazione, nella sua funzione DllMain al momento della disconnessione dal processo, è necessario aggiungere una chiamata AfxTermExtensionModule... La struttura AFX_EXTENSION_MODULE già utilizzata in precedenza viene passata all'ultima funzione come parametro. Per fare questo, nel testo DllMainè necessario aggiungere le seguenti righe.

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

Inoltre, ricorda che la nuova DLL è un'estensione dinamica e deve essere caricata e scaricata dinamicamente utilizzando le funzioni AfxLoadLibrary e AfxFreeLibrary,ma no Carica libreria e FreeLibrary.

Esportazione di funzioni da estensioni dinamiche

Ora diamo un'occhiata a come le funzioni e le classi di un'estensione dinamica vengono esportate in un'applicazione. Sebbene sia possibile aggiungere manualmente tutti i nomi estesi al file DEF, è preferibile utilizzare i modificatori per le dichiarazioni di classi e funzioni esportate, come AFX_EXT_CLASS e AFX_EXT_API, ad esempio:

Classe AFX_EXT_CLASS CMyClass: public CObject (// La tua dichiarazione di classe) void AFX_EXT_API MyFunc ();

Esistono due modi per caricare una DLL: collegamento esplicito e collegamento implicito.

Il collegamento implicito aggiunge le funzioni della DLL caricata alla sezione di importazione del file chiamante. Quando si avvia un file di questo tipo, il caricatore sistema operativo analizza la sezione di importazione e collega tutte le librerie specificate. Grazie alla sua semplicità, questo metodo è molto popolare; ma la semplicità è semplicità e il collegamento implicito presenta alcuni svantaggi e limitazioni:

tutte le DLL collegate vengono sempre caricate, anche se durante l'intera sessione il programma non accede mai a nessuna di esse;

se manca almeno una delle DLL richieste (o la DLL non esporta almeno una funzione richiesta) - il caricamento del file eseguibile viene interrotto dal messaggio "Impossibile trovare la libreria di collegamento dinamico" (o qualcosa del genere) - anche se l'assenza di questa DLL non è fondamentale per eseguire il programma. Per esempio, editor di testo potrebbe funzionare abbastanza bene in una configurazione minima - senza un modulo di stampa, l'output di tabelle, grafici, formule e altri componenti secondari, ma se queste DLL vengono caricate con un collegamento implicito - che ti piaccia o no, dovrai "tirarle" insieme .

la DLL viene ricercata nel seguente ordine: nella directory contenente il file chiamante; nella directory corrente del processo; v directory di sistema% Windows% Sistema%; nella directory principale% Windows%; nelle directory specificate nella variabile PATH. È impossibile specificare un percorso di ricerca diverso (o meglio, forse, ma ciò richiederà modifiche al registro di sistema e queste modifiche influenzeranno tutti i processi in esecuzione nel sistema, il che non va bene).

Il collegamento esplicito rimuove tutti questi svantaggi, al costo di una certa complessità del codice. Il programmatore stesso dovrà occuparsi del caricamento della DLL e del collegamento delle funzioni esportate (senza dimenticare il controllo sugli errori, altrimenti un giorno il sistema si bloccherà). D'altra parte, il collegamento esplicito consente di caricare le DLL secondo necessità e offre al programmatore la capacità di gestire in modo indipendente situazioni in cui mancano le DLL. Puoi andare oltre: non impostare in modo esplicito il nome della DLL nel programma, ma scansiona questa e quella directory per la presenza di librerie dinamiche e collega tutto quello che trovi all'applicazione. Ecco come funziona il meccanismo di supporto dei plug-in nel popolare file manager FAR (e non solo in esso).

Esistono tre modi per caricare una DLL:

a) implicito;

c) differito.

Considera il caricamento implicito della DLL. Per creare un'applicazione che si basa sul caricamento implicito della DLL, è necessario disporre di:

La libreria include file con le descrizioni degli oggetti DLL utilizzati (prototipi di funzioni, dichiarazioni di classi e tipi). Questo file viene utilizzato dal compilatore.

File LIB con un elenco di identificatori importati. Questo file deve essere aggiunto alle impostazioni del progetto (all'elenco delle librerie utilizzate dal linker).

Il progetto viene compilato nel modo consueto. Utilizzando moduli oggetto e un file LIB, oltre a prendere in considerazione i riferimenti agli identificatori importati, il linker (linker, linker) genera un modulo EXE caricabile. In questo modulo, il linker inserisce anche una sezione di importazione che elenca i nomi di tutti i moduli DLL richiesti. Per ogni DLL, la sezione di importazione specifica a quali nomi di funzioni e variabili si fa riferimento nel codice del file eseguibile. Il caricatore del sistema operativo utilizzerà queste informazioni.

Cosa succede durante la fase di esecuzione dell'applicazione client? Dopo aver avviato il modulo EXE, il caricatore del sistema operativo esegue le seguenti operazioni:

1. Crea uno spazio di indirizzi virtuale per un nuovo processo e proietta su di esso un modulo eseguibile;

2. Analizza la sezione di importazione, identificando tutti i moduli DLL necessari e proiettandoli anche nello spazio degli indirizzi del processo.

Una DLL può importare funzioni e variabili da un'altra DLL. Ciò significa che potrebbe avere una propria sezione di importazione, per la quale è necessario ripetere gli stessi passaggi. Di conseguenza, l'inizializzazione del processo può richiedere molto tempo.

Dopo che il modulo EXE e tutte le DLL sono state mappate allo spazio degli indirizzi del processo, il suo thread primario è pronto per l'esecuzione e l'applicazione viene avviata.

Gli svantaggi del caricamento implicito sono il caricamento obbligatorio della libreria, anche se le sue funzioni non verranno chiamate, e, di conseguenza, il requisito obbligatorio della presenza della libreria durante il collegamento.

Il caricamento esplicito della DLL elimina gli svantaggi sopra indicati a scapito di una certa complessità del codice. Il programmatore deve occuparsi del caricamento della DLL e del collegamento delle funzioni esportate. D'altra parte, il caricamento esplicito consente di caricare la DLL secondo necessità e consente al programma di gestire situazioni che si verificano in assenza di una DLL.

In caso di caricamento esplicito, il processo di lavoro con DLL avviene in tre fasi:

1. Caricamento della DLL utilizzando una funzione Carica libreria(o la sua controparte estesa LoadLibraryEx). In caso di caricamento riuscito, la funzione restituisce un descrittore hLib del tipo HMODULE, che consente un ulteriore accesso a questa DLL.

2. Chiamate di funzione OttieniIndirizzoProc per ottenere puntatori alle funzioni richieste o ad altri oggetti. Come primo parametro, la funzione OttieniIndirizzoProc riceve un descrittore hLib, come secondo parametro - l'indirizzo della stringa con il nome della funzione importata. Il puntatore risultante viene quindi utilizzato dal client. Ad esempio, se si tratta di un puntatore a funzione, viene chiamata la funzione richiesta.

3. Quando la libreria dinamica caricata non è più necessaria, si consiglia di liberarla chiamando la funzione Libreria libera. Liberare una libreria non significa che il sistema operativo la rimuoverà immediatamente dalla memoria. Il ritardo di scaricamento viene fornito nel caso in cui la stessa DLL dopo un po' di tempo sia nuovamente necessaria per qualche processo. Ma se ci sono problemi con la RAM, Windows rimuove prima le librerie liberate dalla memoria.

Considera il caricamento lento di una DLL. Una DLL a caricamento ritardato è una DLL collegata in modo implicito che non viene caricata finché il codice non fa riferimento a un identificatore esportato da essa. Tali DLL possono essere utili nelle seguenti situazioni:

Se un'applicazione utilizza più DLL, la sua inizializzazione può richiedere molto tempo, richiesto dal caricatore per proiettare tutte le DLL nello spazio degli indirizzi del processo. Le DLL a caricamento lento risolvono questo problema distribuendo il carico della DLL durante l'esecuzione dell'applicazione.

Se l'applicazione è progettata per funzionare in diverse versioni del sistema operativo, alcune delle funzioni potrebbero essere visualizzate solo nelle versioni successive del sistema operativo e non essere utilizzate nella versione corrente. Ma se il programma non chiama una funzione specifica, la DLL non è necessaria e può continuare a funzionare in sicurezza. Quando si fa riferimento a un inesistente

la funzione può prevedere l'emissione di un opportuno avviso all'utente.

Per implementare il metodo di caricamento lento, è necessario aggiungere all'elenco delle librerie linker non solo la libreria di importazione necessaria MyLib.lib, ma anche la libreria di importazione di sistema delayimp.lib. Inoltre, è necessario aggiungere il flag / delayload: MyLib.dll nelle opzioni del linker.

Queste impostazioni obbligano il linker a eseguire le seguenti operazioni:

Implementa una funzione speciale nel modulo EXE
delayLoadHelper;

Rimuovere MyLib.dll dalla sezione di importazione del modulo eseguibile in modo che il caricatore del sistema operativo non tenti di caricare implicitamente questa libreria nella fase di caricamento dell'applicazione;

Aggiungere una nuova sezione di importazione differita al file EXE con un elenco di funzioni importate da MyLib.dll;

Converti le chiamate di funzione da DLL in chiamate
delayLoadhelper.

Nella fase di esecuzione dell'applicazione, viene implementata una chiamata a una funzione da una DLL chiamando delayLoadHelper. Questa funzione, utilizzando le informazioni dalla sezione di importazione pigra, chiama prima LoadLibrary e poi GetprocAddress. Dopo aver ricevuto l'indirizzo della funzione DLL, delayLoadHelper fa in modo che in futuro questa funzione DLL venga chiamata direttamente. Si noti che ogni funzione in una DLL viene configurata individualmente la prima volta che viene chiamata.

utilizzo DLL(libreria di collegamento dinamico) è molto diffuso nella programmazione di Windows. DLL infatti parte del codice del file eseguibile con estensione DLL... Qualsiasi programma può chiamare DLL.

Vantaggio DLLè come segue:

  • Riutilizzo del codice.
  • Condivisione del codice tra le applicazioni.
  • Codice di suddivisione.
  • Miglioramento del consumo di risorse in Windows.

Creazione DLL

Sul menu File seleziona Nuovo -> Altro. Nella finestra di dialogo sulla scheda Nuovo scegliere Procedura guidata DLL... Verrà creato automaticamente un modulo: un modello vuoto per il futuro DLL.

Sintassi DLL

Costruire DLL, Selezionare Progetto -> Costruisci Nome del progetto .

Visibilità delle funzioni e delle procedure

Funzioni e procedure possono essere locali ed esportate da DLL.

Locale

Le funzioni e le procedure locali possono essere utilizzate internamente DLL... Sono visibili solo all'interno della libreria e nessun programma può richiamarli dall'esterno.

Esportato

Le funzioni e le procedure esportate possono essere utilizzate al di fuori di DLL... Altri programmi possono chiamare tali funzioni e procedure.

Il codice sorgente sopra utilizza una funzione esportata. Il nome della funzione segue una parola riservata Esportazioni.

Caricamento DLL

Ci sono due tipi di caricamento in Delphi DLL:

Caricamento statico

Quando l'applicazione viene avviata, viene caricata automaticamente. Rimane in memoria durante l'esecuzione del programma. Molto facile da usare. Basta aggiungere una parola esterno dopo una dichiarazione di funzione o procedura.

Funzione SummaTotal (fattore: intero): intero; Registrati; esterno "Esempio.dll";

Se DLL non viene trovato, il programma continuerà a funzionare.

caricato in memoria secondo necessità. La sua implementazione è più complicata perché devi caricarlo e scaricarlo dalla memoria. La memoria viene utilizzata con maggiore parsimonia, quindi l'applicazione viene eseguita più velocemente. Il programmatore stesso deve assicurarsi che tutto funzioni correttamente. Per questo hai bisogno di:

  • Dichiarare il tipo della funzione o procedura descritta.
  • Carica la libreria in memoria.
  • Ottieni l'indirizzo di una funzione o di una procedura in memoria.
  • Chiama una funzione o una procedura.
  • Scarica la libreria dalla memoria.

Dichiarazione di tipo che descrive una funzione

tipo TSumaTotal = funzione (fattore: intero): intero;

Caricamento della libreria

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

Ottieni un puntatore a una funzione

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

Chiamata di funzione

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

Scaricare una libreria dalla memoria

FreeLibrary (dll_instance);

Chiamata dinamica DLL richiede più lavoro, ma è più facile gestire le risorse in memoria. Se devi usare DLL all'interno del programma, è preferibile il caricamento statico. Non dimenticare di usare il blocco prova... tranne e prova... finalmente per evitare errori durante l'esecuzione del programma.

Esportazione di stringhe

Creato da DLL insieme a usando Delphi, può essere utilizzato in programmi scritti in altri linguaggi di programmazione. Per questo motivo, non possiamo utilizzare alcun tipo di dati. I tipi che esistono in Delphi potrebbero non esistere in altre lingue. Si consiglia di utilizzare tipi di dati nativi di Linux o Windows. I nostri DLL può essere utilizzato da un altro programmatore che utilizza un linguaggio di programmazione diverso.

Può essere utilizzato stringhe e array dinamici v DLL scritto in Delphi, ma per questo è necessario collegare il modulo CondividiMem alla sezione usa v DLL e il programma che lo utilizzerà. Inoltre, questo annuncio deve essere il primo della sezione usa ogni file di progetto.

tipi corda, come sappiamo, C, C ++ e altri linguaggi non esistono, quindi è consigliabile utilizzare al loro posto PChar.

DLL di esempio

libreria Esempio_dll; usa SysUtils, Classi; var (Dichiara variabili) k1: intero; k2: intero; (Dichiariamo la funzione) funzione SummaTotal (fattore: intero): intero; Registrati; fattore iniziale: = fattore * k1 + fattore; fattore: = fattore * k2 + fattore; risultato: = fattore; fine; (Esportiamo la funzione per un ulteriore utilizzo da parte del programma) exports SummaTotal; (Inizializzazione delle variabili) begin k1: = 2; k2: = 5; fine.

Un esempio di chiamata di una funzione da una DLL

unità Unità1; l'interfaccia utilizza Windows, Messaggi, SysUtils, Varianti, Classi, Grafica, Controlli, Moduli, Finestre di dialogo, StdCtrls; tipo TForm1 = classe (TForm) Button1: TButton; procedura Button1Click (Mittente: TObject); privato (dichiarazioni private) pubblico (dichiarazioni pubbliche) fine; tipo TSummaTotal = funzione (fattore: intero): intero; var Modulo1: TForm1; implementazione ($ R * .dfm) procedura TForm1.Button1Click (Sender: TObject); var dll_instance: Thandle; SummaTotal: TSummaTotal; begin dll_instance: = LoadLibrary ("Example_dll.dll"); @SummaTotal: = GetProcAddress (dll_instance, "SummaTotal"); Label1.Caption: = IntToStr (SummaTotal (5)); FreeLibrary (dll_instance); fine; fine.

LA CAMPANA

C'è chi ha letto questa notizia prima di te.
Iscriviti per ricevere gli ultimi articoli.
E-mail
Nome
Cognome
Come vuoi leggere La Campana?
Niente spam