LA CAMPANA

C'è chi legge questa notizia prima di te.
Iscriviti per ricevere articoli freschi.
E-mail
Nome
Cognome
Come vuoi leggere The Bell
Niente spam

Come probabilmente saprai, le librerie (DLL) connesse dinamicamente usano le convenzioni del linguaggio C quando dichiarano oggetti esportati, mentre C ++ utilizza un sistema leggermente diverso per generare nomi durante la compilazione, quindi non puoi semplicemente esportare funzioni - metodi della classe C ++ e quindi utilizzarli nel codice dell'applicazione client (di seguito denominato client significa un'applicazione che utilizza una DLL). Tuttavia, ciò può essere fatto 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 un'interfaccia astratta e la classe reale che implementa tutte le funzioni può essere qualsiasi cosa. La tecnologia COM (Component Object Model) di Microsoft si basa su un'idea simile (oltre a funzionalità aggiuntive, ovviamente). Questo articolo spiegherà come utilizzare l'approccio di "classe" utilizzando l'interfaccia simile a COM in una fase iniziale (in fase di compilazione) e associazione in ritardo (mentre il programma è in esecuzione).

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

Esistono due metodi per connettere una DLL a un progetto: l'associazione precedente (nella fase di compilazione del programma) e successiva (durante l'esecuzione del programma). I metodi differiscono nel modo in cui viene caricata la DLL e nel metodo di chiamata delle funzioni implementato ed esportato dalla DLL.

Rilegatura anticipata (al momento della compilazione)

Con questo metodo di associazione, il sistema operativo carica automaticamente la DLL durante l'avvio del programma. Tuttavia, è necessario che il file .lib (file di libreria) corrispondente a questa DLL sia incluso nel progetto di sviluppo. Questo file definisce tutti gli oggetti DLL esportati. Le dichiarazioni possono contenere normali funzioni o classi C. Tutto ciò di cui il client ha bisogno è usare questo file.li.lib e includere il file header della DLL - e il sistema operativo caricherà automaticamente questa DLL. Come puoi vedere, questo metodo sembra molto facile da usare, perché 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 file new.lib. Se questo è conveniente per la tua applicazione dipende da te. Una DLL può dichiarare le funzioni che desidera esportare in due modi. Il metodo standard consiste nell'utilizzare i file .def. Tale file .def è semplicemente un elenco di funzioni esportate da una DLL.

// \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // .def file LIBRERIA myfirstdll.dll DESCRIZIONE "La mia prima DLL" ESPORTA MyFunction // \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // Intestazione DLL da includere in bool codice client MyFunction (int parms); // \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // implementa la funzione nella DLL bool MyFunction (int parms) (// fai tutto il necessario ............)

Penso che non si possa dire che in questo esempio viene esportata solo una funzione MyFunction. Il secondo metodo di dichiarazione degli oggetti esportati è specifico, ma molto più potente: è possibile esportare non solo funzioni, ma anche classi e variabili. Diamo un'occhiata al frammento di codice generato durante la creazione della DLL AppWizard da VisualC ++. I commenti inclusi nell'elenco sono abbastanza sufficienti per capire come funziona tutto.

// \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // // L'intestazione della DLL che dovrebbe essere inclusa nel codice client / * Il blocco ifdef successivo è un metodo standard per la creazione di una macro che semplifica l'esportazione da una DLL. Tutti i file di questa DLL sono compilati con una chiave specifica MYFIRSTDLL_EXPORTS. Questa chiave non è definita per nessuno dei progetti che utilizzano questa DLL. Pertanto, qualsiasi progetto in cui è incluso questo file vede le funzioni MYFIRSTDLL_API come importate dalla DLL, mentre la stessa DLL vede queste funzioni esportate. * / #ifdef MYFIRSTDLL_EXPORTS #define MYFIRSTDLL_API __declspec (dllexport) #else #define MYFIRSTDLL_API __declspec (dllimport) #endif // La classe viene esportata dalla classe test2.dll MYFIRSTDLL_API CM metodi.); 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 viene compilato il codice client, questa chiave non è definita e il prefisso __declspec (dllimport) appare davanti agli oggetti, 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) come se fossero definiti e implementato localmente nel progetto. Vediamo ora un altro metodo per utilizzare una DLL, che è spesso più conveniente e potente.

Associazione tardiva (mentre il programma è in esecuzione)

Quando si utilizza l'associazione tardiva, la DLL non viene caricata automaticamente all'avvio del programma, ma direttamente nel codice in cui è necessaria. Non è necessario utilizzare alcun file .lib, quindi l'applicazione client non richiede la ricompilazione quando si modifica la DLL. Tale associazione è potente proprio perché 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 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 progetto. Ma ora tutto il codice è interdipendente, quindi se hai scritto una nuova DLL contenente il codice DirectX, dovrai ricompilare il file eseguibile. L'unica comodità sarà che non dovrai preoccuparti del caricamento (sebbene non sia noto se sia conveniente caricare entrambe le DLL, occupando memoria, ma in realtà ne è necessaria solo una). E infine, secondo me, l'idea migliore è lasciare che l'eseguibile decida quale DLL caricare all'avvio. Ad esempio, se il programma ha stabilito che il sistema non supporta l'accelerazione OpenGL, è meglio caricare la DLL con il 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 applicata una limitazione agli oggetti esportati: è possibile esportare solo le funzioni di tipo C. Classi e variabili non possono essere caricate se il programma utilizza l'associazione tardiva. Vediamo come aggirare questa limitazione usando le interfacce.

Una DLL progettata per l'associazione tardiva in genere utilizza un file .def per identificare gli oggetti che desidera esportare. Se non si desidera utilizzare il file .def, è sufficiente utilizzare il prefisso __declspec (dllexport) prima delle funzioni esportate. Entrambi i metodi fanno la stessa cosa. Il client carica la DLL passando il nome file DLL alla funzione LoadLibrary () Win32, che restituisce l'handle HINSTANCE, che viene utilizzato per lavorare con la DLL e che è necessario per scaricare la DLL dalla memoria quando non è più necessaria. 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.

