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.

Surfaces SDL a partir de dados na memória

Hoje falaremos sobre surfaces SDL, e como criá-las a partir de dados binarios em memória.

Como sempre, estarei usando as seguintes definições de tipos de dados:


typedef unsigned char byte;
typedef unsigned short word;
typedef unsigned int dword;

A criação de surfaces SDL a partir de arquivos de imagem é muito simples. Basta usar a função SDL_LoadBMP ou então a função IMG_Load da biblioteca SDL_Image (para poder ler imagens com outros formatos alem do BMP), como está exemplificado no codigo a seguir:


SDLSurface *surf;

// usando funçao padrao de load da biblioteca SDL 
surf = SDL_LoadBMP("imagem.bmp"); 

// usando funçao de load da biblioteca SDL_Image
surf = IMG_Load("imagem.png");

Porém, se voce usa no teu jogo um formato de arquivo binário que aglomera todos seus arquivos num só (um .dat, por exemplo), não dá para fazer load de uma surface desse jeito. Geralmente, voce extrai os dados binários dessa imagem a partir do arquivo e os coloca em uma posição de memoria. Como criar uma surface a partir desses dados?  A biblioteca SDL inclui estruturas e funções para exatamente este tipo de situação.

Primeiramente, temos a estrutura SDL_RWops. Esta estrutura vai guardar informações para que a SDL possa criar a surface. A SDL tem várias funções para criar uma SDL_RWops a partir de várias fontes, incluindo arquivos, ponteiros para estrutura FILE e, no nosso caso o que iremos usar, de dados da memória. Para tal, usamos a função SDL_RWFromMem(void*,int). Essa função recebe 2 parametros – um ponteiro para a posição de memória do vetor, e o tamanho desse vetor:


SDL_RWops *rw;
rw = SDL_RWFromMem ( (void*)dados, tamanho );

Agora que temos os dados processados, podemos criar a surface usando a função SDL_LoadBMP_RW(SDL_RWops*,int), ou entao a IMG_Load_RW(SDL_RWops*,int). Estas funções recebem 2 parametros também – o ponteiro para a estrutura SDL_RWops, e um inteiro que define se a função vai liberar automaticamente a estrutura passada (valor = 1) ou voce vai fazer isso manualmente (valor = 0).


SDL_Surface *surf;
surf = IMG_Load_RW ( rw, 1 );

Caso se passe 0 como valor, ou seja, a função não irá liberar a memoria da estrutura, usa-se a função SDL_FreeRW(SDL_RWops*) para liberar a memória.

Pronto, agora temos uma surface SDL criada com dados a partir da memória. Agora quero apenas mostrar um código extra que otimiza essa surface, melhorando a performance. Fazemos isso usando a função SDL_DisplayFormat, que transforma a surface que acabamos de criar em uma nova com o mesmo formato da tela:


SDL_Surface *surfOtimizada;
// criando surface otimizada
surfOtimizada = SDL_DisplayFormat ( surf );
// liberando memoria da surface pre-otimizada
SDL_FreeSurface ( surf );

E pronto – temos um código simples. Aqui está o código completo, dentro de uma função de uso fácil:


SDL_Surface* criaSurface ( const byte *dados, const int tamanho )
{
    SDL_RWops *rw;
    SDL_Surface *surf = 0, *surfOtimizada;

    // cria estrutura de controle
    rw = SDL_RWFromMem ( (void*)dados, tamanho );
    // cria surface pre-otimizada
    surf = IMG_Load_RW ( rw, 1 );
    // otimiza surface
    if ( surf )
    {
        surfOtimizada = SDL_DisplayFormat(surf);
        // libera memória de surface pre-otimizada
        SDL_FreeSurface(surf);
    }

    // retorna surface
    return surfOtimizada;
}

GDC 2008 – Aqui vou eu!

 Finalmente consegui efetuar o pagamento do evento, entao é oficial. Estarei participando da Game Developers Conference 2008, maior evento anual sobre desenvolvimento de jogos e tecnologias relacionadas, situado na cidade de SanFrancisco – CA. Este ano o evento acontece de 18 a 22 de Fevereiro.

Nesta conferência estarei assistindo palestras de referências do mercado, assim como ter a possibilidade de conversar  com recrutadores das mais variadas empresas de jogos, e ter acesso à exposição onde mtas empresas expoem seus novos produtos no campo de jogos. Ainda nao tive tempo para sentar e começar a montar meu calendário de palestras que pretendo assistir, mas tenho algumas já que são quase obrigatórias:

– Game Design Challenge

