DLL - Guida rapida

Il collegamento dinamico è un meccanismo che collega le applicazioni alle librerie in fase di esecuzione. Le librerie rimangono nei propri file e non vengono copiate nei file eseguibili delle applicazioni. Le DLL si collegano a un'applicazione quando l'applicazione viene eseguita, piuttosto che quando viene creata. Le DLL possono contenere collegamenti ad altre DLL.

Molte volte, le DLL vengono inserite in file con estensioni diverse come .EXE, .DRV o .DLL.

Vantaggi di DLL

Di seguito sono riportati alcuni vantaggi di avere file DLL.

Utilizza meno risorse

I file DLL non vengono caricati nella RAM insieme al programma principale; non occupano spazio se non richiesto. Quando è necessario un file DLL, viene caricato ed eseguito. Ad esempio, finché un utente di Microsoft Word sta modificando un documento, il file DLL della stampante non è richiesto nella RAM. Se l'utente decide di stampare il documento, l'applicazione Word avvia il caricamento e l'esecuzione del file DLL della stampante.

Promuove l'architettura modulare

Una DLL aiuta a promuovere lo sviluppo di programmi modulari. Ti aiuta a sviluppare programmi di grandi dimensioni che richiedono versioni in più lingue o un programma che richiede un'architettura modulare. Un esempio di un programma modulare è un programma di contabilità con molti moduli che possono essere caricati dinamicamente in fase di esecuzione.

Semplifica la distribuzione e l'installazione

Quando una funzione all'interno di una DLL richiede un aggiornamento o una correzione, la distribuzione e l'installazione della DLL non richiedono il ricollegamento del programma alla DLL. Inoltre, se più programmi utilizzano la stessa DLL, tutti trarranno vantaggio dall'aggiornamento o dalla correzione. Questo problema può verificarsi più frequentemente quando si utilizza una DLL di terze parti che viene regolarmente aggiornata o corretta.

Le applicazioni e le DLL possono collegarsi automaticamente ad altre DLL, se il collegamento DLL è specificato nella sezione IMPORTAZIONI del file di definizione del modulo come parte della compilazione. Altrimenti, puoi caricarli in modo esplicito utilizzando la funzione LoadLibrary di Windows.

File DLL importanti

  • COMDLG32.DLL - Controlla le finestre di dialogo.

  • GDI32.DLL - Contiene numerose funzioni per disegnare grafici, visualizzare testo e gestire i caratteri.

  • KERNEL32.DLL - Contiene centinaia di funzioni per la gestione della memoria e vari processi.

  • USER32.DLL- Contiene numerose funzioni dell'interfaccia utente. Coinvolto nella creazione di finestre di programma e nelle loro interazioni tra loro.

Innanzitutto, discuteremo dei problemi e dei requisiti da considerare durante lo sviluppo delle tue DLL.

Tipi di DLL

Quando si carica una DLL in un'applicazione, due metodi di collegamento consentono di chiamare le funzioni DLL esportate. I due metodi di collegamento sono:

  • collegamento dinamico in fase di caricamento e
  • collegamento dinamico in fase di esecuzione.

Collegamento dinamico in fase di caricamento

Nel collegamento dinamico in fase di caricamento, un'applicazione effettua chiamate esplicite alle funzioni DLL esportate come le funzioni locali. Per utilizzare il collegamento dinamico in fase di caricamento, fornire un file di intestazione (.h) e un file di libreria di importazione (.lib), quando si compila e si collega l'applicazione. Quando si esegue l'operazione, il linker fornirà al sistema le informazioni necessarie per caricare la DLL e risolvere i percorsi di funzione DLL esportati al momento del caricamento.

Collegamento dinamico di runtime

Nel collegamento dinamico di runtime, un'applicazione chiama la funzione LoadLibrary o la funzione LoadLibraryEx per caricare la DLL in fase di esecuzione. Dopo che la DLL è stata caricata correttamente, si utilizza la funzione GetProcAddress per ottenere l'indirizzo della funzione DLL esportata che si desidera chiamare. Quando si utilizza il collegamento dinamico di runtime, non è necessario un file di libreria di importazione.