// \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // .def file LIBRERIA myfirstdll.dll DESCRIZIONE "La mia prima DLL" ESPORTA MyFunction // \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d / * Implementazione della funzione in DLL * / bool MyFunction (int parms) (// do qualcosa ............) // \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // Codice cliente / * La dichiarazione di funzione è davvero necessaria solo per al fine di determinare i parametri. Le dichiarazioni di funzioni sono generalmente contenute nel file di intestazione fornito con la DLL. La parola chiave C esterna in una dichiarazione di funzione indica al compilatore di utilizzare le convenzioni di denominazione delle variabili C. * / Extern "C" bool MyFunction (int parms); typedef bool (* MYFUNCTION) (int parms); MYFUNCTION pfnMyFunc \u003d 0; // puntatore a MyFunction HINSTANCE hMyDll \u003d :: LoadLibrary ("myfirstdll.dll"); if (hMyDll! \u003d NULL) (// Determina l'indirizzo della funzione pfnMyFunc \u003d (MYFUNCTION) :: GetProcAddress (hMyDll, "MyFunction"); // In caso di esito negativo, scarica la DLL se (pfnMyFunc \u003d\u003d 0) (:: FreeLibrary (hMyDll) ; return;) // Chiama la funzione bool result \u003d pfnMyFunc (parms); // Scarica la DLL se non ne abbiamo più bisogno :: FreeLibrary (hMyDll);)

Come puoi vedere, il codice è piuttosto semplice. Ora vediamo come può essere implementato il lavoro con le "classi". Come accennato in precedenza, se si utilizza l'associazione tardiva, non esiste un modo diretto per importare le classi dalle DLL, quindi è necessario implementare la "funzionalità" della classe utilizzando un'interfaccia contenente tutte le funzioni pubbliche, ad eccezione del costruttore e del distruttore. L'interfaccia sarà una normale struttura C / C ++ contenente solo funzioni di membro astratto virtuale. La classe effettiva nella DLL erediterà da questa struttura e implementerà tutte le funzioni 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 usarle. Per implementare questo metodo, sono necessarie altre due funzioni, una delle quali creerà un'interfaccia e la seconda eliminerà l'interfaccia dopo aver terminato di utilizzarla. Un esempio di attuazione di questa idea è riportato di seguito.

// \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // .def file LIBRERIA myinterface.dll DESCRIZIONE "implementa l'interfaccia I_MyInterface ESPORTAZIONI GetMyInterface FreeMyInterface // \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // Il file di intestazione utilizzato nella DLL e al client // che dichiara l'interfaccia // I_MyInterface.h struct I_MyInterface (virtual bool Init (int parms) \u003d 0; virtual bool Release () \u003d 0; virtual void DoStuff () \u003d 0;); / * Dichiarazioni di Dll esportate e definizioni di tipi di puntatori a funzione per caricare e lavorare facilmente con le funzioni. Nota il prefisso "C" esterno, che indica al compilatore che vengono utilizzate le funzioni in stile C * / "C" esterno (I_MyInterface ** pInterface); typedef HRESULT (* GETINTERFACE) (I_MyInterface ** pInterface); HRESULT FreeMyInterface (I_MyInterface ** pInterface); typedef HRESULT (* FREEINTERFACE) (I_MyInterface ** pInterface);) // \u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // // Implementazione dell'interfaccia in Dll // Classe MyInterface.h CMyClass: public I_MyInterface (public: bool Init (int parms); bool Release (); void DoStuff (); CMyClass (); ~ CMyClass (); // qualsiasi altro membro della classe ............ privato: // qualsiasi membro della classe ............); // \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // Funzioni esportate che creano e distruggono l'interfaccia // Dllmain.h HRESULT GetMyInterface (I_MyInterface ** pInterface) (if (! * PInterface) (* pInterface \u003d new CMyClass; return S_OK;) restituisce E_FAIL;) HRESULT FreeMyInterface (I_MyInterface ** pInterface) (if (! * PInterface) restituisce E_FAIL; elimina * pInterface; * pInterface \u003d 0; restituisce S_OK;) // \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d \u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d // Codice client // Dichiarazioni di interfaccia e chiamate di funzione GETINTERFACE pfnInterface \u003d 0; // puntatore alla funzione GetMyInterface I_MyInterface * pInterface \u003d 0; // puntatore alla struttura MyInterface HINSTANCE hMyDll \u003d :: LoadLibrary ("myinterface.dll"); if (hMyDll! \u003d NULL) (// Determina l'indirizzo della funzione pfnInterface \u003d (GETINTERFACE) :: GetProcAddress (hMyDll, "GetMyInterface"); // Scarica la DLL se l'operazione precedente non è riuscita se (pfnInterface \u003d\u003d 0) (:: FreeLibrary (hMyDll); return;) // Chiama la funzione HRESULT hr \u003d pfnInterface (& pInterface); // Scarica se non ha successo se (FAILED (hr)) (:: FreeLibrary (hMyDll); return;) // L'interfaccia è caricata, puoi chiamare funzioni pInterface-\u003e Init (1); pInterface-\u003e DoStuff (); pInterface-\u003e Release (); // Libera l'interfaccia FREEINTERFACE pfnFree \u003d (FREEINTERFACE) :: GetProcAddress (hMyDll, "FreeMyInterface"); if (pfnFree! \u003d 0) pfnFree (& hMyDll); // Scarica DLL :: FreeLibrary (hMyDll); )

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

Dalla nascita (o poco più tardi), il sistema operativo Windows utilizzava le librerie di collegamento dinamico (Libreria di collegamento dinamico), che conteneva implementazioni delle funzioni più comunemente utilizzate. Gli eredi di Windows - NT e Windows 95, così come OS / 2 - dipendono anche dalle DLL in termini di fornitura di una parte significativa della loro funzionalità.

Considerare una serie di aspetti della creazione e dell'utilizzo di DLL:

  • come collegare staticamente DLL;
  • come caricare in modo dinamico DLL;
  • come creare DLL;
  • come creare estensioni dll mfc

Utilizzando DLL

È quasi impossibile creare un'applicazione Windows che non utilizza DLL. La DLL contiene tutte le funzioni dell'API Win32 e innumerevoli altre funzioni dei sistemi operativi Win32.