3 famosos game designers recebem um desafio – desenvolver uma idea de jogo baseada em uma peculariedade geralmente pouco ortodoxa. No ano de 2005 (q incluiu no painel Will Wright), eles tinham q criar um jogo baseado na poesia e vida da poeta Emilly Dickinson. No ano de 2007, eles tinham que criar um jogo usando como “controle” quadrados de pano, agulha e linha.  É interessante ver as soluçoes criativas que estes grandes mestres do design criam, e mais importante como eles chegam nessas ideas, e entender como funciona a concepção original de ideas de jogos.

– Sound Design Challenge

Assim como no GameDesignChallenge, 3 grandes compositores e artistas da área de som de jogos são convidados a criar soluçoes criativas para um desafio. Embora eu nao tenha ainda assistido nenhuma versao deste challenge – se ele for do nivel do primeiro, será extremamente interessante. Este ano, o  desafio é criar uma demo de 30segundos de uma música-tema de um jogo de terror – usando como base brinquedos de criança. Estou mto curioso para escutar os resultados!

– Chills and Thrills: Undefined Behavior in C++

Para simplificar o padrao da linguagem e dar maior flexibilidade a implementadores de compiladores, o C++ deixa algumas areas da linguagem indefinidas. Sendo acessar uma variavel antes de ela ser inicializada, ou mesmo ir alem dos limites de um vetor, comportamentos indefinidos podem levar voce a grandes problemas. Esta palestra vai demonstrar porquê existe isto, o que isso significa para o seu jogo, e como (e quando) evita-los.

Dynamic Performance Profiling of C++ Code

Esta palestra descreve um método de fazer profiling de codigo C++, sem a necessidade de espalhar  códigos de timer pelo programa inteiro. Segundo a informação da palestra, eles usam técnicas de código auto-mutável que permite instrumentação de funções dinamicamente, sem necessidade de parar o jogo. Me pareceu mto interessante mesmo, estou bastante curioso para entender como funciona.

São tantas palestras, geralmente fico com duvida entre várias que infelizmente acontecem simultaneamente. Mas estou muito empolgado e não vejo a hora de embarcar. Se algum de voces está tambem indo na GDC, deixa um recado aí!

Novo local

Caros visitantes,

Após bastante deliberação, decidi mudar permanentemente este blog para o wordpress. Desde jeito, espero melhorar a qualidade deste blog e atingir maior público.

Obrigado pela atenção, espero que gostem!

Pilha de telas

Oi povo, desculpem a demora para responder, final do ano é sempre uma correria, e no trabalho não é diferente. Estou de volta com novos posts dicas e informações.

Sei que falei que iria falar da classe BaseGame da engine, mas decidi passar pra algo mais pratico. Várias pessoas nas ultimas semanas vieram conversar comigo sobre gerenciamento de telas em engines de jogos. Aqui na Tecnodata, desenvolvemos uma solução bem elegante e de conceito simples – uma pilha.

A engine tem, internamente, uma pilha controlada por um gerenciador de telas. Esse gerenciador contem os metodos de push() e pop(), assim como metodos para acessar a tela no topo da pilha ( topScreen() ), e delegar as chamadas dos metodos Update() e Draw() à tela mais visivel. Para que isso seja viável, uma tela é um conceito encapsulado em uma classe Tela. Vamos ver um exemplo de código de como isso poderia funcionar em C++. O esqueleto da classe Tela:


class Tela
{
public:
    virtual void update(dword msTime) = 0;
    virtual void draw(dword msTime) = 0;
};

A classe Tela é bem simples, basicamente apenas declarando uma interface minima que todas as telas devem ter para que possam ser gerenciadas. (nota: o tipo de dados dword é simplesmente um valor 32bits sem sinal – ou seja – um unsigned int numa maquina 32bits).Agora vamos ver o esqueleto do gerenciador:


class GerenciadorTelas
{
private:
    stack<Tela*> _pilha;

public:
    // construtor/destrutor omitidos

    void push(Tela *tela);
    Tela* pop();
    Tela* telaAtual();

    void update(dword msTime);
    void draw(dword msTime);
};

Como eu tinha dito, o gerenciador internamente trabalha com uma pilha de telas. Para adicionar uma nova tela visivel no jogo, basta criar o objeto dessa tela (ie. Tela *t = new TelaQualquer() ) e adicionar na pilha. Desse jeito, no proximo frame da engine, a tela irá trocar automaticamente da tela anterior para a nova, pois estará chamando update e draw na nova tela.

