DLL - como escrever

Primeiro, discutiremos os problemas e os requisitos que você deve considerar ao desenvolver suas próprias DLLs.

Tipos de DLLs

Quando você carrega uma DLL em um aplicativo, dois métodos de vinculação permitem chamar as funções DLL exportadas. Os dois métodos de vinculação são -

  • vinculação dinâmica de tempo de carregamento e
  • vinculação dinâmica em tempo de execução.

Link dinâmico de tempo de carregamento

Na vinculação dinâmica de tempo de carregamento, um aplicativo faz chamadas explícitas às funções DLL exportadas, como funções locais. Para usar a vinculação dinâmica de tempo de carregamento, forneça um arquivo de cabeçalho (.h) e um arquivo de biblioteca de importação (.lib), ao compilar e vincular o aplicativo. Quando você fizer isso, o vinculador fornecerá ao sistema as informações necessárias para carregar a DLL e resolver os locais de função exportados da DLL no momento do carregamento.

Link dinâmico de tempo de execução

Na vinculação dinâmica de tempo de execução, um aplicativo chama a função LoadLibrary ou a função LoadLibraryEx para carregar a DLL em tempo de execução. Depois que a DLL é carregada com êxito, você usa a função GetProcAddress, para obter o endereço da função DLL exportada que você deseja chamar. Ao usar a vinculação dinâmica em tempo de execução, você não precisa de um arquivo de biblioteca de importação.

A lista a seguir descreve os critérios do aplicativo para escolher entre o link dinâmico em tempo de carregamento e o link dinâmico em tempo de execução -

  • Startup performance - Se o desempenho de inicialização inicial do aplicativo for importante, você deve usar o link dinâmico em tempo de execução.

  • Ease of use- Na vinculação dinâmica de tempo de carregamento, as funções DLL exportadas são como funções locais. Isso ajuda a chamar essas funções facilmente.

  • Application logic- No link dinâmico de tempo de execução, um aplicativo pode se ramificar para carregar módulos diferentes conforme necessário. Isso é importante ao desenvolver versões em vários idiomas.

O ponto de entrada DLL

Ao criar uma DLL, você pode especificar opcionalmente uma função de ponto de entrada. A função de ponto de entrada é chamada quando processos ou threads se anexam à DLL ou se desconectam da DLL. Você pode usar a função de ponto de entrada para inicializar ou destruir estruturas de dados conforme exigido pela DLL.

Além disso, se o aplicativo for multithread, você pode usar o armazenamento local de thread (TLS) para alocar memória que é particular para cada thread na função de ponto de entrada. O código a seguir é um exemplo da função de ponto de entrada 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 a função de ponto de entrada retorna um valor FALSE, o aplicativo não será iniciado se você estiver usando a vinculação dinâmica de tempo de carregamento. Se você estiver usando vinculação dinâmica em tempo de execução, apenas a DLL individual não será carregada.

A função de ponto de entrada deve realizar apenas tarefas de inicialização simples e não deve chamar nenhuma outra função de carregamento ou encerramento de DLL. Por exemplo, na função de ponto de entrada, você não deve chamar direta ou indiretamente oLoadLibrary função ou o LoadLibraryExfunção. Além disso, você não deve ligar para oFreeLibrary função quando o processo está terminando.

WARNING- Em aplicativos multithread, certifique-se de que o acesso aos dados globais DLL esteja sincronizado (thread-safe) para evitar possível corrupção de dados. Para fazer isso, use o TLS para fornecer dados exclusivos para cada thread.

Exportando funções DLL

Para exportar funções DLL, você pode adicionar uma palavra-chave de função às funções DLL exportadas ou criar um arquivo de definição de módulo (.def) que lista as funções DLL exportadas.

Para usar uma palavra-chave de função, você deve declarar cada função que deseja exportar com a seguinte palavra-chave -

__declspec(dllexport)

Para usar funções DLL exportadas no aplicativo, você deve declarar cada função que deseja importar com a seguinte palavra-chave -

__declspec(dllimport)

Normalmente, você usaria um arquivo de cabeçalho com define declaração e um ifdef declaração para separar a declaração de exportação e a declaração de importação.

Você também pode usar um arquivo de definição de módulo para declarar funções DLL exportadas. Quando você usa um arquivo de definição de módulo, não é necessário adicionar a palavra-chave de função às funções DLL exportadas. No arquivo de definição do módulo, você declara oLIBRARY declaração e o EXPORTSdeclaração para a DLL. O código a seguir é um exemplo de arquivo de definição.

// SampleDLL.def
//
LIBRARY "sampleDLL"

EXPORTS
   HelloWorld

Escreva uma DLL de amostra

No Microsoft Visual C ++ 6.0, você pode criar uma DLL selecionando o Win32 Dynamic-Link Library tipo de projeto ou o MFC AppWizard (dll) tipo de projeto.

O código a seguir é um exemplo de uma DLL que foi criada no Visual C ++ usando o tipo de projeto 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

Chamando uma DLL de amostra

O código a seguir é um exemplo de um projeto de aplicativo Win32 que chama a função DLL exportada na 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 - Na vinculação dinâmica de tempo de carregamento, você deve vincular a biblioteca de importação SampleDLL.lib que é criada quando você constrói o projeto SampleDLL.

Na vinculação dinâmica de tempo de execução, você usa um código semelhante ao código a seguir para chamar a função DLL exportada de 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 você compila e vincula o aplicativo SampleDLL, o sistema operacional Windows procura a DLL SampleDLL nos seguintes locais nesta ordem -

  • A pasta do aplicativo

  • A pasta atual

  • A pasta do sistema Windows (o GetSystemDirectory função retorna o caminho da pasta do sistema Windows).

  • A pasta do Windows (o GetWindowsDirectory função retorna o caminho da pasta do Windows).