In generale, le DLL sono semplicemente raccolte di funzioni compilate in librerie. Tuttavia, a differenza dei loro fratelli statici (file .Lib), le DLL non sono direttamente collegate ai file eseguibili usando l'editor dei collegamenti. Il file eseguibile contiene solo informazioni sulla loro posizione. Al momento dell'esecuzione del programma, viene caricata l'intera libreria. Grazie a ciò, diversi processi possono condividere le stesse librerie in memoria. Questo approccio riduce la quantità di memoria necessaria per diverse applicazioni che utilizzano molte librerie condivise, oltre a controllare la dimensione dei file .exe.

Tuttavia, se la libreria viene utilizzata da una sola applicazione, è meglio renderla normale, statica. Naturalmente, se le funzioni incluse nella sua composizione verranno utilizzate in un solo programma, puoi semplicemente inserire il file corrispondente con il testo sorgente in esso.

Molto spesso, un progetto si connette staticamente o implicitamente alla DLL nella fase di compilazione. Il caricamento della DLL durante l'esecuzione del programma è controllato dal sistema operativo. Tuttavia, le DLL possono essere caricate in modo esplicito o dinamico mentre l'applicazione è in esecuzione.

Importa librerie

Con una connessione DLL statica, il nome del file .lib viene determinato tra gli altri parametri dell'editor dei collegamenti nella riga di comando o nella scheda Collegamento della finestra di dialogo Impostazioni progetto di Developer Studio. Tuttavia, il file .lib utilizzato quando si collega implicitamente una DLL, - Questa non è una normale libreria statica. Vengono chiamati tali file .lib importare librerie (importare librerie). Non contengono il codice della libreria stesso, ma solo collegamenti a tutte le funzioni esportate dal file DLL, in cui è archiviato tutto. Di conseguenza, le librerie di importazione tendono ad essere più piccole dei file DLL. Torneremo ai metodi della loro creazione in seguito. Vediamo ora altri problemi relativi alla connessione implicita delle librerie dinamiche.

Negoziazione dell'interfaccia

Quando si utilizzano le proprie librerie o librerie di sviluppatori indipendenti, è necessario prestare attenzione all'abbinamento della chiamata di funzione con il suo prototipo.

Se il mondo fosse perfetto, i programmatori non dovrebbero preoccuparsi di coordinare le interfacce delle funzioni quando si collegano le librerie: sarebbero tutti uguali. Tuttavia, il mondo è lungi dall'essere perfetto e molti programmi di grandi dimensioni sono scritti usando varie librerie senza C ++.

Per impostazione predefinita, in Visual C ++, le interfacce delle funzioni sono coerenti in base alle regole C ++. Ciò significa che i parametri vengono inseriti nello stack da destra a sinistra, il programma chiamante è responsabile della loro rimozione dallo stack quando si esce dalla funzione e ne espande il nome. La modifica del nome consente all'editor dei collegamenti di distinguere tra funzioni sovraccaricate, ad es. funziona con lo stesso nome ma elenchi di argomenti diversi. Tuttavia, la vecchia libreria C non ha nomi estesi.

Sebbene tutte le altre regole per chiamare una funzione in C siano identiche alle regole per chiamare una funzione in C ++, nelle librerie C, i nomi delle funzioni non vengono espansi. Un carattere di sottolineatura (_) viene aggiunto solo a questi davanti.

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

Extern "C" int MyOldCFunction (int myParam);

Le dichiarazioni delle funzioni della libreria vengono generalmente inserite nel file di intestazione di questa libreria, sebbene le intestazioni della maggior parte delle librerie C non siano progettate per l'uso nei progetti C ++. In questo caso, è necessario creare una copia del file di intestazione e includere il modificatore "C" esterno per dichiarare tutte le funzioni di libreria utilizzate. Il modificatore "C" esterno può anche essere applicato all'intero blocco a cui è collegato il vecchio file di intestazione C mediante la direttiva #tinclude. Pertanto, invece di modificare ciascuna funzione singolarmente, è possibile eseguire solo tre righe:

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

I programmi per le versioni precedenti di Windows utilizzavano anche convenzioni di chiamata delle funzioni. pASCAL per funzioni API di Windows. I programmi più recenti dovrebbero usare il modificatore winapi, convertito in _stdcall. Sebbene questa non sia un'interfaccia di funzione C o C ++ standard, è utilizzata per accedere alle funzioni dell'API di Windows. Tuttavia, di solito tutto ciò è già preso in considerazione nelle intestazioni standard di Windows.

Scarica DLL implicitamente allegata

All'avvio, l'applicazione tenta di trovare tutti i file DLL che sono implicitamente connessi all'applicazione e di posizionarli nell'area della RAM occupata da questo processo. La ricerca dei file DLL da parte del sistema operativo viene eseguita nella seguente sequenza.

  • La directory in cui si trova il file .exe.
  • La directory di processo corrente.
  • 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 cercati. Quindi il processo si interrompe.

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

Caricamento e scaricamento dinamico di DLL