O interessante é que, como estamos falando de pilha, a tela anterior ainda existe, entao voltar para a tela anterior é tao simples quanto dar um pop() no gerenciador. Isso garante que voce possa ter uma hierarquia de telas no seu jogo faceis de gerenciar e navegar.O exemplo mostrado aqui é extremamente simples, apenas para demonstrar a idea básica. Porém, no nosso sistema, telas incluem máquina de estados, serialização e transparência (caso uma tela tenha uma transparência inferior a 1.0, a tela abaixo dela na pilha também é visivel e, portanto, também recebe chamadas a update() e draw() ).

O gerenciador de telas não apenas gerencia a pilha, como também tem opções específicas para transição entre-telas (não apenas troca), acesso a telas por índice e por tipo (não apenas ao topo da pilha), e tem todos seus métodos como virtuais, possibilitando sua extensão fácil caso necessário.
Leitura extra

Um artigo bem interessante sobre organização de telas em classes pode ser lido neste post do blog Ponto V. Nao deixem de visitar.

Biblioteca Tecnodata3D.Utility – Classe Global

Opa! gente bacana,

Aqui estou entao mais uma vez para explicar a nossa engine, e começaremos pela assembly básica – Utility.

Como expliquei antes, a Utility contem classes que representam a base da engine, assim como várias classes de apoio. Vamos começar por ententer alguns conceitos

O que é um singleton?

Um singleton é uma classe da qual existe apenas uma instancia dela em toda a vida do projeto. Na nossa engine, poucas classes sao singletons, de bom exemplo a classe Global. Nesta classe ficam agregados os varios modulos da Utility necessarios parao uso daengine. Ela tem uma estrutura bem simples:

