Geração de Wrappers com Cheetah Templates

Em um post mais antigo mostrei como fazer um wrapper managed para
classes unamanaged manualmente. Agora vou começar a mostrar como a
geração automática pode ser feita usando as ferramentas Cheetah
Templates e gccxml.

Criando templates para Wrappers com o Cheetah

O que torna o Cheetah especialmente útil para fazer a geração de
wrappers é o fato de que um wrapper para uma classe unmanaged segue um
padrão em relação a classe unmanaged em si. O wrapper nada mais é que
uma classe especial em C++/CLI que tem um ponteiro privado para a
classe wrapped onde cada um dos métodos expostos é delegado para um
método equivalente da classe wrapped. Aqui vai um exemplo de código
para deixar mais claro:

// Classe unmanaged
class UnmanagedClass { ... };


// Wrapper managed
public ref class ManagedClass {
private:
   // O ponteiro para a classe managed
   UnmanagedClass * m_Impl;

public:
   // O construtor apenas cria uma instância da classe
   // unmanaged usando o ponteiro privado
   ManagedClass() : m_Impl( new UnmanagedClass ) {}
   
   // O destrutor apaga o ponteiro da classe
   // unmanaged
   ~ManagedClass() {
      delete m_Impl;
   }

protected:
   // Um destrutor especial destrói o ponteiro no caso
   // em que o garbage colector recolha a instância
   // do wrapper
   !ManagedClass() {
      delete m_Impl;
   }

   // Aqui viriam outros métodos
   ...
};


A partir desse exemplo simples podemos determinar que a instanciação e
destruição da classe unmanaged dentro do wrapper segue um padrão bem
simples. Para fazer de forma genérica só precisamos usar dois
placeholders, um para o nome da classe unmanaged e outro para o nome
da classe wrapper e temos um template inicial. Como a sintaxe de
placeholders do Cheetah é apenas um nome qualquer com o símbolo $
antes, podemos manter os mesmos nomes ManagedClass e
UnmanagedClass usados no exemplo de código acima, usando {} para
agrupar o nome em casos que o Cheetah interpretaria de outra forma.

public ref class $ManagedClass {
private:
   // O ponteiro para a classe managed
   $UnmanagedClass * m_Impl;

public:
   // O construtor apenas cria uma instância da classe
   // unmanaged usando o ponteiro privado
   ${ManagedClass}() : m_Impl( new $UnmanagedClass ) {}
   
   // O destrutor apaga o ponteiro da classe
   // unmanaged
   ~${ManagedClass}() {
      delete m_Impl;
   }

protected:
   // Um destrutor especial destrói o ponteiro no caso
   // em que o garbage colector recolha a instância
   // do wrapper
   !${ManagedClass}() {
      delete m_Impl;
   }

   // Aqui viriam outros métodos
   ...
};

Wrappers para os métodos

O próximo passo seria gerar wrappers para cada um dos métodos. No
geral isso não é difícil e também segue um padrão. No melhor caso é
algo simples como isso:

class UnmanagedClass {
...
public:
   void methodA();
   int methodB(int a, int b);
}

class ref ManagedClass {
...
public:
   void methodA() {
      m_Impl->methodA();
   }
   int methodB(int a, int b) {
       return m_Impl->methodB(a, b);
   }
}


Ou seja, nada mais é do que uma simples delegação de métodos. O caso
complica um pouco mais quando os parâmetros passados ou os valores
retornados não são de tipos primitivos. Daí entra em um caso onde é
necessário fazer algumas conversões. Por exemplo, um parâmetro do tipo
char* precisaria ser convertido para uma classe String managed (já
incluída na biblioteca padrão do C++/CLI) para que ele possa ser usado
nos programas managed. O mesmo ocorre para dados do tipo std::string.

Como o número de métodos em cada classe varia, não é possível fazer um
template apenas com placeholders. A idéia é usar os comandos de
iteração disponíveis no Cheetah para gerar templates específicos para
cada método de acordo com uma lista que especifica o formato de cada
um deles. A lista em si será passada pelo programa em Python, mas um
exemplo de uma possível implementação desse template seria assim:

#for method in methods
$method.returnType ${method.name}(method.typedArgList) {
   #if method.returnType != "void"
       return 
   #end if
   m_Impl->${method.name}(method.argList);
}
#end for


No caso em que os parâmetros ou valores de retorno são de classes
definidas pelo usuário, o programa de geração de wrappers precisa
"conhecer" a classe. Como o gccxml pode fazer uma compilação de todo o
projeto, ele vai eventualmente "conhecer" todas as classes envolvidas
e saber como construir objetos daquele tipo. Evidentemente podem
surgir casos em que não será possível decidir como criar um objeto de
uma determinada classe, mas cada um desses casos devem ser estudados a
parte conforme surgirem.

No próximo post veremos como usar o gccxml para preencher os valores
usados dentro do template.

Last edited Feb 3, 2009 at 4:35 PM by kcfelix, version 1

Comments

No comments yet.