Invece di collegare dinamicamente Windows alla DLL quando l'applicazione viene caricata per la prima volta nella RAM, è possibile associare il programma al modulo libreria durante l'esecuzione del programma (in questo modo, non è necessario utilizzare la libreria di importazione durante il processo di creazione dell'applicazione). In particolare, è possibile determinare quale delle DLL è disponibile per l'utente o consentire all'utente di scegliere quale delle librerie verrà caricata. Pertanto, è possibile utilizzare DLL diverse che implementano le stesse funzioni che eseguono azioni diverse. Ad esempio, un'applicazione progettata per il trasferimento di dati indipendente può, durante l'esecuzione, decidere se caricare DLL per il protocollo TCP / IP o per un altro protocollo.

Scarica DLL normale

La prima cosa da fare quando si carica dinamicamente la DLL è mettere il modulo della libreria nella memoria di processo. Questa operazione viene eseguita utilizzando la funzione :: LoadLibraryavere un singolo argomento è il nome del modulo da caricare. Il pezzo corrispondente del programma dovrebbe apparire così:

HINSTANCE hMyDll; :: if ((hMyDll \u003d :: LoadLibrary ("MyDLL")) \u003d\u003d NULL) (/ * DLL non è riuscita a caricare * /) else (/ * l'applicazione ha il diritto di utilizzare le funzioni DLL tramite hMyDll * /)

L'estensione di file predefinita per un file di libreria di Windows è.dll, a meno che non si specifichi un'estensione diversa. Se il nome del percorso è specificato anche nel nome del file, verrà utilizzato solo per cercare il file. Altrimenti, Windows cercherà il file come nel caso di DLL implicitamente connesse, a partire dalla directory da cui viene caricato il file exe e continuando in base al valore PATH.

Quando Windows rileva un file, il suo percorso completo verrà confrontato con il percorso delle DLL già caricate da questo processo. Se viene rilevata un'identità, anziché caricare una copia dell'applicazione, viene restituito l'handle della libreria già connessa.

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

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

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

Quindi dovresti ottenere un descrittore di libreria, con il quale puoi determinare gli indirizzi delle funzioni, ad esempio l'indirizzo di una funzione chiamata MyFunction:

HMyDll \u003d :: LoadLibrary ("MyDLL"); pfnMyFunction \u003d (PFN_MyFunction) :: GetProcAddress (hMyDll, "MyFunction"); :: int iCode \u003d (* pfnMyFunction) ("Hello");

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

È inoltre possibile fare riferimento alla funzione tramite il numero seriale con cui viene esportato (inoltre, per creare la libreria è necessario utilizzare un file def, che verrà discusso più avanti):

PfnMyFunction \u003d (PFN_MyFunction) :: GetProcAddress (hMyDll, MAKEINTRESOURCE (1));

Dopo aver finito di lavorare con la libreria di collegamento dinamico, è possibile scaricarlo dalla memoria del processo utilizzando la funzione :: FreeLibrary:

:: FreeLibrary (hMyDll);

Scarica le estensioni della libreria dinamica MFC

Quando si caricano le estensioni MFC per DLL (descritte in maggior dettaglio di seguito) anziché le funzioni LoadLibrarye FreeLibraryle funzioni sono utilizzate AfxLoadLibrary e AfxFreeLibrary. Questi ultimi sono quasi identici alle funzioni dell'API Win32. Garantiscono solo che le strutture MFC inizializzate dall'estensione .dll non sono danneggiate da altri thread.

Risorse DLL

Il caricamento dinamico si applica anche alle risorse DLL utilizzate da MFC per caricare risorse standard dell'applicazione. Per fare ciò, devi prima chiamare la funzione LoadLibrarye metti la dll in memoria. Quindi utilizzando la funzione AfxSetResourceHandle È necessario preparare la finestra del programma per la ricezione di risorse dalla libreria appena caricata. Altrimenti, le risorse verranno caricate dai file collegati all'eseguibile del processo. Questo approccio è utile se è necessario utilizzare diversi insiemi di risorse, ad esempio per lingue diverse.

Commento. Utilizzando la funzione LoadLibrary Puoi anche caricare file eseguibili in memoria (non eseguirli!). Un descrittore di modulo eseguibile può quindi essere utilizzato per accedere alle funzioni. FindResourcee LoadResource per cercare e scaricare risorse dell'applicazione. Scaricare i moduli dalla memoria utilizzando la funzione FreeLibrary.

Un esempio di una normale DLL e metodi di caricamento

Ecco il codice sorgente per una libreria collegata dinamicamente chiamata MyDLL che contiene una funzione, MyFunction, che visualizza semplicemente un messaggio.

Innanzitutto, la costante macro è definita nel file di intestazione. ESPORTARE. L'uso di questa parola chiave quando si definisce una funzione di una libreria connessa in modo dinamico consente di dire al linker che questa funzione è disponibile per l'uso da parte di altri programmi, a seguito della quale la inserisce nella libreria di importazione. Inoltre, tale funzione, proprio come una procedura di finestra, deve essere determinata usando una costante Richiama:

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

Anche il file della libreria è leggermente diverso dai normali file C per Windows. In esso, invece della funzione WinMain c'è una funzione DllMain. Questa funzione viene utilizzata per eseguire l'inizializzazione, che verrà discussa in seguito. Affinché la libreria rimanga dopo il suo caricamento in memoria e richiami le sue funzioni, è necessario che il suo valore di ritorno sia 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 la traduzione e il collegamento di questi file, vengono visualizzati due file: MyDLL.dll (la stessa libreria di collegamento dinamico) e MyDLL.lib (la sua libreria di importazione).

Esempio di connessione DLL implicita per applicazione

Ecco il codice sorgente per 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 \u003d MyFunction ("Hello"); return 0;)

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

È estremamente importante comprendere che il codice della funzione MyFunction non è incluso nel file MyApp.exe. Invece, esiste semplicemente un collegamento al file MyDLL.dll e un collegamento alla funzione MyFunction che si trova in questo file. Il file MyApp.exe richiede l'avvio del file MyDLL.dll.

Il file di intestazione MyDLL.h è incluso nel file di origine di MyApp.c nello stesso modo in cui è incluso il file windows.h. L'abilitazione della libreria di importazione MyDLL.lib per la compilazione è simile all'inserimento di tutte le librerie di importazione di Windows. Quando il programma MyApp.exe è in esecuzione, si connette alla libreria MyDLL.dll allo stesso modo di tutte le librerie di collegamento dinamico Windows standard.

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

Ora diamo il codice sorgente completo di una semplice applicazione che utilizza la funzione MyFunction dalla libreria MyDLL.dll usando 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 \u003d LoadLibrary ("MyDLL")) \u003d\u003d NULL) funzione 1; PFN_MyffFF , "MyFunction"); int iCode \u003d (* pfnMyFunction) ("Hello"); FreeLibrary (hMyDll); return 0;)

Creazione di DLL

Ora che abbiamo acquisito familiarità con il funzionamento delle DLL nelle applicazioni, esamineremo i modi per crearle. Quando si sviluppa un'applicazione, le funzioni a cui accedono numerosi processi dovrebbero essere collocate preferibilmente in una DLL. Ciò consente un uso più efficiente della memoria in Windows.