namespace Tecnodata3D.Utility
{
public class Global
{
private static Global _default;
    private BaseGame _game;
    private List _listModules;
    public AssetManager AssetManager { get(); set(); }
    public Camera Camera { get(); set(); }
    public static Global Default { get(); }
    public BaseGame Game { get(); }
    public Input Input { get(); set(); }
    public Log Log { get(); set(); }
    public ScreenManager { get(); set(); }
    public UserProfileManager UserProfileManager { get(); set(); }
    private Global ();
    public static void CreateInstance(BaseGame game);
    public void Initialize();
    public void AddModule ( AModule oModule );
    public AModule GetModule ( string sModule );
    public void ReplaceModule ( AModule oModule );
    public void RemoveModule ( AModule oModule );
    public void Update ( GameTime gameTime );
    public void Draw ( GameTime gameTime );
}

Como falei anteriormente, para usar a engine, deve-se criar uma classe de jogo que herda a classe BaseGame. A propria BaseGame se preocupa em criar a instancia de Global, entao nao existe necessidade de se preocupar com isso. Esta classe, entao, é uma interface global para uso da biblioteca Utility da engine – atravez dela temos acesso aos modulos principais, alem de possibilidade de criar e adicionar novos modulos, ou substituir os padrao por outros (por exemplo, voce pode criar uma classe CameraRTS, herdando-a da classe Camera, e utilizar do metodo ReplaceModule() para substituir a camera padrao pela customizada).Isso nos dá uma poderosa biblioteca básica para usar para a maioria dos projetos (por exemplo, Camera vem já com 4 tipos de camera diferentes), e caso exista necessidade de substituir ou extender essas funcionalidades, temos a flexibilidade de o fazer facilmente.Mas como usar a Global? É simples, em qualquer local do projeto, basta acessar a propriedade estática Default, a qual guarda a instancia “singleton” da classe Global. Entao, acessamos facilmente a Camera da biblioteca através do formato Global.Default.Camera. Embora possamos acessar qualquer módulo existente na Global atraves do metodo GetModule(), criamos algumas propriedades para facilitar a digitação para acesso a alguns modulos mais utilizados (internamente eles usam o metodo GetModule).

E como é a organização interna da Global?

Internamente, todos os módulos sao guardados numa lista, obedecendo a uma ordem definida por uma propriedade OrderID da classe abstrata AModule. Se um módulo necessita ser executado antes de outro, basta alterar sua propria OrderID antes de ser adicionado na Global. Por padrao, todos os módulos que tem uma propriedade de facil acesso já sao criados e adicionados na Global pela BaseGame.

Embora seja a BaseGame que recebe primeiramente as chamadas a Update() e Draw(), elas sao repassadas imediatamente para Global, que trata de iterar por todos os modulos em ordem correta, repassando assim as chamadas a cada um dos módulos. Este sistema é extremamente eficiente e simples de trabalhar – e remove a necessidade de chamar individualmente Update() e Draw() para os modulos necessarios – tudo é cuidado automaticamente pela engine.

E é isso aí gente – como tinha dito, a Global é uma classe bem simples, porém facilita muito a vida na hora de acessar dados globais do projeto. Próximo post estarei explicando detalhadamente o funcionamento da classe BaseGame – que é o coração da parte Utility da engine.

Abraço e até mais!

Estrutura da engine Tecnodata3D

A engine tem uma estrutura simples, porém bem flexivel.

Divimos a engine em varias assemblies, sendo atualmente as seguintes:

– Tecnodata3D.Utility
Engloba metodos e classes básicas necessárias ao funcionamento da engine. Nela se encontram singletons como a Global ( disponibiliza uma interface geral de acesso a todos os subsistemas da utility ), Input, Camera, AssetManager (gerencia o conteúdo do jogo), Log, ScreenManager (gerencia as telas do jogo) e UserProfileManager. Possui classes que representam entidades, luzes, coleções (ex. Pair), perfis de usuário e configurações, exceções, efeitos e módulos. Além de tudo isso, inclui também a classe BaseGame que é necessário herdar para a criação de um aplicativo usando a engine;

– Tecnodata3D.Additional
Inclui vários componentes como contador de FPS, capturador de imagem de tela, e telas padrão de jogo (explicarei esta parte em mais detalhe num novo post);

– Tecnodata3D.ContentPipeline
Todas as classes necessárias para a criação de tipos próprios de dados, como por exemplo nossa T3DModel e Terrain;

– Tecnodata3D.Physics
Toda a lógica de mundo físico é definida aqui nesta assembly. Por hora estamos utilizando a biblioteca Newton para simulação de física, mas nossa estrutura de classes é flexivel ao ponto de nos permitir utilizar outras bibliotecas, ou mesmo criar uma lógica do zero;

– Tecnodata3D.Renderer
Aqui se encontram as classes responsáveis pelo desenho 2D e 3D na tela, incluindo um SceneManager (que utiliza de um SceneGraph internamente para controle de cena). Inclui também facilidades pra criação de interfaces gráficas 2D, e controle de cursor de mouse;

Esta estrutura nos possibilitou uma fácil visualização do escopo da engine, e uma flexibilidade enorme na hora de alterar e adicionar módulos de engine. Eu estarei explicando mais detalhadamente cada uma das assemblies em posts seguintes – então até breve.

Model do XNA = Martelo

Depois de considerar bastante as opções, decidimos por criar nossas próprias classes de modelo no XNA, em detrimento da classe Model padrão do framework. Aqui listo algumas das razões que nos levaram a tomar essa decisão:

– Suporte em tempo de compilação para geração do nosso próprio modelo de objeto físico – bounding box, sphere ou mesh;
– Nosso próprio formato de Vertices – isto é crucial para alguns dos shaders mais avançados que planejamos utilizar (por exemplo, bump mapping);
– Como criamos uma classe própria de shader, o modelo pode vir já do content pipeline com um efeito apropriado, e não o padrao BasicEffect;
– A classe Model padrão não inclui informações de animação, e utilizar a propriedade Tag para armazenamento desses dados não nos pareceu uma opção elegante;
– Extensibilidade – usando um modelo próprio, temos a possibilidade de extender nosso código com novas funcionalidades, sem necessidade de alterar (fora algumas exceções) a caixa preta que define nosso sistema de modelos, meshes, shaders e animações;

Por enquanto estamos em fase de planejar essas classes novas ( a engine está, por hora, baseada na Model padrão), assim como entender claramente o funcionamento do content pipeline. Em breve terei uma versão funcional que estarei explicando detalhadamente aqui neste blog.

Próximo post vou explicar um pouco sobre a estrutura da engine Tecnodata3D, e algumas das decisões que tomamos e porquê.

Tecnodata Educacional

Como primeiro post, eu queria explicar um pouco sobre o meu trabalho aqui na Tecnodata.

A Tecnodata é uma empresa especializada em educação de trânsito, onde eu trabalho no setor de TI, mais especificamente no setor de Novas Tecnologias – desenvolvimento de software interativo e jogos educativos.

Mais informações sobre a Tecnodata Educacional, acesse http://www.tecnodatacfc.com.br/

Neste blog eu irei postar informações e dicas sobre problemas e soluções encontradas no meu dia a dia de trabalho com C++, C# com XNA e Flash CS3. Estarei colocando links para outros blogs de meus colegas que trabalham com algumas tecnologias diferentes tambem (exemplo do Flávio com Flex e Java, ou do Jefferson com ASP.NET).

Espero que com este blog eu possa estar ajudando outras pessoas a superar problemas que eu tenha encontrado, alem de que possam aproveitar positivamente as dicas que planejo postar.