Archive for the ‘API’ Tag

Plugins com DLLs em C++

Hoje vou falar um pouco de como fazer um programa em C++ que aceita plugins.

Plugin é uma pequena adição ao programa que voce pode plugar no teu software para extender as funcionalidades dele sem necessidade de recompilação. Existem diversas técnicas avançadas para criaçao de sistema de plugins, mas aqui vou exemplificar uma bem simples, que tem um pouco de relação com o assunto anterior que foi discutido. (o de carregar imagens SDL a partir de dados em memória – que voce pode acessar aqui).

Vamos imaginar que o jogo usa um formato de arquivo binário onde tem todos os arquivos do jogo aglomerados, vamos chamar de arquivo .DAT. Esse arquivo tem q ser montado por algum software, e geralmente o pessoal de jogos cria alguma ferramenta para geração desses tipos de arquivos. Para montar cada tipo de arquivo, podemos usar um sistema de plugins para que seja facil adicionar novos tipos de arquivos suportados pelo montador. Irei descrever um jeito bacana de criar um sistema simples, porém bastante flexivel e poderoso, num post futuro.

Um plugin nada mais é que uma DLL com um conjunto de funções pré-definidas que serão chamadas pela aplicação. Uma DLL tem uma estrutura bem parecida com um programa comum, como segue o exemplo abaixo:


#include <windows.h>

BOOL APIENTRY DllMain( HMODULE hModule, DWORD dwReasonForCall, LPVOID lpReserved )
{
   switch ( dwReasonForCall )
   {
   case DLL_PROCESS_ATTACH:
   case DLL_THREAD_ATTACH:
   case DLL_THREAD_DETACH:
   case DLL_PROCESS_DETACH:
      break;
   }
   return TRUE;
}

Isso é o código mínimo para se ter uma DLL. Claro que essa DLL nao tem nenhuma função exportada e portanto é totalmente inútil. Vamos então definir uma função na DLL de plugin. Para isso, precisamos de criar um arquivo header (.h), que será usado tanto para definir a exportação da função na DLL, como permitir que ferramentas que incluem a DLL estaticamente possam acessa-la. (o sistema de plugin usa linkagem dinâmica, porém o arquivo .h deve sempre ser criado).arquivo.h


#include <windows.h>

#ifdef PLUGIN_SOMA_EXPORTS
   #define PLUGINSOMA_DLL __declspec(dllexport)
#else
   #define PLUGINSOMA_DLL __declspec(dllimport)
#endif

extern "c"
{
PLUGINSOMA_DLL int calc ( int a, int b);
}

E agora a implementação do arquivo .cpp:


#include "arquivo.h"

BOOL APIENTRY DllMain( HMODULE hModule, DWORD dwReasonForCall, LPVOID lpReserved )
{
   switch ( dwReasonForCall )
   {
   case DLL_PROCESS_ATTACH:
   case DLL_THREAD_ATTACH:
   case DLL_THREAD_DETACH:
   case DLL_PROCESS_DETACH:
      break;
   }
   return TRUE;
}

int calc ( int a, int b )
{
   return (a+b);
}

A função calc nesta DLL recebe 2 inteiros, e retorna a soma deles. Agora basta definir a MACRO PLUGIN_SOMA_EXPORTS e compilar para ter o arquivo.dll Este código tem várias coisas que podem parecer estranhas pra quem nunca fez uma DLL, entao vou explicar rapidamente. No arquivo.h , temos um conjunto de operações de pre-processador. Aqueles comandos apenas especificam que, se a macro PLUGIN_SOMA_EXPORTS está definida ( ou seja, estamos compilando a DLL ), a macro PLUGINSOMA_DLL é setada para definir as funções como exportadas. Caso contrario (estamos usando a .h para chamar estaticamente as funções da DLL), define como importações.Além disso, temos a extern “C”, que fala para o compilador não gerar nomes de funções da DLL no formato C++ e sim no formato C (C++ altera o nome da função – explicarei isso num post futuro também). Finalmente, veja que a função foi declarada usando a macro PLUGINSOMA_DLL, que já foi explicada.No arquivo.cpp, incluimos o arquivo.h, e implementamos a funçao de entrada da DLL (DllMain), e a nossa função calc. Agora que temos a DLL, como podemos proceder o uso desta dentro da aplicação de forma dinamica? Usando algumas funções da Win32 API, nomeadamente LoadLibrary(LPCTSTR) e GetProcAdress(HMODULE,LPCSTR). Aqui está um exemplo de como iriamos chamar a função calc de nossa DLL:


// define ponteiro para função da DLL
//    Se vc nao entende o que isto significa,
//    procure informações sobre ponteiros de função
typedef int (*dllCalc)(int,int);

// LPCTSTR - veja comentário no final do código
int chamaCalc ( LPCTSTR nomeDll )
{
   // define variaveis
   HINSTANCE hDll = 0;
   dllCalc fpCalc = 0;

   // carrega DLL
   HINSTANCE hDll = LoadLibrary ( "arquivo.dll" );
   // se DLL foi carregada com sucesso, pegue ponteiro para função
   if ( hDll )
   {
      // GetProcAdress devolve o endereço de memória da função pedida
      fpCalc = (dllCalc)GetProcAddress(hDll, "calc");
   }

   // chama função
   return (fpCalc)(2,2);
}

// LPCTSTR significa ponteiro para string constante,
// basicamente, eh um tipo da Win32 API q se traduz
// para 'const char*' - porém ao programar com Win32
// API, se recomenda usar os tipos definidos da API.

Código bem simples, não? a função LoadLibrary carrega a DLL e devolve um HINSTANCE, que voce usa para pegar a função que voce quer com GetProcAdress.Munidos deste conhecimento, fica fácil criar um sistema de plugins. A aplicação pode ter na sua pasta uma outra chamada ‘Plugins’. O código pode, então, listar todas as DLLs existentes nessa pasta e as usar como plugins.Num post futuro (bem breve) irei colocar uma descrição de um sistema simples de montador de arquivos estilo DAT. Irei usar as ideas aqui estabelecidas neste post como base para criação dos plugins.

Abraços, obrigado a todos pelas visitas – espero que meu blog esteja sendo util para todos voces.