Il modo più semplice per creare un nuovo progetto DLL è tramite AppWizard, che esegue automaticamente molte operazioni. Per le DLL semplici, come quelle descritte in questo capitolo, è necessario selezionare il tipo di progetto Libreria Dynamic-Link Win32. Al nuovo progetto verranno assegnati tutti i parametri necessari per la creazione della DLL. I file di origine dovranno essere aggiunti manualmente al progetto.

Se si prevede di utilizzare appieno la funzionalità MFC, ad esempio documenti e viste, o si intende creare un server di automazione OLE, è meglio scegliere il tipo di progetto MFC AppWizard (dll). In questo caso, oltre ad assegnare parametri al progetto per il collegamento di librerie dinamiche, la procedura guidata eseguirà alcune operazioni aggiuntive. I collegamenti necessari alle librerie MFC e ai file di origine contenenti una descrizione e un'implementazione nella DLL dell'oggetto dell'oggetto classe applicazione derivato da Cwininpp.

A volte è conveniente creare prima un progetto del tipo MFC AppWizard (dll) come applicazione di test, quindi una DLL come parte integrante di esso. Di conseguenza, la DLL verrà creata automaticamente se necessario.

Funzione Dllmain

La maggior parte delle DLL sono semplicemente raccolte di funzioni praticamente indipendenti esportate e utilizzate dalle applicazioni. Oltre alle funzioni destinate all'esportazione, ogni DLL ha una funzione DllMain. Questa funzione è progettata per inizializzare e pulire la DLL. Ha sostituito le funzioni LibMain e WEPutilizzato 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 \u003d TRUE; switch (dwReason) (case DLL_PROCESS_ATTACH: // Inizializza process.break; case DLL_THREAD_ATTACH: // Initialize_DETREACH. strutture di flusso. break; case DLL_PROCESS_DETACH: // Cancella strutture di processo. break;) if (bAllWentWell) restituisce VERO; altrimenti restituisce FALSO;)

Funzione DllMainchiamato in diversi casi. Il motivo per chiamarlo è determinato dal parametro dwReason, che può assumere uno dei seguenti valori.

Al primo caricamento della DLL, il processo chiama la funzione DllMain con dwReason uguale a DLL_PROCESS_ATTACH. Ogni volta che un processo crea un nuovo thread, DllMainO viene chiamato con dwReason uguale a DLL_THREAD_ATTACH (ad eccezione del primo thread, perché in questo caso dwReason è uguale a DLL_PROCESS_ATTACH).

Alla fine del processo con la funzione DLL DllMain chiamato con il parametro dwReason uguale a DLL_PROCESS_DETACH. Quando un flusso viene distrutto (tranne il primo), dwReason sarà uguale a DLL_THREAD_DETACH.

Tutte le operazioni di inizializzazione e pulizia per i processi e i thread necessari alla DLL sono necessarie in base al valore dwReason, come mostrato nell'esempio precedente. L'inizializzazione dei processi è generalmente limitata all'allocazione delle risorse condivise dai thread, in particolare il download di file condivisi e l'inizializzazione delle librerie. L'inizializzazione dei thread viene utilizzata per configurare le modalità specifiche di un determinato thread, ad esempio per inizializzare la memoria locale.

Le DLL possono includere risorse che non appartengono all'applicazione che chiama questa libreria. Se le funzioni DLL funzionano con risorse DLL, sarebbe ovviamente utile salvare l'hInst da qualche parte in un luogo appartato e usarlo quando si caricano risorse da una DLL. Puntatore IpReserved riservato per interno usando windows. Pertanto, l'applicazione non dovrebbe pretendere. Puoi solo verificarne il significato. Se la DLL è stata caricata in modo dinamico, sarà NULL. Con un carico statico, questo puntatore sarà diverso da zero.

In caso di successo, la funzione DllMain dovrebbe restituire VERO. Se si verifica un errore, viene restituito FALSE e vengono terminate ulteriori azioni.

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

Esportazione di funzioni da una DLL

Affinché l'applicazione acceda alle funzioni della libreria dinamica, ognuna di esse deve occupare una riga nella tabella delle funzioni DLL esportate. Esistono due modi per aggiungere una funzione a questa tabella in fase di compilazione.

__ Metodo Declspec (dllexport)

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

Il metodo __declspec non viene utilizzato spesso come il secondo metodo, che funziona con i file di definizione del modulo (.def) e consente di controllare meglio il processo di esportazione.

File di definizione del modulo

La sintassi dei file con estensione .def in Visual C ++ è abbastanza semplice, principalmente perché i parametri complessi utilizzati nelle versioni precedenti di Windows non vengono più utilizzati in Win32. Come risulta dal seguente semplice esempio, il file .def contiene il nome e la descrizione della libreria, nonché un elenco di funzioni esportate:

MYDLL.DEF LIBRERIA "MyDLL" DESCRIZIONE "MyDLL - esempio della libreria DLL" EXPORTS MyFunction @ 1

Nella riga di esportazione di una funzione, è possibile specificare il suo numero seriale anteponendolo al simbolo @. Questo numero verrà quindi utilizzato quando si fa riferimento a GetProcAddress (). In effetti, il compilatore assegna i numeri di serie a tutti gli oggetti esportati. Tuttavia, il modo in cui lo fa è in parte imprevedibile se non si assegnano esplicitamente questi numeri.

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

MyFunction @ 1 NONAME

A volte questo può risparmiare molto spazio nel file DLL. Le applicazioni che utilizzano la libreria di importazione per connettere implicitamente una DLL non noteranno la differenza, poiché le connessioni implicite utilizzano automaticamente i numeri di sequenza. Le applicazioni che caricano DLL in modo dinamico dovranno passare GetProcAddressnumero di serie, non nome della funzione.

Quando si utilizza quanto sopra, il file def della descrizione delle funzioni esportate della libreria DLL può, ad esempio, essere diverso:

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

Esportazione di classe