Il seguente elenco descrive i criteri dell'applicazione per scegliere tra collegamento dinamico in fase di caricamento e collegamento dinamico in runtime:

  • Startup performance : Se le prestazioni di avvio iniziale dell'applicazione sono importanti, è necessario utilizzare il collegamento dinamico in fase di esecuzione.

  • Ease of use: Nel collegamento dinamico in fase di caricamento, le funzioni DLL esportate sono come funzioni locali. Ti aiuta a chiamare facilmente queste funzioni.

  • Application logic: Nel collegamento dinamico runtime, un'applicazione può essere diramata per caricare diversi moduli come richiesto. Questo è importante quando si sviluppano versioni multilingue.

Il punto di ingresso della DLL

Quando si crea una DLL, è possibile specificare facoltativamente una funzione del punto di ingresso. La funzione del punto di ingresso viene chiamata quando processi o thread si collegano alla DLL o si scollegano dalla DLL. È possibile utilizzare la funzione del punto di ingresso per inizializzare o distruggere le strutture di dati come richiesto dalla DLL.

Inoltre, se l'applicazione è multithread, è possibile utilizzare l'archiviazione locale del thread (TLS) per allocare la memoria privata a ogni thread nella funzione del punto di ingresso. Il codice seguente è un esempio della funzione del punto di ingresso della DLL.

BOOL APIENTRY DllMain(
HANDLE hModule,	// Handle to DLL module DWORD ul_reason_for_call, LPVOID lpReserved )  // Reserved
{
   switch ( ul_reason_for_call )
   {
      case DLL_PROCESS_ATTACHED:
      // A process is loading the DLL.
      break;
      case DLL_THREAD_ATTACHED:
      // A process is creating a new thread.
      break;
      case DLL_THREAD_DETACH:
      // A thread exits normally.
      break;
      case DLL_PROCESS_DETACH:
      // A process unloads the DLL.
      break;
   }
   return TRUE;
}

Quando la funzione del punto di ingresso restituisce un valore FALSE, l'applicazione non verrà avviata se si utilizza il collegamento dinamico in fase di caricamento. Se si utilizza il collegamento dinamico di runtime, non verrà caricata solo la singola DLL.

La funzione del punto di ingresso dovrebbe eseguire solo semplici attività di inizializzazione e non dovrebbe chiamare altre funzioni di caricamento o terminazione della DLL. Ad esempio, nella funzione punto di ingresso, non dovresti chiamare direttamente o indirettamente il fileLoadLibrary funzione o il LoadLibraryExfunzione. Inoltre, non dovresti chiamare ilFreeLibrary funzione quando il processo sta terminando.

WARNING: Nelle applicazioni multithread, assicurarsi che l'accesso ai dati globali della DLL sia sincronizzato (thread-safe) per evitare il possibile danneggiamento dei dati. A tale scopo, utilizzare TLS per fornire dati univoci per ogni thread.

Esportazione di funzioni DLL

Per esportare le funzioni DLL, è possibile aggiungere una parola chiave di funzione alle funzioni DLL esportate o creare un file di definizione del modulo (.def) che elenca le funzioni DLL esportate.

Per utilizzare una parola chiave funzione, è necessario dichiarare ogni funzione che si desidera esportare con la seguente parola chiave:

__declspec(dllexport)

Per utilizzare le funzioni DLL esportate nell'applicazione, è necessario dichiarare ogni funzione che si desidera importare con la seguente parola chiave:

__declspec(dllimport)

In genere, useresti un file di intestazione con estensione define dichiarazione e un ifdef istruzione per separare l'istruzione export e l'istruzione import.

È inoltre possibile utilizzare un file di definizione del modulo per dichiarare le funzioni DLL esportate. Quando si utilizza un file di definizione del modulo, non è necessario aggiungere la parola chiave function alle funzioni DLL esportate. Nel file di definizione del modulo, dichiari il fileLIBRARY dichiarazione e il EXPORTSistruzione per la DLL. Il codice seguente è un esempio di un file di definizione.

// SampleDLL.def
//
LIBRARY "sampleDLL"

EXPORTS
   HelloWorld

Scrivi una DLL di esempio

In Microsoft Visual C ++ 6.0 è possibile creare una DLL selezionando il file Win32 Dynamic-Link Library tipo di progetto o il file MFC AppWizard (dll) Tipo di progetto.

Il codice seguente è un esempio di una DLL creata in Visual C ++ utilizzando il tipo di progetto Win32 Dynamic-Link Library.

// SampleDLL.cpp

#include "stdafx.h"
#define EXPORTING_DLL
#include "sampleDLL.h"

BOOL APIENTRY DllMain( HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved )
{
   return TRUE;
}

void HelloWorld()
{
   MessageBox( NULL, TEXT("Hello World"), 
   TEXT("In a DLL"), MB_OK);
}
// File: SampleDLL.h
//
#ifndef INDLL_H
#define INDLL_H

#ifdef EXPORTING_DLL
extern __declspec(dllexport) void HelloWorld() ;
#else
extern __declspec(dllimport) void HelloWorld() ;
#endif

#endif

Chiamare una DLL di esempio

Il codice seguente è un esempio di un progetto di applicazione Win32 che chiama la funzione DLL esportata nella DLL SampleDLL.

// SampleApp.cpp 

#include "stdafx.h"
#include "sampleDLL.h"

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR     lpCmdLine,  int       nCmdShow)
{ 	
   HelloWorld();
   return 0;
}