La creazione di un file .def per esportare anche le classi semplici da una libreria dinamica può essere piuttosto difficile. Sarà necessario 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 ci sono costruttori e distruttori impliciti, funzioni dichiarate nelle macro MFC, in particolare _DECLARE_MESSAGE_MAP, nonché funzioni scritte da un programmatore.

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

Memoria Dll

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

In Win32, la DLL si trova nell'area di memoria del processo caricandola. Ogni processo viene fornito con una copia separata della memoria DLL "globale", che viene reinizializzata ogni volta che un nuovo processo lo carica. Ciò significa che la libreria dinamica non può essere condivisa, nella memoria condivisa, come in Winl6.

Tuttavia, dopo aver eseguito una serie di manipolazioni complesse sul segmento di dati DLL, è possibile creare un'area di memoria comune per tutti i processi utilizzando questa libreria.

Supponiamo di avere una matrice di numeri interi che dovrebbero essere utilizzati da tutti i processi che caricano questa DLL. Questo può essere programmato come segue:

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

Tutte le variabili dichiarate tra le direttive #pragma data_seg () sono collocate nel segmento .myseg. La direttiva #pragma comment () non è un commento normale. Indica alla libreria del sistema C in esecuzione di contrassegnare la nuova partizione come letta, scritta e condivisa.

Compilazione completa di DLL

Se il progetto di libreria dinamica è stato creato utilizzando AppWizard e il file .def viene modificato di conseguenza, questo è sufficiente. Se i file di progetto vengono creati manualmente o in altri modi senza l'aiuto di AppWizard, il parametro / DLL deve essere incluso nella riga di comando dell'editor dei collegamenti. Di conseguenza, verrà creata una DLL anziché un file eseguibile autonomo.

Se è presente una riga LIBRART nel file .def, non è necessario specificare esplicitamente il parametro / DLL nella riga di comando dell'editor dei collegamenti.

MFC ha una serie di modalità speciali riguardanti l'uso della libreria dinamica delle librerie MFC. La sezione seguente è dedicata a questo problema.

DLL e MFC

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

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

DLL MFC ordinaria

Le DLL MFC regolari consentono di utilizzare MFC nelle librerie dinamiche. Tuttavia, le applicazioni che accedono a tali librerie non devono essere costruite sulla base di MFC. Nelle normali DLL, è possibile utilizzare MFC in qualsiasi modo, inclusa la creazione di nuove classi nella DLL in base alle classi MFC e l'esportazione in applicazioni.

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

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

L'architettura delle DLL convenzionali è 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 del tipo MFC AppWizard (dll). Nella prima finestra di dialogo della procedura guidata dell'applicazione, è necessario selezionare una delle modalità per le normali librerie dinamiche: "DLL normale con MFC statisticamente collegata" o "DLL normale che utilizza DLL MFC condivisa". Il primo fornisce una connessione statica e il secondo - dinamico delle librerie MFC. Successivamente, è possibile modificare la modalità di connessione MFC a DLL utilizzando la casella combinata nella scheda Generale della finestra di dialogo Impostazioni progetto.

Gestione delle informazioni sullo stato MFC

Ogni modulo del processo MFC contiene informazioni sul suo stato. Pertanto, le informazioni sullo stato della DLL sono diverse dalle informazioni sullo stato dell'applicazione che lo ha chiamato. Pertanto, qualsiasi funzione esportata dalla libreria a cui si accede direttamente dalle applicazioni dovrebbe indicare all'MFC quali informazioni sullo stato utilizzare. In una normale DLL MFC che utilizza librerie MFC dinamiche, prima di chiamare qualsiasi routine MFC, è necessario inserire la seguente riga all'inizio della funzione esportata:

AFX_MANAGE_STATE (AfxGetStaticModuleState ());

Questo operatore determina l'uso delle informazioni di stato rilevanti durante l'esecuzione della funzione che accede a questa routine.

Estensioni MFC dinamiche

MFC consente di creare tali DLL che vengono percepite dalle applicazioni non come un insieme di funzioni separate, ma come estensioni di MFC. Utilizzando questo tipo di DLL, è possibile creare nuove classi derivate dalle classi MFC e utilizzarle nelle applicazioni.

Per consentire il libero scambio di puntatori a oggetti MFC tra l'applicazione e la DLL, è necessario creare un'estensione MFC dinamica. Questo tipo di DLL si collega alle librerie dinamiche MFC allo stesso modo di qualsiasi applicazione che utilizza l'estensione MFC dinamica.

Per creare una nuova estensione MFC dinamica, il modo più semplice è utilizzare la procedura guidata dell'applicazione per assegnare un tipo al progetto MFC AppWizard (dll) e al passaggio 1 attivare la modalità "DLL di estensione MFC". Di conseguenza, al nuovo progetto verranno assegnati tutti gli attributi necessari dell'espansione dinamica di MFC. Inoltre, verrà creata una funzione. DllMain per una DLL che esegue una serie di operazioni specifiche per inizializzare un'estensione DLL. Va notato che le librerie dinamiche di questo tipo non contengono e non devono contenere oggetti derivati Cwininpp.

Inizializzazione di estensioni dinamiche

Per "adattarsi" alla struttura MFC, le estensioni MFC dinamiche richiedono ulteriori configurazione iniziale. Le operazioni corrispondenti vengono eseguite dalla funzione DllMain. Si consideri un esempio di questa funzione creata da Creazione guidata applicazione.

Statico AFX_EXTENSION_MODULE MyExtDLL \u003d (NULL, NULL); extern "C" int APIENTRY DllMain (HINSTANCE hinstance, DWORD dwReason, LPVOID IpReserved) (if (dwReason \u003d\u003d DLL_PROCESS_ATTACH) (TRACED ("MYEXT.DLL Initializing! \\ n"); // Extension DLL inizializzazione una tantum AfxInitExtensionMDule , hinstance); // Inserire questa DLL nella catena di risorse nuovo CDynLinkLibrary (MyExtDLL);) altrimenti if (dwReason \u003d\u003d 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 dinamica, che consente di funzionare correttamente come parte della struttura MFC. Gli argomenti di questa funzione sono il descrittore della libreria DLL passato a DllMain e la struttura AFX_EXTENSION_MODULE, che contiene informazioni sulla libreria dinamica connessa all'MFC.

Non è necessario inizializzare la struttura AFX_EXTENSION_MODULE in modo esplicito. Tuttavia, deve essere annunciato. Il costruttore sarà impegnato nell'inizializzazione CDynLinkLibrary. Nella DLL è necessario creare una classe CDynLinkLibrary. Il suo costruttore non solo inizializzerà la struttura AFX_EXTENSION_MODULE, ma aggiungerà anche una nuova libreria all'elenco di DLL con cui MFC può lavorare.

Scarica estensioni MFC dinamiche

A partire dalla versione 4.0, MFC consente di caricare e scaricare in modo dinamico DLL, comprese le estensioni. Per la corretta esecuzione di queste operazioni sulla DLL creata nella sua funzione DllMain al momento della disconnessione dal processo, è necessario aggiungere una chiamata AfxTermExtensionModule. L'ultima struttura AFX_EXTENSION_MODULE utilizzata sopra viene passata come parametro all'ultima funzione. Per fare questo nel testo DllMain È necessario aggiungere le seguenti righe.

If (dwReason \u003d\u003d DLL_PROCESS_DETACH) (AfxTermExtensionModule (MyExtDLL);)

Inoltre, ricorda che la nuova DLL è un'estensione dinamica e deve essere caricata e scaricata in modo dinamico utilizzando le funzioni AfxLoadLibrary e AfxFreeLibrary,ma no LoadLibrary e FreeLibrary.

Esporta funzioni da estensioni dinamiche

Consideriamo ora come esportare funzioni e classi da un'estensione dinamica a un'applicazione. Sebbene sia possibile aggiungere manualmente tutti i nomi estesi a un file DEF, è meglio usare modificatori per dichiarare le classi e le funzioni esportate, come AFX_EXT_CLASS e AFX_EXT_API, ad esempio:

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

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

Quando si collega in modo implicito, le funzioni della DLL caricata vengono aggiunte alla sezione di importazione del file chiamante. Quando viene avviato un file di questo tipo, il caricatore del sistema operativo analizza la sezione di importazione e collega tutte le librerie specificate. Per la sua semplicità, questo metodo è molto popolare; ma semplicità: semplicità e layout implicito presentano alcuni svantaggi e limitazioni:

tutte le DLL connesse 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 è critica per eseguire il programma. Ad esempio, un editor di testo potrebbe funzionare abbastanza bene in una configurazione minima - senza un modulo di stampa, che genera tabelle, grafici, formule e altri componenti minori, ma se queste DLL sono caricate con un layout implicito - se lo vuoi o no, devi trascinarle.

la ricerca DLL si verifica nel seguente ordine: nella directory contenente il file chiamante; nella directory di processo corrente; nella directory di sistema% Windows% System%; nella directory principale% Windows%; nelle directory specificate nella variabile PATH. È impossibile specificare un altro percorso di ricerca (o meglio, è possibile, ma per questo sarà necessario apportare modifiche al registro di sistema, e queste modifiche influenzeranno tutti i processi in esecuzione nel sistema - il che non è buono).

Il layout esplicito elimina tutte queste carenze, a scapito della complessità del codice. Lo stesso programmatore dovrà occuparsi del caricamento della DLL e della connessione delle funzioni esportate (senza dimenticare il controllo degli errori, altrimenti un giorno il sistema finirà con un crash del sistema). Ma il layout esplicito consente di caricare la DLL in base alle esigenze e offre al programmatore la possibilità di gestire autonomamente situazioni con una DLL mancante. Puoi andare oltre: non specificare il nome DLL nel programma in modo esplicito, ma scansiona tale e tale directory per le librerie dinamiche e connetti tutto trovato all'applicazione. Ecco come funziona il meccanismo di supporto del 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 progettata per il caricamento implicito della DLL, è necessario disporre di:

Una libreria include file con descrizioni di oggetti usati da una DLL (prototipi di funzioni, dichiarazioni di classi e tipi). Questo file viene utilizzato dal compilatore.

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

La compilazione del progetto viene eseguita nel solito modo. Utilizzando moduli oggetto e un file LIB, oltre a tenere conto dei collegamenti agli identificativi importati, il linker (linker, editor di link) forma un modulo EXE di caricamento. In questo modulo, il linker posiziona anche la sezione di importazione, che elenca i nomi di tutti i moduli DLL necessari. Per ogni DLL, la sezione di importazione indica quali funzioni e nomi di variabili sono referenziati nel codice del file eseguibile. Queste informazioni saranno utilizzate dal bootloader del sistema operativo.

Cosa succede durante il runtime 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 di processo.

Una DLL può importare funzioni e variabili da un'altra DLL. Pertanto, potrebbe avere una propria sezione di importazione, per la quale è necessario ripetere le stesse azioni. Di conseguenza, l'inizializzazione del processo può richiedere del tempo.

Dopo che il modulo EXE e tutti i moduli DLL sono stati mappati allo spazio degli indirizzi del processo, il suo flusso primario è pronto per l'esecuzione e l'applicazione inizia a funzionare.

Gli svantaggi del caricamento implicito sono il caricamento obbligatorio della libreria, anche se non si verificherà l'accesso alle sue funzioni e, di conseguenza, il requisito obbligatorio per la presenza della libreria durante il collegamento.

Il caricamento esplicito della DLL elimina gli inconvenienti sopra menzionati a causa di alcune complicazioni del codice. Lo stesso programmatore deve occuparsi del caricamento della DLL e del collegamento delle funzioni esportate. Ma il caricamento esplicito consente di caricare le DLL in base alle esigenze e consente al programma di gestire le situazioni che si presentano in assenza di DLL.

Nel caso di un carico esplicito, il processo di utilizzo di una DLL si svolge in tre fasi:

1. Caricamento di una DLL mediante una funzione LoadLibrary(o la sua controparte estesa LoadLibraryEx).Se il caricamento ha esito positivo, la funzione restituisce un handle hLib di tipo HMODULE, che consente di accedere a questa DLL in futuro.

2. Chiamate di funzione GetProcAddressper ottenere puntatori alle funzioni richieste o ad altri oggetti. Come primo parametro, la funzione GetProcAddressriceve l'handle hLib, come secondo parametro, l'indirizzo della stringa con il nome della funzione importata. Successivamente, il puntatore risultante viene utilizzato dal client. Ad esempio, se si tratta di un puntatore a una funzione, viene chiamata la funzione desiderata.

3. Quando la libreria dinamica caricata non è più necessaria, si consiglia di liberarla chiamando la funzione FreeLibrary.Rilasciare una libreria non significa che il sistema operativo la cancellerà immediatamente dalla memoria. Il ritardo di scaricamento viene fornito nel caso in cui la stessa DLL dopo qualche tempo necessiti di un nuovo processo. Ma se hai problemi con la RAM, Windows elimina prima le librerie liberate dalla memoria.

Prendi in considerazione il caricamento differito della DLL. Una DLL con caricamento ritardato è una DLL implicitamente collegata che non viene caricata fino a quando il codice non accede a un identificatore esportato da essa. Tali DLL possono essere utili nelle seguenti situazioni:

Se un'applicazione utilizza più DLL, l'inizializzazione potrebbe richiedere molto tempo, il che richiede al caricatore di mappare tutte le DLL sullo spazio degli indirizzi del processo. Le DLL di caricamento ritardate risolvono questo problema distribuendo il caricamento delle DLL durante l'esecuzione dell'applicazione.

Se l'applicazione è progettata per funzionare con diverse versioni del sistema operativo, alcune funzioni potrebbero apparire solo nelle versioni successive del sistema operativo e non essere utilizzate nella versione corrente. Ma se il programma non chiama una funzione specifica, allora non ha bisogno di una DLL e può tranquillamente continuare a funzionare. Quando ci si riferisce a un inesistente

le funzioni possono consentire all'utente di ricevere un avviso.

Per implementare il metodo di caricamento ritardato, è necessario aggiungere non solo la libreria di importazione MyLib.lib necessaria all'elenco delle librerie di linker, ma anche la libreria di importazione del 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:

Implementare 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 in sospeso al file EXE con un elenco di funzioni importate da MyLib.dll;

Converti le chiamate di funzione dalla DLL in chiamate
delayLoadhelper.

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

utilizzando dll (libreria a collegamento dinamico) è comune nella programmazione di Windows. dll attualmente parte del codice del file eseguibile con l'estensione dll. Qualsiasi programma può chiamare dll.

Vantaggio dll è come segue:

  • Riutilizzo del codice.
  • Condivisione del codice tra applicazioni.
  • Separazione del codice
  • Miglioramento del consumo di risorse in Windows.

Creazione di DLL

Sul menu File seleziona Nuovo -\u003e Altro. Nella finestra di dialogo, scheda Nuovo Selezionare Procedura guidata DLL. Verrà creato automaticamente un modulo, un modello vuoto per il futuro dll.

Sintassi DLL

Costruire dllSelezionare Progetto -\u003e Costruisci Nome del progetto .

Visibilità di funzioni e procedure

Le funzioni e le 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ò chiamarli dall'esterno.

esportato

Le funzioni e le procedure esportate possono essere utilizzate all'esterno dll. Altri programmi possono chiamare tali funzioni e procedure.

Il codice sorgente sopra utilizza la funzione esportata. Il nome della funzione segue la parola riservata. esportazioni.

Caricamento DLL

Esistono due tipi di avvio in Delphi dll:

Carico statico

Quando si avvia l'applicazione si carica automaticamente. Rimane in memoria durante l'esecuzione del programma. Molto facile da usare Aggiungi solo la parola esterno dopo la dichiarazione di una funzione o procedura.

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

Se una dll non verrà trovato, il programma continuerà a funzionare.

caricato in memoria secondo necessità. La sua implementazione è più complicata, perché tu stesso devi caricarlo e scaricarlo dalla memoria. La memoria viene utilizzata in modo più economico, quindi l'applicazione viene eseguita più velocemente. Lo stesso programmatore deve assicurarsi che tutto funzioni correttamente. Per fare questo, è necessario:

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

Dichiarazione del tipo di funzione

tipo TSumaTotal \u003d funzione (fattore: intero): intero;

Caricamento libreria

dll_instance: \u003d LoadLibrary ("Example_dll.dll");

Otteniamo un puntatore a una funzione

@SummaTotal: \u003d GetProcAddress (dll_instance, "SummaTotal");

Chiamata di funzione

Label1.Caption: \u003d 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 ... finalmenteper evitare errori durante l'esecuzione del programma.

Esportazione di riga

Creato da dll a partire dal 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 esistenti in Delphi potrebbero non essere disponibili in altre lingue. Si consiglia di utilizzare i propri tipi di dati da Linux o Windows. Nostro dll può essere utilizzato da un altro programmatore che utilizza un linguaggio di programmazione diverso.

Può essere utilizzato stringhe e array dinamici nel dllscritto in Delphi, ma per questo è necessario collegare il modulo Sharemem alla sezione usi nel dll e il programma che lo utilizzerà. Inoltre, questo annuncio dovrebbe essere il primo nella sezione. usi ogni file di progetto.

Di tipi corda, come sappiamo, C, C ++ e altre lingue non esistono, quindi è consigliabile utilizzare invece PChar.

Esempio DLL

libreria Example_dll; usa SysUtils, Classes; var (Dichiara variabili) k1: intero; k2: intero; (Dichiara una funzione) funzione SummaTotal (fattore: intero): intero; Registrati; fattore iniziale: \u003d fattore * k1 + fattore; fattore: \u003d fattore * k2 + fattore; risultato: \u003d fattore; fine; (Esportiamo la funzione per un ulteriore utilizzo da parte del programma) esporta SummaTotal; (Inizializzazione di variabili) inizio k1: \u003d 2; k2: \u003d 5; fine.

Un esempio di una chiamata di funzione da una DLL

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

LA CAMPANA

C'è chi legge questa notizia prima di te.
Iscriviti per ricevere articoli freschi.
E-mail
Nome
Cognome
Come vuoi leggere The Bell
Niente spam