NOTE : Nel collegamento dinamico in fase di caricamento, è necessario collegare la libreria di importazione SampleDLL.lib creata quando si compila il progetto SampleDLL.

Nel collegamento dinamico di runtime, si utilizza il codice simile al codice seguente per chiamare la funzione DLL esportata SampleDLL.dll.

...
typedef VOID (*DLLPROC) (LPTSTR);
...
HINSTANCE hinstDLL;
DLLPROC HelloWorld;
BOOL fFreeDLL;

hinstDLL = LoadLibrary("sampleDLL.dll");
if (hinstDLL != NULL)
{
   HelloWorld = (DLLPROC) GetProcAddress(hinstDLL, "HelloWorld");
	
   if (HelloWorld != NULL)
   (HelloWorld);

   fFreeDLL = FreeLibrary(hinstDLL);
}
...

Quando si compila e si collega l'applicazione SampleDLL, il sistema operativo Windows cerca la DLL SampleDLL nelle seguenti posizioni in questo ordine:

  • La cartella dell'applicazione

  • La cartella corrente

  • La cartella di sistema di Windows (The GetSystemDirectory funzione restituisce il percorso della cartella di sistema di Windows).

  • La cartella Windows (The GetWindowsDirectory funzione restituisce il percorso della cartella Windows).

Per poter utilizzare una DLL, è necessario registrarla inserendo opportuni riferimenti nel Registro. A volte accade che un riferimento al Registro di sistema venga danneggiato e le funzioni della DLL non possano più essere utilizzate. La DLL può essere registrata nuovamente aprendo Start-Run e immettendo il seguente comando:

regsvr32 somefile.dll

Questo comando presuppone che somefile.dll si trovi in ​​una directory o cartella che si trova nel PERCORSO. In caso contrario, è necessario utilizzare il percorso completo della DLL. È inoltre possibile annullare la registrazione di un file DLL utilizzando l'opzione "/ u" come mostrato di seguito.

regsvr32 /u somefile.dll

Può essere utilizzato per attivare e disattivare un servizio.

Sono disponibili diversi strumenti per aiutarti a risolvere i problemi della DLL. Alcuni di loro sono discussi di seguito.

Dipendenza Walker

Lo strumento Dependency Walker (depends.exe) può eseguire la scansione ricorsiva di tutte le DLL dipendenti utilizzate da un programma. Quando apri un programma in Dependency Walker, Dependency Walker esegue i seguenti controlli:

  • Verifica la presenza di DLL mancanti.
  • Verifica la presenza di file di programma o DLL non validi.
  • Verifica che le funzioni di importazione e di esportazione corrispondano.
  • Controlla gli errori di dipendenza circolare.
  • Controlla i moduli che non sono validi perché i moduli sono per un sistema operativo diverso.

Utilizzando Dependency Walker, è possibile documentare tutte le DLL utilizzate da un programma. Può aiutare a prevenire e correggere i problemi DLL che potrebbero verificarsi in futuro. Dependency Walker si trova nella seguente directory quando si installa Microsoft Visual Studio 6.0:

drive\Program Files\Microsoft Visual Studio\Common\Tools

Risolutore universale di problemi DLL

Lo strumento DLL Universal Problem Solver (DUPS) viene utilizzato per controllare, confrontare, documentare e visualizzare le informazioni sulle DLL. Il seguente elenco descrive le utilità che compongono lo strumento DUPS:

  • Dlister.exe - Questa utility enumera tutte le DLL sul computer e registra le informazioni in un file di testo o in un file di database.

  • Dcomp.exe - Questa utilità confronta le DLL elencate in due file di testo e produce un terzo file di testo che contiene le differenze.

  • Dtxt2DB.exe - Questa utilità carica i file di testo creati utilizzando l'utilità Dlister.exe e l'utilità Dcomp.exe nel database dllHell.

  • DlgDtxt2DB.exe - Questa utilità fornisce una versione dell'interfaccia utente grafica (GUI) dell'utilità Dtxt2DB.exe.

Tenere a mente i seguenti suggerimenti durante la scrittura di una DLL:

  • Utilizzare la convenzione di chiamata appropriata (C o stdcall).

  • Essere consapevoli del corretto ordine degli argomenti passati alla funzione.

  • Non ridimensionare MAI array o concatenare stringhe utilizzando gli argomenti passati direttamente a una funzione. Ricorda, i parametri che passi sono dati di LabVIEW. La modifica delle dimensioni dell'array o della stringa può provocare un arresto anomalo sovrascrivendo altri dati archiviati nella memoria di LabVIEW. È POSSIBILE ridimensionare gli array o concatenare stringhe se si passa un LabVIEW Array Handle o LabVIEW String Handle e si utilizza il compilatore Visual C ++ o il compilatore Symantec per compilare la DLL.

  • Durante il passaggio di stringhe a una funzione, selezionare il tipo corretto di stringa da passare. String Handle in C o Pascal o LabVIEW.

  • Le stringhe Pascal sono limitate a 255 caratteri di lunghezza.

  • Le stringhe C terminano con NULL. Se la funzione DLL restituisce dati numerici in un formato di stringa binaria (ad esempio, tramite GPIB o la porta seriale), potrebbe restituire valori NULL come parte della stringa di dati. In questi casi, il passaggio di array di interi brevi (8 bit) è più affidabile.

  • Se stai lavorando con array o stringhe di dati, passa SEMPRE un buffer o un array abbastanza grande da contenere i risultati inseriti nel buffer dalla funzione a meno che non li passi come handle di LabVIEW, nel qual caso puoi ridimensionarli usando CIN funzioni in Visual C ++ o compilatore Symantec.

  • Elenca le funzioni DLL nella sezione EXPORTS del file di definizione del modulo se stai utilizzando _stdcall.

  • Elenca le funzioni DLL che altre applicazioni chiamano nella sezione EXPORTS del file di definizione del modulo o per includere la parola chiave _declspec (dllexport) nella dichiarazione della funzione.

  • Se usi un compilatore C ++, esporta le funzioni con l'istruzione extern .C. {} Nel tuo file di intestazione per evitare alterazioni del nome.

  • Se stai scrivendo la tua DLL, non dovresti ricompilare una DLL mentre la DLL è caricata in memoria da un'altra applicazione. Prima di ricompilare una DLL, assicurarsi che tutte le applicazioni che utilizzano quella particolare DLL vengano scaricate dalla memoria. Assicura che la DLL stessa non venga caricata in memoria. Potresti non riuscire a ricostruire correttamente se lo dimentichi e il tuo compilatore non ti avvisa.

  • Verifica le tue DLL con un altro programma per assicurarti che la funzione (e la DLL) si comportino correttamente. Testarlo con il debugger del tuo compilatore o un semplice programma C in cui puoi chiamare una funzione in una DLL ti aiuterà a identificare se possibili difficoltà sono inerenti alla DLL o relative a LabVIEW.

Abbiamo visto come scrivere una DLL e come creare un programma "Hello World". Quell'esempio deve averti dato un'idea del concetto di base della creazione di una DLL.

Qui, daremo una descrizione della creazione di DLL usando Delphi, Borland C ++ e ancora VC ++.

Prendiamo questi esempi uno per uno.