UNIVERSIDADE FEDERAL DE SANTA CATARINA Aliando

Propaganda
UNIVERSIDADE FEDERAL DE SANTA CATARINA
Aliando Performance e Produtividade
no Desenvolvimento de Jogos
Fábio Luís Stange
Florianópolis
2005/1UNIVERSIDADE FEDERAL DE SANTA CATARINA
DEPARTAMENTO DE INFORMÁTICA E ESTATÍSTICA
BACHARELADO EM CIÊNCIAS DA COMPUTAÇÃO
Aliando Performance e Produtividade
no Desenvolvimento de Jogos
Fábio Luís Stange
Trabalho de conclusão de curso apresentado como
parte dos requisitos para obtenção do grau de
Bachareu em Ciências da Computação
Florianópolis
2005/1Fábio Luís Stange
Aliando Performance e Produtividade
no Desenvolvimento de jogos
Trabalho de conclusão de curso apresentado como parte dos requisitos para obtenção do
grau de Bachareu em Ciências da Computação.
Orientador:
________________________________
Eng. Marcelo A. Camelo
[email protected]
Co-orientador:
________________________________
Prof. Mst. Renato Cislaghi
[email protected]
Banca Examinadora:
_______________________________
Bel. Guilherme Bertoni Machado
[email protected]
________________________________
Bel. Bruno da Silva Oliveira
[email protected]
“Make it as simple as possible. But no simpler.”
Albert Einstein
Sumário
Lista de Figuras .................................................................................................. 1
Lista de Reduções .............................................................................................. 3
Resumo .............................................................................................................. 5
Abstract .............................................................................................................. 7
1. Introducao ......................................................................................................
9
1.1. Os limites da tecnologia .................................................................... 10
1.2. A indústria de jogos ........................................................................... 11
1.3. Nem tudo está perdido ...................................................................... 13
1.4. Cavando um pouco mais fundo ......................................................... 14
1.5. Line Printator Tabajara ...................................................................... 15
2. Estudo de caso: Taikodom ............................................................................. 17
2.1. Taikodom .......................................................................................... 18
2.2. Arquitetura ........................................................................................ 19
2.3. O que dizem os números .................................................................
21
3. Integrando Linguagens .................................................................................... X
3.1. A integração no braço ....................................................................... X
3.2. As opções ......................................................................................... X
3.3. some_lib: Uma biblioteca didática .................................................... X
4. SWIG – Simplified Wrapper and Interface Generator .................................... X
4.1. Hello SWIG ....................................................................................... X
4.2. Integrando some_lib ......................................................................... X
4.3. Testando ..........................................................................................
X
5. Boost Python .................................................................................................. X
5.1. Hello Boost.Python ........................................................................... X
5.2. Integrando some_lib ......................................................................... X
5.3. Testando ..........................................................................................
X
6. Apocalypse ..................................................................................................... X
6.1. Comparando estratégias num projeto real........................................
X
6.2. SWIG ................................................................................................ X
6.3. Pyste ................................................................................................. X
7. Conclusão ......................................................................................................
X
7.1. SWIG x Pyste+Boost.Python ...........................................................
X
7.2. Apocalypse – PostMortem ...............................................................
X
7.3. Botando as cartas na mesa .............................................................
X
8. Referências Bibliográficas .............................................................................
X
9. Anexos ...........................................................................................................
X
9.1. Código fonte: some_lib ..................................................................... X
9.2. Interface SWIG para some_lib .........................................................
X
9.3. Código fonte: testes de some_lib em python ...................................
X
9.4. Código fonte: testes de some_lib em java .......................................
X
9.5. Resultados dos testes de some_lib em python ................................ X
9.6. Resultados dos testes de some_lib em java .................................... X
9.7. Interface Pyste + Boost.Python para some_lib ................................. X
9.8. Código fonte: testes de some_lib com Boost.Python ....................... X
9.9. Resultado dos testes de some_lib com Boost.Python ....................... X
9.10. Apocalypse – Source Code ............................................................ X
9.11. Apocalypse – Interface SWIG ......................................................... X
9.12. Apocalypse – Interface Pyste + Boost.Python ................................ X
9.13. The Zen of Python ........................................................................... X
10. Artigo ............................................................................................................. X
Resumo
“A designer knows that he has achieved
perfection not when there is nothing left to add,
but when there is nothing left to take away”
Antoine de St-ExpureyResumo
blah
Abstract
“However beautiful the strategy, you should occasionally
look at the results.”
Winston ChurchillAbstract
blah
Listas de Siglas e Abreviações
“I believe that a scientist looking at nonscientific problems is just as dumb as the next guy”
Richard FeynmanListas de Siglas e Abreviações
API: Application Programming Interface
CVS: Concurrent Version System
GUI: Graphic User Interface
IGDA: International Game Developers Association
IDE: Integrated Development Environment
MMOG: Massive Multiplayer Online Game
SWIG: Simplified Wrapper and Interface Generator
UML: Unified Modeling Language
WYSIWYG: What You See Is What You Get
Listas de Figuras
“There are two ways of constructing a software design:
one way is to make it so simple that there are obviously no
deficiencies, and the other way is to make it so
complicated that there are no obvious deficiencies. The
first method is far more difficult”
C.A.R. HoareLista de Figuras
Figura 2.1: Taikodom – Histórico de código .................................................................. X
Figura 2.2: Taikodom – Arquitetura de código .............................................................. X
Figura 4.1: Sistema SWIG completo ............................................................................. X
Figura 5.1: Sistema Pyste + Boost.Python completo ..................................................... X
1. Introdução
“The conceptual floor is littered with the
virtual carcasses of people who let
their desire for high machine efficiency
override other considerations, things
like programmer and user productivity.”
Karl Lehenbauer
1.1. Os limites da tecnologia
A tecnologia disponível para a construção de softwares vem avançando a passos
largos nas ultimas décadas. Linguagens de programação de alto nível, IDEs, ferramentas
auxiliares de todo tipo, UML...
Olhando especificamente para as linguagens de programação, existem diversas
opções, com os mais diferentes focos e paradigmas imagináveis. Variando da hoje
comum Orientação a Objetos e da Programação Estruturada à Programação Lógica e
Programação Funcional, passando por outras formas de pensar diferentes, algumas
surpreendentes, outras de valor prático questionável. Cada paradigma destes por sua vez
pode dar luz a uma varidade de linguagens de programação.
No entanto, é comum que cada uma dessas diferentes linguagens de programação
tenha severos pontos fracos. Algumas pessoas que defendem determinadas linguagens
com afinco religioso parecem pensar diferente. A vida seria bem mais fácil se elas
estivessem certas...
Uma dessas desvantagens é uma absurda valorização da performance do
software, contra a produtividade do seu programador, pela vasta maioria das linguagens
comerciais de hoje, apesar de ser fato bem conhecido que uma quantidade mínima de
código é responsável por uma grande quantidade do tempo total de execução de um
programa. (TODO: referencia sobre isso)
Para aqueles que aceitam a afirmação de que todas as linguagens tem severos
pontos fracos como verdadeira, este trabalho visa procurar e sugerir soluções práticas
que nos permitam focalizar nas maiores vantagens de diferentes linguagens, ao mesmo
tempo em que nos permitam fugir das suas desvantagens.
1.2. A indústria de jogos
Em Novembro de 2004, apenas o primeiro dia de vendas do jogo Halo 2
movimentou mais de 125 milhoes de dólares, o maior dia de abertura da toda indústria de
entretenimento (incluindo a do cinema). A mídia vem dizendo que a indústria de jogos
movimenta mais dólares do que a indústria do cinema. Independentemente de essa
informação ser verdadeira ou não, é fato que a indústria de jogos movimenta bilhôes. Os
custos milionários para se desenvolver um jogo de ponta já quase se equiparam a
produções de Hollywood.
A complexidade e escalas enormes desses sistemas, os diversos desafios
tecnológicos envolvidos e o fascínio que a indústria de jogos parece ter sobre
desenvolvedores fazem com que a indústria de jogos concentre bons profissionais da
indústria de software, talvez os melhores.
Se a indústria de jogos movimenta bilhôes de dólares e dispõe dos melhores
profissionais, seria razoável concluirmos que consequentemente a construção do software
de um jogo, por dispor de mão de obra de qualidade e dinheiro, é um processo
relativamente indolor e sem solavancos.
No entanto, nessa indústria, com uma frequencia muito maior do que esperada,
novos títulos são lançados com vários anos de atraso, e muitas vezes com qualidade
questionável. E pior que isso: a indústria de jogos é bem conhecida pelas condições
extremas de trabalho, com funcionários trabalhando semanas de 60-80+ horas sendo
mais uma regra do que uma exceção.
Num episódio que ficou conhecido como “EA Spouse”, e que está chacoalhando as
fundações do mercado de trabalho da indústria de jogos, a esposa de um funcionário da
Eletronic Arts – maior distrubuidora de jogos do mundo – conta o sofrimento por que
passa a sua família e descreve as condições quase desumanas em que seu marido
trabalha.
(http://www.livejournal.com/users/ea_spouse/).
Pouco tempo depois, a IDGA publicou em seu site uma carta aberta com suas
preocupações com a qualidade de vida dos trabalhadores da indústria de jogos
(http://www.igda.org/qol/open_letter.php).
Demonstrações
semelhantes
de
descontentamento aconteceram após este episódio e continuam a acontecer em diversas
partes do mundo.
A indústria de jogos sofre muito com a falta de veteranos entre seus funcionários,
pois poucas pessoas conseguem trabalhar mais de 5 anos nessas condições. Talvez por
isso a indústria acredita que precise cobrar tanto de cada funcionário, entrando num triste
ciclo vicioso. Citando Francois Dominic Laramee: “Making fun and games isn't all fun and
games. It's serious work, harder than most”.
Independente dos motivos que levaram a indústria a chegar neste ponto, os fatos
acima deixam claro que a indústria de jogos precisa desesperadamente de um alto nível
de produtividade para seus desenvolvedores. Que ela também precisa de altos níveis de
performance é inquestionável e bem conhecido. Talves por isso mesmo ainda não tenha
encontrado soluções satisfatorias para o problema da produtividade: a busca incançável
por performance disfarça o caminho para a produtividade.
Os conceitos apresentados neste trabalho podem ser aplicados em quaisquer tipos
de software. No entanto, pelas razões mencionadas acima, e pela experiências do autor
na área, este trabalho se focaliza mais na aplicação desses conceitos no desenvolvimento
de jogos.
1.3. Nem tudo está perdido
Como resolver o dilema de aliar performance e produtividade, duas características
aparentemente antagônicas? O primeiro passo é entender que existe sim um certo
antagonismo entre esses dois conceitos, mas apenas se estivermos analizando seus
extremos, e dentro de uma mesma linguagem de programação.
Ainda está para existir uma linguagem de programação que consiga aliar
satisfatoriamente estes dois conceitos. Apesar de exitir pessoas que pensem em
contrário, volto a repetir que o mundo seria mais colorido se elas realmente estivessem
certas. Espero que um dia elas estejam.
Mas enquanto este dia não chega, há algo que podemos fazer, e neste caso é
olhar para mais de uma linguagem de programação. Olhar com olhos críticos, deixar de
lado os preconceitos e preferências, e encontrar as vantagens e desvantagens de cada
uma para os requisitos que se tem em mãos.
Para o caso da performance, a escolha não é difícil e geralmente se baseia em
C++. Para a produtividade, a escolha é bem mais difícil, e existem várias linguagens que
levam a produtividade do programador experiente a níveis bem superiores àqueles
obtidos por programadores experientes em C++ resolvendo os mesmos problemas.
Não estou me referindo á utilização de linguagens de script. Existem diversos
artigos cobrindo a utilização de scripts para auxiliar o desenvolvimento de jogos, e
praticamente todas as companhias de jogos os utilizam hoje. Estou me referindo dar um
passo a mais, ir mais além, e utilizar uma linguagem poderosa, que não seja
necessariamente C++, para desenvolver toda a parte do jogo que não seja de
performance crítica, e não apenas scripts.
1.4. Cavando um pouco mais fundo
Para aqueles que me perguntam sobre o desenvolvimento de jogos, parece existir
um conceito de que a grande maioria do desenvolvimento de um jogo gira em torno de
renderização e física. Este conceito não está muito longe de estar correto se o que está
sendo analizado é tempo de execução. Mas a regra dos 10% / 90% para quantidade de
código / tempo de execução.também é válida no desenvolvimento de jogos, o que
significa que cerca de 90% de todo o código de um jogo roda apenas cerca de 10% do
tempo total de execução deste, e quando roda, geralmente não necessita de alta
performance.
Idealmente, a física e a renderização não deveriam ocupar muito tempo de
desenvolvimento, já que são tecnologias conhecidas, e tempo gasto nelas é tempo gasto
melhorando a tecnologia atual, ou reinventando a roda. Na verdade adaptando a roda às
evoluções tecnológicas, ou à necessidades específicas.
Os outros 90% do código é aquilo que dá ao jogo a sua indentidade, é aquilo que
entretêm as pessoas, e é aquilo que não precisa rodar absurdamente rápido. Transações
financeiras, iterações entre os usuários, sistema de danos, sistema de comunicação, de
transporte, enfim, um sem fim de requisitos que vão muito além da simulação física e da
renderização. Requisitos estes que geralmente podem ser implementados outra
linguagem, mas que geralmente são implementados em C++ por estarem colados nas
partes que precisam de performance.
É prática comum no desenvolvimento de jogos utilizar linguagens mais produtivas
para a realização de tarefas mais independentes, como uma subrotina de inteligencia
artificial, que toma como entrada todos os dados de que precisa, e retorna todos os dados
a serem utilizados. (TODO: Referencias sobre scripts)
O foco aqui é bem diferente. Podemos desenvolver praticamente todo o jogo em
uma linguagem de alto nível, e não apenas scripts. Cerca de 90% do nosso código roda
10% do tempo, esperando para ser implementado em outra linguagem, melhorar nossa
produtividade e nos dar mais tempo. E tempo é dinheiro.1.5. Line Printator Tabajara
Para demonstrar o quanto a programação em uma linguagem de alto-nível pode
fazer diferença, vou ilustrar um problema comum na programação, abrir um arquivo,
percorrer as suas linhas fazendo alguma coisa com elas. Neste caso, vou apenas gerar
um aplicativo que vai imprimir todas as linhas de um arquivo passado por linha de
comando, mostrando o código em Python e o código em C que executa esta tarefa.
TODO: Ver o código em C++ que faz isso com streams
Line Printator Tabajara, em Python:
# Line Printator - linguagem de alto nivel
import sys
arquivo = file(sys.argv[0])
for linha in arquivo.readlines():
print linha
arquivo.close()
Line Printator Tabajara, em C:
// Line Printator - linguagem nao tao alto nivel assim...
#include <stdio.h>
int main(int argc, char *argv[])
{
char linha[1024];
FILE *arquivo = fopen(argv[1], "r");
while(fgets(linha, 1024, arquivo))
{
printf("%s\n", linha);
}
fclose(arquivo);
return 0;
}
Não se faz necessário tecer comentários sobre as vantagens que o primeiro código
apresenta sob a lente da produtividade. Como muitas pessoas tem uma pulga na orelha
com relação à performance de linguagens interpretadas, foi feita uma rápida análise:
ambos os programas foram rodados lendo um arquivo fonte C++ com 800 linhas e 32Kb,
com a mesma janela de console. O programa em C++ rodou em 5,5 segundos. O
programa em Python rodou em 5,5 segundos.
Nesse momento o leitor me xinga e pensa: “Mas esse exemplo é injusto, o gargalo
está no IO e na renderização do console!”. Exatamente! Se sabemos de ante-mão onde
estão nossos gargalos e sabemos que o processamento não é um deles, não existe
nenhuma necessidade de implementarmos em C++. Se por outro lado o processamento é
um dos gargalos (com frequencia é), mas não temos extrema necessidade de que esse
processamento termine em um período extremamente curto de tempo, também não
precisamos implementar em C++ (o usuário não acharia ruim se uma transação financeira
demorasse meio segundo ao invéz de 400 milisegundos, por exemplo)
Se no exemplo dado, o programa em Python tivesse rodado em 6 segundos, ele
ainda estaria fazendo seu serviço satisfatoriamente. Nâo tão satisfatoriamente como
aquele em C++, é verdade, mas o tempo (e consequentemente o dinheiro) que
economizamos para fazê-lo em Python pode significar a diferença entre atingir ou não um
prazo determinado, que pode por muitas vezes determinar o sucesso ou o fracasso de um
projeto. O perfeccionismo é um dos maiores impecilhos para que um desenvolvedor
melhore a sua produtividade. Citando Steve Pavlina: “Realize that an imperfect job
completed today is always superior to the perfect job delayed indefinitely”
Se o leitor sobreviveu até aqui, são boas as chances de que os preconceitos não
estão muito enraizados em sua mente. Aprender a utilizar o melhor que cada linguagem
nos tem a oferecer ao invéz de defender apenas uma com unhas e dentes só nos traz
vantagens. Abra a sua mente e siga em frente.
2. Estudo de caso: Taikodom
“Making fun and games isn't all fun and games.
It's serious work, harder than most.”
Francois Dominic Laramee
2. Estudo de caso: Taikodom
Em Janeiro de 2004, fui contratado pela Hoplon Infotainment, empresa que
desenvolve jogos online. Durante todo o ano de 2004 trabalhei no principal projeto da
empresa, um MMOG espacial, chamado Taikodom. Os conceitos e o conhecimento
adquiridos durante este período acabaram por mudar o foco deste trabalho para onde ele
se encontra hoje. (TODO: como falar isso em terceira pessoa?)
Taikodom é um projeto ambicioso, e seu sucesso depende, dentre muitos outros
fatores, de que a equipe de desenvolvimento implemente muitas funcionalidades em
muito pouco tempo. Aliado à falta de experiênia da maioria da equipe no desenvolvimento
comercial de jogos, essa era uma grande barreira a ser transposta.
Um ano se passou, e hoje mesmo antes de seu lançamento o projeto já tem
milhares de fãs e grande interesse da mídia especializada, que aguardam o seu
lançamento com espectativa. Eu acredito que para termos chegado até aqui, a utilização
de diferentes linguagens para diferentes partes da implementação foi de fundamental
importância.
Vale salientar que o futuro sucesso ou fracasso comercial deste projeto não reforça
ou diminui o valor dos conceitos apresentados neste trabalho, já que muitas outras
variáveis estão presentes na equação do sucesso comercial. Para maiores informações
sobre o projeto Taikodom: www.taikodom.com.br
2.1. Arquitetura
A figura 2.1 diagrama os componentes da arquitetura de código do Taikodom. Este
diagrama não tem nenhuma relação com a arquitetura do software como costumamos nos
referir a ela, ele apenas mostra a inter-relação entre as diferentes linguagens utilizadas no
projeto.
Figura 2.1: Taikodom – arquitetura de código
Podemos ver que a maioria do cliente é implementada em Python e a maioria do
servidor é implementada em Java. A escolha de Python no cliente foi baseada na maior
produtividade que o desenvolvimento nesta linguagem proporciona, na opinião da nossa
equipe.
Já a escolha de Java no servidor adveio da maior disponibilidade de APIs prontas
para funcionalidades que precisáva-mos. No nosso caso específico, JGROUPS para a
lógica de grupos de rede, e HYBERNATE para abstrações de persistência com banco de
dados. O ganho em produtividade de uma implementação em Python não justificaria o
tempo gasto com a implementação destas funcionalidades num cenário de médio prazo.
Foi cogitada a utilização das 3 linguagens no servidor, mas a manutenção das interfaces
numa configuração complexa como esta nos pareceu arriscada demais.
Já a parte comum a ambos, como a hierarquia básica dos objetos de jogo,
simulação física, protocolo de rede, classes base de configuração e logging, etc., são
implementadas em C++, para evitar o custo, principalmente de manutenção, de se
implementar uma funcionalidade em duas linguagens diferentes.
Existem também funcionalidades específicas somente para o cliente ou para o
servidor que são implementadas em C++ pela necessidade de que estas rodem o mais
rápido possível. A mais notória destas funcionalidades é a parte de renderização 3D, mas
outras funcionalidades que são identificadas de ante-mão como sendo de alta
performance também são implementadas em C++. A simulação física também entra
nessa categoria apesar de já ter sido identificada como comum anteriormente.
Existe ainda um grupo de funcionalidades que são inicialmente implementadas em
Python ou Java, mas que por profilling (TODO - traducao) percebemos como sendo
gargalos, e são traduzidas para C++. Muitos advogam esta característica como sendo
uma desvantagem que torna o uso de outras linguagens impraticável. Mas na verdade a
porcentagem de funcionalidades que necessitam de reimplementação é muito pequena, e
portanto o tempo perdido traduzindo uma funcionalidade para C++ é ínfimo perto do
tempo ganho em produtividade na implementação de todas as outras funcionalidades.
Por fim, o SWIG faz a 'cola' para que os códigos em Python e em Java consigam
conversar com o código implementado em C++. Veremos como ele funciona mais tarde.
2.2. O que dizem os números
A figura 2.2 apresenta o número de linhas de código das diferentes linguagens
utilizadas no projeto Taikodom.
As amostras foram tiradas dos dias 1, 8, 15 e 22 de cada mês, do tronco (HEAD)
do repositório, desde que começamos a utilizar o CVS até o fim de 2004. Não foram
computadas linhas de comentários, linhas em branco, e linhas com apenas um caracter
além dos brancos (geralmente abertura ou fechamento de bloco)
Figura 2.2: Taikodom – Histórico de código
TODO: Ver porcentagem do Java que eh gerado pelo hybernate
TODO: Ver total de código depois de rodado os scripts de geração
Podemos ver pelo gráfico que nos primeiros meses de desenvolvimento a
curva de crescimento do código C++ é mais acentuada que a curva de crescimento das
outras linguagens, mas a partir do terceiro mês, a taxa de crescimento das outras
linguagens ultrapassa a taxa de crescimento do C++, e nos últimos meses o total de
linhas de código de ambas as linguagens ultrapassa a contagem do C++.
Isso se deve ao fato de que no início do projeto, é necessária a implementação de
toda a infra-estrutura a ser utilizada pelos outros componentes a serem implementados
posteriormente. A física e a renderização também tiveram a maioria de sua
implementação realizada nestes primeiros meses. Apenas depois de que este alicerce em
C++ estava pronto, a implementação do resto do sistema em outras linguagens teve base
para se expandir.
A tendência é que a curva de crescimento de C++ se transforme em uma reta com
derivada zero conforme avançamos mais no tempo, e esse decréscimo seja transformado
em crescimento nas outras linguagens. Jogos online, como é o caso do Taikodom, podem
se beneficiar bastante deste cenário, já que a sua implementação continua mesmo após o
seu lançamento, geralmente incrementando o produto por toda a sua vida útil enquanto
este estiver online.
Jogos que não necessitem de uma arquitetura cliente-servidor, ou onde o cliente e
servidor serão implementados na mesma linguagem, poderiam se beneficiar mais ainda
de uma implementação em uma linguagem de alto nível aliada ao C++, pois não
necessitariam ter o código comum ao cliente e ao servidor implementado em C++.
3. Integrando Linguagens
“If knowledge can create problems, it is not
through ignorance that we can solve them.”
Isaac Asimov3.1. A integração no braço
Praticamente todas as linguagens de programação oferecem algum tipo de API que
permite ao programador integrar programas escritos nestas linguagens com código nativo
escrito em outra linguagem – como C.
No entanto, a maioria destas APIs são complexas, e implementar métodos
envoltórios, que permitam que métodos já prontos em código nativo sejam chamados a
partir da linguagem alvo, é geralmente um processo difícil e penoso, completamente
imcompatível com a premissa básica de ganho de produtividade que queremos alcançar.
Para ilustrar, a segue um trecho de uma função envotória que utiliza JNI (gerada
automaticamente pelo SWIG, pode ser simplificada), que nos permite chamar a função
looseFunction() em java:
JNIEXPORT jint JNICALL
Java_some_1package_some_1lib_1javaJNI_looseFunction(JNIEnv *jenv, jclass jcls) {
jint jresult = 0 ;
int result;
(void)jenv;
(void)jcls;
result = (int)looseFunction();
jresult = (jint)result;
return jresult;
}
E isto não é tudo, existem ainda algumas declarações que precisam ser feitas em
java para que afim possamos chamar a função nativa looseFunction em java.
Mas não vamos nos aprofundar em JNI ou em nenhuma outra API de acesso
nativo, já se produtividade é a meta, a última coisa que queremos ver é código feio e
confuso como esse.
3.2. As opções
A ferramenta mais amplamente utilizada para a integração entre linguagens de alto
nível e código nativo é o SWIG (Simplified Wrapper and Interface Generator). SWIG pode
conectar código escrito em C ou C++ com 13 linguagens diferentes: Python, Java, Perl,
PHP, Tcl, C#, Chicken, Ruby, Allegro CL, Guile, Modula-3, Mzscheme e OCAML.
SWIG é atualmente a solução mais genérica e mais abrangente para a
programação hídrida. Isso não faz dela necessariamente a melhor solução. Soluções
mais específicas, que se destinam a integrar apenas uma linguagem de alto-nível a
código nativo, podem se beneficiar desta especialização para serem mais simples, mais
eficientes, e/ou mais completas, no ambiente específico a que se destinam.
Uma destas soluções específicas é a Boost.Python, parte da biblioteca Boost, e
que se destina a integrar especificamente Python com C/C++. A análize de uma solução
específica para python se deve ao fato de que a linguagem python sempre perseguiu a
produtividade do desenvolvedor como prioridade, mesmo quando isso possa impactar
negativamente a performance do software ou a compatibilidade com versões antigas da
linguagem.
Vale lembrar que nem todas as funcionalidades providas pelo C++ podem estar
presentes na linguagem alvo, e nesses caso a interface deve sempre que possível prover
mecanismos para que essa funcionalidade possa ser utilizada na linguagem alvo, mesmo
que com restrições ou por caminhos não usuais.
Existem ainda outras soluções para programação hídriba, inclusive para outras
linguagens que não aquelas suportadas pelo SWIG, mas elas não serão analizadas neste
estudo.
3.1. some_lib: uma biblioteca didática
Para testar as duas alternativas escolhidas, foi criada uma bibliteca hipotética –
some_lib. Uma espécie de 'Hello World' bombado, esta biblioteca contém apenas código
extremamente simples, mas se utiliza de uma extensa gama de funcionalidades providas
por C e C++. Desta forma, podemos testar praticamente tudo que pode-se querer
disponibilizar para uso em uma linguagem de alto nível, enquanto mantendo o código o
mais simples possível.
A biblioteca também foi estruturada de maneira a se assemelhar de um caso real
de utilização de uma biblioteca já pronta que queira se exportar – ao invéz de conter todo
o código em um único arquivo, some_lib tem uma estrutura semelhante à de uma
biblioteca distribuida online:
+ some_lib
+ include
# diretorio raiz
# diretorio com os arquivos de interface
- some_lib.h
+ src
# diretorio com os arquivos fonte
- some_lib.cpp
+ lib
# diretorio com arquivos .lib binários
- some_lib.lib
+ vc7
# diretorio[s] com arquivos de projeto para cada plataforma
- some_lib.vcproj
Funcionalidades presentes em some_lib que serão testadas em outras linguagens:
Acesso a variáveis e funções globais, utilização de constantes (#defines e enums),
estruturas e classes, métodos públicos e privados, de instância e de classe, herança,
classes abstratas, polimorfismo, referencias por valor, ponteiro ou referência, argumentos
padrão e argumentos múltiplos, utilização de callbacks, sobrecarga de operadores,
exceções, templates, herança múltipla, e utilização de classes da Standard Library.
Todas as funcionalidades estão presentes de forma simples em some_lib. O código
completo da biblioteca está no Anexo 1. (TODO: e pode ser baixado em
www.inf.ufsc.br/~petrucio/tcc_src.zip)
4. SWIG – Simplified Wrapper
and Interface Generator
“Very often, people confuse simple with simplistic.
The nuance is lost on most."
Clement Mok, Chief Creative Officer, Sapient.4.1. Hello SWIG
“SWIG é uma ferramenta de desenvolvimento de software para a construção de
interfaces entre linguagens de scripting e programas C e C++” [TODO]. A meta do SWIG
é fazer com que a integração de linguagens de alto-nível com C/C++ seja o mais
transparente e indolor possível, permitindo que o programador não perca tempo com a
tediosa e complexa tarefa de fazer as duas linguagens conversarem entre si. SWIG é uma
ferramenta gratuita, inclusive para uso comercial, e pode ser baixada de www.swig.org.
A figura 4.1 ilustra o funcionamento completo de um sistema híbrido em C++ com
uma linguagem de script, utilizando SWIG para criação das interfaces. Todas as partes
apresentadas nesta figura estão presentes em um sistema híbrido utilizando SWIG,
mesmo um simples 'Hello World'.
Pode parecer intimidante a princípio, mas se torna simples com a utlização e a
prática. Não se pode ganhar todas, e no caso da construção de um sistema híbrido, existe
sempre um custo inicial para a integração de todas as partes envolvidas, e um custo de
criação e manutenção das interfaces.
As fontes da biblioteca e os cabeçalhos da biblioteca são previamente conhecidos,
e compõe o código C/C++ que queremos exportar para uma linguagem de alto nível. A
sua interação com o compilador e a geração dos binários da biblioteca não são novidade.
Para o nosso exemplo em some_lib, os fontes estão no arquivo some_lib.cpp, os
cabeçalhos em some_lib.h, e os binários no arquivo some_lib.lib.
Se estivermos exportando uma biblioteca pronta para a qual não temos os arquivos
fontes, em teoria apenas os arquivo de cabeçalho e os binários pré-compilados da
biblioteca já deveriam nos ser suficientes. Em alguns casos, podem ser necessárias
modificações no código fonte C/C++, ou a criação de código envoltório que adapte o
código da biblioteca às limitações do SWIG ou da linguagem alvo.
Figura 4.1: Sistema SWIG completo
As interfaces do SWIG (ver fig. 4.1) são o coração
da integração em um sistema híbrido utilizando SWIG. O arquivo de interface (geralmente
com extensão .i) é dividido em duas seções: a primeira com as declarações de tudo o que
o SWIG precisa conhecer sobre o código C++, e a segunda com declarações de tudo
aquilo que deseja-se exportar.
Vejamos como seria um arquivo de interface SWIG simples para um exemplo 'Hello
World':
%module hello_module
%{
// Declarações daquilo que o SWIG precisa conhecer
void printHello();
%}
// Declarações daquilo que o SWIG deve exportar
void printHello();
Em casos bem comportados, como no exemplo acima, boa parte, senão todas as
declarações feitas na primeira seção serão as mesmas declarações que queremos
exportar na segunda seção. As diferentes seções são necessárias no entanto em casos
mais complexos, como veremos mais adiante. Para arquivos de cabeçalho bem
comportados, podemos simplesmente incluir o arquivo de cabeçalho em ambas as seções
e exportar tudo que está declarado dentro dele:
%module hello_module
%{
#include “hello_lib.h”
%}
%include “hello_lib.h”
Se rodarmos o SWIG passando-lhe este arquivo de interface, ele irá nos gerar os
arquivos C/C++ necessários para gerar uma biblioteca que contenha todos os métodos
envoltórios para chamarmos a função printHello na linguagem target. Exemplo de uma
chamada de SWIG para a geração de envoltórios python:
$ swig -python -c++ -o output_file.cpp hello_module.i
Esse comando irá gerar o arquivo fonte envoltório 'output_file.cpp' e o arquivo de
acesso 'hello_module.py'. O arquivo fonte envoltório deve ser compilado e linkado junto
com os binários da biblioteca, para gerar uma biblioteca dinâmica chamada
'_hello_module.dll'. O arquivo de acesso é implementado na linguagem alvo, e existe para
realizar algumas adaptações sobre a biblioteca exportada para prover uma API que seja
mais natural à linguagem alvo.
De posse do arquivo de acesso e da biblioteca dinâmica adaptada, pode-se
chamar as funções implementadas em C++ de dentro da linguagem alvo:
$ python
Python 2.4c1 (#59, Nov 18 2004, 18:54:56) [MSC v.1310 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import hello_module
# Isso importa o arquivo de acesso hello_module.py, que
por sua vez carrega a biblioteca dinâmica _hello_module.dll
>>> dir(hello_module)
# Imprime todos os membros de hello_module
['printHello', ...]
>>> hello_module.printHello()
Hello World!
4.2. Integrando some_lib
Como o SWIG nos permite a integração com várias linguagens, seria interessante
que um teste dele fizesse integração com mais de uma linguagem. Foram escolhidas as
linguagens python e java para este estudo.
Como vamos realizar a integração com duas linguagens, os arquivos de interface
foram separados em três, com um arquivo de interface comum contendo as declarações
pertinentes a ambas as linguagens, e com um arquivo específico para cada linguagem,
para tratar das peculiaridades de cada uma.
Segue a árvore de diretórios e arquivos relevantes do nosso diretório de testes do
SWIG. Arquivos envoltos por parênteses são gerados automaticamente.
+ SWIG
# diretorio raiz
- some_lib_swig_interface.i
# Arquivo de interface comum
- some_lib_swig_python_interface.i
# Arquivo de interface python
- some_lib_swig_java_interface.i
# Arquivo de interface java
- swig_run.sh
# script que roda o SWIG
+ some_lib_python_wrappers
# fontes e projeto dos envoltórios python
- some_lib_python_wrappers.vcproj # projeto do visual studio
- (some_lib_python_wrappers.cpp)
# Código gerado pelo SWIG
- (some_lib_python_wrappers.h)
# Código gerado pelo SWIG
+ some_lib_java_wrappers
# fontes e projeto dos envoltórios java
- some_lib_java_wrappers.vcproj
# projeto do visual studio
- (some_lib_java_wrappers.cpp)
# Código gerado pelo SWIG
+ python_tests
- (some_lib_python.py)
# Código de acesso gerado pelo SWIG
- (_some_lib_python.dll)
# Resultado da compilacao dos envoltorios
- some_lib_python_tests.py
# Testes em python
+ java_tests
+ some_package
# Diretorio com o pacote de acesso
- (...)
# Classes de acesso geradas pelo SWIG
- (some_lib_java.dll)
# Resultado da compilacao dos envoltorios
- some_lib_JavaTests.java
# Testes em java
Todos os arquivos de interface e o script para rodar o SWIG estão no Anexo 9.2.
O arquivo de interface comum - some_lib_swig_interface.i - inclue o arquivo
some_lib.h na primeira seção para que o SWIG conheça toda a interface da biblioteca, e
inclue some_lib.h na segunda seção, para que o SWIG exporte tudo aquilo que for
possível de ser exportado sem maior interação. Em situações normais, isso pode
geralmente ser suficiente. No nosso caso, o arquivo de interface pode parecer grande e
complexo,
mas
como
o
arquivo
some_lib.h
contém
intencionalmente
muitas
funcionalidades diferentes a serem exportadas, o arquivo de interface acaba sendo
grande.
Comparando os arquivos de interface utilizados para python e para java, percebese que o arquivo específico para java é bem maior do que o arquivo para python. Isso
porque java não possui conceitos como sobrecarga de operadores e herança múltipla, e
portanto precisamos realizar algumas manobras para utilizar esses conceitos em java.
Quando mais completa for uma linguagem - na verdade quanto mais conceitos
encontrados em C++ também existirem na linguagem alvo - maior será a facilidade de se
implementar um sistema híbrido usando essa linguagem.
Dentre as funcionalidades presentes em some_lib, algumas precisaram de
alterações específicas nos arquivos de interface e serão detalhadas a seguir. Os itens a
seguir são tópicos avançados, e o leitor que não tenha bons conhecimentos em C++ pode
avançar para o item 4.3. Vale lembrar que a complexidade dos tópicos apresentados aqui
não devem afastar o leitor de tentar utilizar o SWIG, porque a maioria deles não
precisarão ser utilizados em uma situação de uso real, mas eles são tratados aqui para
verificarmos a completude da ferramenta.
4.2.1. Callbacks e polimorfismo inter-linguagens:
Callbacks costumam ser pouco utilizados em programas comuns, no entanto são
importantes no desenvolvimento de jogos, então vamos nos aprofundar um pouco na sua
utilização inter-linguagens.
Não é possível implementar uma função na linguagem alvo e utilizá-la como uma
função de callback em C++. Para passar funções de callbacks para código em C++,
podemos implementar as funções em C++ , ou usar uma solução de callback polimórfico.
Na primeira solução, implementa-se um função de callback em C++ que seja
linkada junto com a biblioteca. No nosso exemplo, a função foi implementada dentro do
próprio arquivo de interface. Então temos uma função em C++ que recebe como
parâmetro uma função de callback:
void
setCallback(float (*callback)(float));
E uma função, também em C, que implementa uma função de callback:
float increment_callback(float value);
Declarando no arquivo de interface some_lib_swig_interface.i:
%constant float increment_callback(float);
Fazemos com que a nossa função seja tratada como um ponteiro para função na
linguagem alvo.
Finalmente, podemos ligar as pontas na linguagem alvo:
setCallback(some_lib_python.increment_callback);
Isso no entanto pode não ser suficiente, porque talvez seja necessário que a
função que callback necessite acessar dados disponíveis apenas na linguagem alvo.
Poderíamos implementar funções para alimentar esses dados para o C++, mas se
precisarmos implementar muitos callbacks, isso não vai ser nada produtivo. Neste caso, a
melhor solução pode ser utilizar um esquema de callback polimórfico.
Um callback polimórfico consiste em definir uma classe de callback com um
método virtual que será chamado como função de callback. Diferentes callbacks seriam
sub-classes desta, com diferentes implementações do método virtual. No entanto, o
método que requisita o callback irá agora requisitar uma instância dessa classe, e não
mais um ponteiro para função, o que nos obriga a modificar o código da biblioteca, e
portanto nos impede de utilizar esse tipo de solução se não pudermos modificar o código
da biblioteca.
Exemplo:
Classe de callback virtual em C++:
class Callable {
public:
virtual float call(float parm) { return 0; }
};
Subclasse implementada na linguagem alvo:
class PythonCallable(some_lib_python.Callable):
def __init__(self):
some_lib_python.Callable.__init__(self)
def call(self, parm):
return parm + 0.2
Passando instância de Callable na linguagem alvo:
python_callback = PythonCallable()
setPolyCallback(python_callback)
Para que essa solução funcione, precisamos informar o SWIG para habilitar
polimorphismo inter-linguagens, habilitando os 'directors' nos arquivo de interface:
%module(directors="1") some_lib_python
%{
%}%feature("director");
4.2.2. Arrays e ponteiros para tipos básicos
O SWIG tem alguns arquivos de interface pré definidos para exportar algumas
funcionalidades comuns. O arquivo 'carrays.i' contém mapeaentos de tipos para parmitir a
utilização de arrays C como se fossem arrays nativos na linguagem alvo.
No nosso caso, definimos uma classe envoltória para um array de inteiros chamada
INT_ARRAY com o seguinte trecho no arquivo de interface:
%include "carrays.i"
%array_class(int, INT_ARRAY);
Podemos então utilizar um array de C++ como se fosse um array nativo da
linguagem alvo criando uma instância dessa classe com o método de classe
'from_pointer':
array = some_lib_python.INT_ARRAY_frompointer(some_lib_python.cvar.int_array)
Utilizando o array como um array qualquer da linguagem alvo:
from item in array: print item
Já o arquivo 'typemaps.i' nos permite passar tipos básicos da linguagem alvo como
sendo ponteiros ou referências para os mesmos tipos básicos em C++. Vale lembrar que
para a maioria das linguagens de alto-nível, não existe nenhuma diferença entre
parâmetros por valor, por ponteiro, ou por referência, como existe em C++.
Com as seguintes linhas no arquivo de interface comum, definimos que parâmetros
recebendo ponteiros ou referências para inteiros poderão receber inteiros nativos à
linguagem alvo como parâmetros, e serem considerados como parâmetro de saída (que
terão seus valores modificados pela função):
%include "typemaps.i"
%apply int *INOUT { int *a };
%apply int &INOUT { int &a };
Como algumas linguagens alvo não possuem o conceito de parâmetros de saída –
como python – a chamada de uma função que receba um parâmetro desse tipo terá o
valor que teria modificado o parâmetro em questão adicionado ao retorno da função.
Como exemplo, uma função que retorne um inteiro, se tiver como parâmetro um
ponteiro para inteiro, retornaria em uma linguagem alvo sem suporte a parâmetros de
saída uma tupla (no caso de python) ou lista, com o valor do parâmetro adicionado ao
resultado:
Assinaturas em C++:
int resetInt_Value
(int
value);
// Zera valor e retorna 8
int resetInt_Pointer(int *value);
// Zera valor e retorna 8
Chamadas em Python:
>>> some_lib_python.resetInt_Value(5);
8
>>> some_lib_python.resetInt_Pointer(5);
(8,0)
4.2.3. Argumentos variáveis
A maioria das linguagens de alto-nível não tem o conceito de funções com número
variável de argumentos. Mesmo em C/C++, elas são muito pouco utilizadas. Se
quisermos chamar funções com argumentos variáveis na linguagem alvo, precisamos
implementar 'instâncias' dessas funções com argumentos bem conhecidos.
Exemplo:
Temos em C++ uma função 'average' com um número variável de argumentos que
queremos chamar na linguagem alvo:
double AdvancedClass::average(double value1, ...) ;
No arquivo de interface, podemos criar funções envoltórias que recebem um número fixo
de argumentos conhecidos e repassem para esta função:
%extend AdvancedClass {
static double wrap_average(double a) {
return AdvancedClass::average(a, -1.0);
}
// ... - Definições com outro número de argumentos
};
4.2.4. Templates e a biblioteca std
Como o conceito de templates não existe nas linguagens de auto-nível, se
quisermos utilizar classes com templates nessas linguagens, precisamos exportar
'instâncias' com tipos definidos para cada template.
Isso é bastantante simples em SWIG, com o seguinte comando, criamos na
linguagem alvo uma classe 'TemplatedClass_int', que tem os tipos que uma classe
TemplatedClass<int> teria em C++:
%template(TemplatedClass_int) TemplatedClass<int>;
Utilizar classes da biblioteca std é também simples e segue um processo
semelhante, mas antes precisamos incluir os arquivos de interface já prontos que fazem o
mapeamento dos tipos da std para a linguagem alvo:
%include "std_vector.i"
%include "std_map.i"
%include "std_string.i"
Agora basta exportar as 'instâncias' das classes std que queremos utilizar na
linguagem alvo:
%template(FloatVector)
std::vector<float>;
%template(Float2StrMap) std::map<float, string>;
Utilizando na linguagem alvo:
>>> dict = some_lib_python.Float2StrMap();
>>> dict[123.4] = 'Hello'
>>> dict.keys()
[123.40000152587891]
>>> dict.values()
['Hello']
>>> dict[123.4]
'Hello'
4.3. Testando
De posse dos arquivos envoltórios gerados pelo SWIG no passo anterior, basta
compilá-los para gerar a biblioteca adaptada que utilizaremos para os testes. A utilização
da biblioteca na linguagem alvo depende um pouco da linguagem sendo utilizada.
Para python, assumindo que a dll adaptada está no mesmo diretório que o arquivo
de acesso, basta importar o pacote de acesso – ele por sua vez irá importar a dll
automaticamente – e utilizá-lo normalmente:
>>> import some_lib_python
>>> hex(some_lib_python.RED)
'0xff0000'
Em java, precisamos carregar explicitamente a dll, e importar cada classe de
acesso que queremos utilizar:
import some_package.SomeClass;
public class testClass {
public static void main(String[] args) {
System.loadLibrary("some_lib_java");
SomeClass someInstance = new SomeClass();
// ...
}
}
Métodos e variáveis globais são colocados como membros de classe de uma
classe com o mesmo nome do módulo, já que java não tem o conceito de 'global':
import some_package.some_lib_java;
// ...
some_lib_java.looseFunction();
// Funcao estática da classe some_lib_java
Para testar some_lib nas linguagens alvo, foi criado um script python e uma
aplicação java, que executam diversos testes semelhantes sobre as funções exportadas
em some_lib. O código fonte de ambas está no anexo 9.3 e 9.4.
Os resultados dos testes são auto explicativos, detalhados pelas strings impressas
pelos aplicativos de teste. O resultado completo de ambos os testes estão no Anexo 9.5 e
9.6.
Os testes com python foram muito bem sucedidos, com todos os testes rodando
bem sucedidos após algumas alterações que foram sendo feitas no arquivo de interface,
até chegarmos ao arquivo de interface atual, que consegue passar em todos em testes
em python.
Algumas modificações foram feitas diretamente no código de some_lib ao longo
das iterações de testes. Se não pudéssemos modificar o código da bibliteca sendo
exportada, essas alterações geralmente poderiam ser feitas em um arquivo envoltório
dedicado a este fim que seria compilado e linkado junto com os envoltórios do SWIG, ou
adicionadas ao próprio arquivo de interface. No entanto, se temos acesso ao arquivo de
interface, e as modificações a serem feitas para facilitar a exportação da biblioteca não
causam grande impacto em código existente que utiliza a biblioteca, alterar diretamente o
fonte desta pode ser mais simples, e ajuda a manter a interface enxuta.
Os testes em java não tiveram tanto êxito. A maioria dos testes funcionou bem, no
entanto alguns testes falharam:
- A tentativa de criação de uma instância de classe abstrata não gerou nenhuma exceção,
e uma tentativa futura de chamar algum membro da instãncia bastarda causa crash da
aplicação.
- O mapeamento de std::string não ficou completo. Apesar de que a função que pede
uma std::string como argumento foi corretamente mapeada para pedir uma String
nativa de java, a função que retornava uma
std::string retornou um tipo
SWIGTYPE_p_string , que não era tratado como uma string pelo java.
- O poliforfismo inter-linguagens não funcionou corretamente, e a chamada para a função
de callback polimórfico chamou a implementação padrão em C++, e não aquela que tinha
sido extendida em java.
As duas últimas situações descritas acima são utilizadas com sucesso no projeto
Taikodom, o que mostra que o SWIG as suporta, porém me faltou tempo para debuggálas e definir o que está errado – ou, mais provavelmente, faltando – nos arquivos de
interface java.
5. Boost Python
"Any intelligent fool can make things bigger, more
complex, and more violent. It takes a touch of genius -and a lot of courage -- to movein the opposite direction."
Albert Einstein5.1. Hello Boost.Python
Boost.Python é “uma biblioteca que permite a interoperability transparente entre
C++ e a linguagem de programação Python” [3]. A Boost.Python é parte da biblioteca
Boost, que é também gratuita para quaisquer fins e pode ser baixada em www.boost.org.
Diferentemente do SWIG, a Boost.Python não se destina a oferecer a
interoperability com C++ a várias linguagens, mas se especializa unicamente em Python.
Isso permite que o desenvolvimento da biblioteca seja mais focado, já que os
desenvolvedores não dispersam esforços entre várias frentes. Isso também pode fazer
com que o aprendizado completo seja mais rápido – uma olhada na diferença de tamanho
entre os dois manuais é um indício disso, apesar de que a instalação a utilização do
SWIG pela primeira vez é mais simples do que para a Boost.Python.
A filosofia de utilização da Boost.Python também é completamente diferente do
SWIG. Ao invéz de parsear um arquivo com sintaxe própria para gerar os arquivos
envoltórios com código feio para serem compilados, a criação das interfaces a serem
exportadas se dá em C++ mesmo, e será o mesmo código que será compilado para gerar
a biblioteca adaptada. A utilização de macros e funções disponíveis na Boost.Python
torna possível a criação dos envoltórios em C++ sem deixar o código absurdamente feio
como vimos anteriormente.
Exemplo:
Suponhamos que temos uma função printHello() que queremos exportar, como no
exemplo do capítulo anterioir. O código envoltório seria:
#include <boost/python.hpp>
using namespace boost::python;
BOOST_PYTHON_MODULE(hello_module)
{
def("print_hello", printHello);
}
Não existe a necessidade de passar o arquivo de interface por uma ferramenta
para gerar os arquivos que serão compilados, pois este é ao mesmo tempo o arquivo de
interface e o arquivo que será compilado. Também não são criados arquivos de acesso,
basta importar a dll gerada e chamar as funções dela diretamente.
$ python
>>> import hello_module
# Importa a biblioteca hello_module.dll
>>> dir(hello_module)
# Imprime todos os membros de hello_module
['print_hello', ...]
>>> hello_module.print_hello()
Hello World!
Apesar de parecer mais simples a princípio por não precisar de um passo extra por
uma ferramenta e de arquivos de acesso, a sintaxe C++ que precisa ser utilizada para a
exportação de membros de uma biblioteca, apesar de ser apenas a sintaxe normal de
C++, tem poucas semelhanças com a sintaxe C++ de uso comum, e pode ser bem mais
difícil de digerir do que a sintaxe utilizada pelo SWIG. Além disso, a Boost.Python não faz
parsing dos arquivos de interface a serem exportados, e portanto não se pode exportar
todo o conteúdo de um arquivo de cabeçalho como fizemos com o SWIG, deve-se
explicitar um a um todos os membros a serem exportados.
Essas duas desvantagens sozinhas são suficientes para que eu desconside a
utilização da Boost.Python sozinha para aumentar a produtividade no desenvolvimento de
um jogo. Mas existe uma ferramenta chamada Pyste, também gratuita, desenvolvida por
Bruno da Silva de Oliveira, bacharel em Ciências da Computação recém formado pela
Universidade Federal de Santa Catarina, que pode resolver os dois problemas.
A utilização do Pyste em conjunto com a Boost.Python torna o funcionamento desta
solução muito parecido com a utilização do SWIG, de forma que o Pyste roda sobre um
arquivo de interface, gerando código envoltório, que por sua vez será compilado para
gerar a biblioteca adaptada para uso em Python, como mostra a figura 5.1
A utilização de Pyste + Boost.Python como solução de integração Python-C++ está
sendo cada vez mais utilizada para o desenvolvimento de jogos; o jogo Civilization IV, um
dos jogos mais esperados do ano, sequência de uma das séries de jogos de PC mais
bem sucedidas de todos os tempos, está sendo desenvolvido utilizando Pyste +
Boost.Python. Atualmente o Pyste já vem incluído na distribuição padrão da
Boost.Python.Figura 4.1: Sistema PYSTE + Boost.Python completo5.2. Integrando
some_lib
Segue a árvore de diretórios e arquivos relevantes do nosso diretório de testes de
Pyste + Boost.Python. Arquivos envoltos por parênteses são gerados automaticamente.
+ Boost.Python
# diretorio raiz
- some_lib_boost_interface.pyste
# Arquivo de interface pyste unico
- pyste_run.sh
# script que roda o Pyste
- some_lib_adapter_code.h
# Codigo adaptador auxiliar
+ some_lib_wrappers
# fontes e projeto dos envoltórios
- some_lib_wrappers.vcproj
# projeto do visual studio
- (some_lib_boost_wrapper.cpp)
# Código Boost.Python gerado pelo Pyste
+ tests
- boost_python_debug.dll
# Biblioteca dinamica da Boost.Python
- (some_lib_boost.dll)
# Resultado da compilacao dos envoltorios
- some_lib_boost_tests.py
# Testes de some_lib usando Boost
Todos os arquivos de interface, o script para rodar o Pyste, e o fonte do código
adaptador estão no Anexo 9.7.
O arquivo some_lib_boost_interface.pyste é o núcleo da nossa interface. Ao
invéz de declarar um arquivo de cabeçalho como fizemos em Python, declaramos as
classes, estruturas e funções a serem exportadas, e em qual arquivo se encontra a sua
declaração.
Variáveis globais são exportadas como somente-leitura, então é necessário fazer
funções de acesso a elas em C++ para utilizá-las para leitura e escrita. O arquivo
some_lib_adapter_code.h faz esse tipo de adaptação.
Os problemas discutidos no capítulo anterior relacionados a argumentos multiplos e
templates acontecem da mesma maneira utilizando Boost.Python, e são solucionados de
maneira semelhante.
Funções envoltório foram criadas no arquivo de adaptação com um número fixo de
argumentos para resolver o problema dos argumentos multiplos, semelhante a solução
adotada com o SWIG.
Com uma solução também semelhante à utilizada no SWIG, para exportar
templates, cria-se no Pyste um tipo 'Template' com a classe de template que queremos
exportar, e cria-se 'instâncias' com tipos definidos que serão exportadas:
T = Template("TemplatedClass", "some_lib.h")
T('int', 'TemplatedClass_int')
5.2.1. Políticas de chamada
Em C++, é difícil determinar a semântica de argumentos e retornos por ponteiros e
referência, principalmente na definição de quem é o 'dono' do objeto – o responsável por
removê-lo. Um método que retorne um char *, por exemplo, pode ter alocado espaço para
a string e espera que quem chame a função desaloque este espaço, pode estar
retornando o endereço de algo que foi passado por parâmetro, pode estar retornando o
endereço de um recurso fixo, etc.
Nesse contexto, imagine que temos uma simples funcao em C++, foobar:
ClassA &foobar(classB &b) {
return b.a;
}
Utilizar essa função sem maiores cuidados em Python pode ser fatal:
>>> a = foobar(b)
>>> del b
>>> a.some_method() # CRASH!
Isso acontece porque a vida de a está atrelada a vida de b, e quando deletamos b,
a passou a ser uma referência 'suspensa', já que essa semãntica de política de chamada
não tem como ser deduzida pela assinatura do método foobar.
Para funções que retornam ponteiros ou referências, a semântica de retorno
precisa ser explicidamente definida como fizemos no nosso arquivo de interface. Maiores
detalhes sobre o funcionamento e tipos de políticas de chamadas podem ser encontrados
no tutorial da Boost.Python5.3. Testando
De modo análogo aos testes realizados com SWIG no capítulo anterior, criamos
um cript para realizar testes com a bibliteca criada utilizando Boost.Python. O código fonte
dos testes e o resultado destes estão nos Anexos 9.8 e 9.9, respectivamente.
TODO:
Resolver alguns pepinos com os testes usando Pyste + Boost.Python:
- Acessar char* como string nativa
- Acessar C array como lista nativa
- Passar valores para funções C++ que pedem ponteiros pra tipos básicos tipo int* e int&
- O Pyste se perde se deixarmos os includes da std em some_lib.h
- Operator overload nao ta rolando [TODO – levantar mais dados]
- Passar funcao de callback definida em C++ nao ta rolando
Ver se tem como fazer isso:
- Exportar #defines tipo o nosso RED
- Levantar exceção se o cara tentar escrever em variável global somente-leitura
6. Apocalypse
“No matter how beautiful, no matter
how cool your interface, it would be
better if there were less of it."
Alan Cooper6.1. Comparando estratégias num projeto real
Apocalypse é um jogo que vem sendo desenvolvido pelo autor deste trabalho, e
está cerca de 70% concluído. Para comparar a utilização prática das duas soluções
discutidas anteriormente, iremos utilizar o Apocalypse como plataforma de testes,
exportando partes da base de código utilizada atualmente para uma linguagem de autonível, permitindo que parte dos 30% restantes, e possíveis modificações futuras, sejam
implemendadas com maior rapidez utilizando uma linguagem de alto-nível.
Durante todo o desenvolvimento do jogo, uma das partes que foi mais adiada foi a
criação das diversas GUIs que compõe o jogo – apesar de a interface principal do jogo ter
sido criada, as interfaces que permeiam o jogo e que também são essenciais para o seu
funcionamento completo ainda não foram criadas, como: menu principal, menu de
opções, menu de criação de jogo, entre outros. Isso se deve ao fato de que a API de
rotinas de baixo nível utilizada (Allegro), é pouco ergonômica para a criação de GUIs, e
também ao fato de que ao criar uma interface em C++, sem a disponibilidade de um editor
WYSIWYG, ficamos presos ao ciclo codificar-compilar-testar, que é extremamente penoso
para a contrução de interfaces onde é preciso modificar o código inúmeras vezes para
verificar os resultados de pequenas modificações nas estruturas da interface.
Exportando uma interface que permita a criação das interfaces em Python,
podemos cortar a compilação do ciclo de cada iteração de teste, além de poder adaptar
as estruturas feias providas pela Allegro a um ambiente de construção de interfaces mais
limpo e rápido.
Começaremos definindo uma interface em C++ para o nosso sistema de interface a
ser exportada, e implementando em C++ a lógica que conversa com a Allegro. Depois de
implementada, essa interface será exportada para Python, utilizando tanto o SWIG quanto
o Pyste + Boost.Python. A solução que se mostrar mais apta para resolver o nosso
problema será mantida, e por fim implementaremos algumas das interfaces do
Apocalypse em Python, comparando o tempo de desenvolvimento e as dificultades
encontradas para a sua construção com os seus equivalentes para as interfaces que já
foram construídas anteriormente em C++.6.2. SWIG
blah blah
6.3. Pyste
blah blah
7. Conclusão
“When the solution is simple,
God is anwering”
Albert Eintein7. Conclusão
blah blah
7.1. SWIG x Pyste+Boost.Python
blah blah
7.2. Apocalypse – Post Mortem
blah blah
7.3. Botando as cartas na mesa
blah blah
8. Referências Bibliográficas
“In the face of ambiguity,
refuse the temptation to guess.”
Tim Peters (The Zen of Python)
8. Referências Bibliográficas
[1] A Conversation with Bruce Eckel. Disponível em:
<http://www.artima.com/intv/aboutme.html> (Part I). Acesso em: 21/11/04.
<http://www.artima.com/intv/prodperf.html> (Part II). Acesso em: 21/11/04.
[2] SWIG, Presentations, and Papers. Disponível em:
<http://www.swig.org/doc.html>. Acesso em: 22/11/04
[3] Boost.Python. Disponível em:
<http://www.boost.org/libs/python/doc/>. Acesso em: 22/11/04
[4] Java Native Interface Tutorial. Disponível em:
<http://java.sun.com/docs/books/tutorial/native1.1/>. Acesso em: 24/04/05
[5] Pyste Documentation. Disponível em:
<http://www.boost.org/libs/python/pyste/>. Acesso em: 15/05/2005
[6] Boost.Python Tutorial. Disponível em:
<http://www.boost.org/libs/python/doc/tutorial/doc/html/index.html> Acesso em 15/05/2005
9. Anexos
“The programmer, like the poet, works only slightly
removed from pure thought-stuff. He builds his castles in
the air, from air, creating by exertion of the imagination.
Few media of creation are so flexible, so easy to polish
and rework, so readily capable of realizing grand
conceptual structures.”
Frederick P Brooks Jr
9.1. Código fonte: some_lib
9.1.1. some_lib.h
/*******************************************************************************
* Arquivo: some_lib.h
* Autor: Petrucio - [email protected]
*
* Interface de some_lib - biblioteca didatica para programacao hibrida
******************************************************************************/
#ifndef _SOMELIB_H_
#define _SOMELIB_H_
#include <vector>
#include <string>
#include <map>
using namespace std;
/***
* Basic Concepts
******************************************************************************/
// 1- Global variables:
extern int
base_int;
extern float base_float;
extern double base_double;
extern char
base_char;
extern char
base_string[2];
extern int
int_array[5];
// 2- Constants:
extern const int const_int;
#define RED
0xFF0000
#define GREEN 0x00FF00
#define BLUE 0x0000FF
enum size { small, medium, big };
extern size some_size;
// 3- Loose Functions:
int looseFunction();
// 4- Structs:
struct SomeStruct {
SomeStruct():
struct_member(4) {}
int struct_member;
};
/***
* Object Oriented Concepts
******************************************************************************/
// 5- Classes:
class SomeClass {
public:
SomeClass();
// Functions:
float getFoo();
void setFoo(float foo);
// Instance Variables:
float foo;
// Class Members:
static int getBar();
// Private members:
private:
static int bar;
int getPrivate();
int private_member;
};
// 6- Inheritance
class OtherClass: public SomeClass {
public:
OtherClass() {}
float getFoo();
};
// Pure virtual methods (Abstract classes)
class AbstractClass {
public:
virtual float getFloat() = 0;
};
// 7- Virtual methods (Polimorphism)
class RealClassCpp: public AbstractClass {
public:
RealClassCpp() {}
virtual float getFloat();
};
// Calling target code from C++ throught Polimorphism:
float callGetFloat(AbstractClass &abstractClassInstance);
/***
* Advanced Concepts
******************************************************************************/
class Callable {
public:
Callable() {}
virtual float call(float parm) { return 0; }
};
class AdvancedClass {
public:
AdvancedClass (float value);
void setValue(float value);
float getValue();
// 8- Reference types:
static int resetInt_Value
(int a);
static int resetInt_Pointer (int *a);
static int resetInt_Reference(int &a);
// 9- Default arguments:
static void print_(const char *string = "
Hello World");
// Multiple arguments:
static double average(double a, ...);
// 10- Callbacks:
void setCallback(float (*callback)(float));
float callCallback();
void setPolyCallback(Callable *callbackInstance);
float callPolyCallback();
// 11- Operator Overloading:
AdvancedClass * operator + (AdvancedClass other);
private:
float value;
float (*callback)(float);
Callable *callbackInstance;
};
// 12- Templates:
template <class T>
class TemplatedClass {
public:
TemplatedClass() {}
void setValue(T value) { this->some_value = value; }
T getValue() { return this->some_value; }
private:
T some_value;
};
// Multiple inheritance:
class MultipleClass: public SomeClass, public RealClassCpp {
public:
MultipleClass() {}
};
// Std functions
class StdClass {
public:
StdClass() {
float_vector.reserve(10);
}
// Using std::vectors, strings, and dictionaries
vector<float>
float_vector;
map
<float, string> float2str_map;
// Hack pra adicionar valores em java
void addFloat(float value) {
float_vector.push_back(value);
}
};
#endif
9.1.2. some_lib.cpp
/*******************************************************************************
* Arquivo: some_lib.cpp
* Autor: Petrucio - [email protected]
*
* some_lib - biblioteca didatica para programacao hibrida
******************************************************************************/
#include "../include/some_lib.h"
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
/***
* Base types
******************************************************************************/
int
base_int
= 1;
float
base_float
= 1.0f;
double base_double
= 1.0;
char
base_char
= '1';
char
base_string[2] = "1";
int
int_array[5]
= { 1, 2, 3, 4, 5 };
const int const_int = 2;
size some_size = big;
int looseFunction() {
return 3;
}
/***
* SomeClass
******************************************************************************/
SomeClass::SomeClass():
foo(5),
private_member(5) {
}
float SomeClass::getFoo() {
return this->foo;
}
void SomeClass::setFoo(float foo) {
this->foo = foo;
}
int SomeClass::getBar() {
return SomeClass::bar;
}
int SomeClass::bar = 5;
int SomeClass::getPrivate() {
return this->private_member;
}
/***
* OtherClass
******************************************************************************/
float OtherClass::getFoo() {
return this->foo + 1;
}
/***
* Virtuals:
******************************************************************************/
float RealClassCpp::getFloat() {
return 7.0f;
}
float callGetFloat(AbstractClass &abstractClassInstance) {
return abstractClassInstance.getFloat();
}
/***
* AdvancedClass
******************************************************************************/
AdvancedClass::AdvancedClass(float p_value):
value(p_value),
callback(NULL),
callbackInstance(NULL) {
}
void AdvancedClass::setValue(float value) {
this->value = value;
}
float AdvancedClass::getValue() {
return this->value;
}
// ResetInts
int AdvancedClass::resetInt_Value(int a) {
a = 0;
return 8;
}
int AdvancedClass::resetInt_Pointer(int *a) {
*a = 0;
return 8;
}
int AdvancedClass::resetInt_Reference(int &a) {
a = 0;
return 8;
}
// Arguments
void AdvancedClass::print_(const char *string) {
printf("%s\n", string);
fflush(0);
}
double AdvancedClass::average(double a, ...) {
int count = 0;
double sum = 0, i = a;
va_list
args;
va_start(args, a);
while(i != -1.0) {
sum += i;
count++;
i = va_arg(args, double);
}
va_end(args);
return(sum ? (sum / count) : 0);
}
// Callbacks
void AdvancedClass::setCallback(float (*callback)(float)) {
this->callback = callback;
}
float AdvancedClass::callCallback() {
return this->callback(this->value);
}
void AdvancedClass::setPolyCallback(Callable *callbackInstance) {
this->callbackInstance = callbackInstance;
}
float AdvancedClass::callPolyCallback() {
return this->callbackInstance->call(this->value);
}
// Operator
AdvancedClass* AdvancedClass::operator+ (AdvancedClass other) {
return new AdvancedClass(this->getValue() + other.getValue());
}
9.2. Interface SWIG para some_lib
9.2.1. some_lib_swig_interface.i
// Some_lib common interface file
%{
// Implementing a callback
float increment_callback(float value) {
return value + 0.1f;
}
// Needed headers:
#include <some_lib.h>
%}
// Enable all Cross language polimorphysm:
%feature("director");
// Setting the callback as a function pointer in target language:
%constant float increment_callback(float);
// Using c arrays as arrays in the target language:
%include "carrays.i"
%array_class(int, INT_ARRAY);
// Passing base pointers as input or output:
%include "typemaps.i"
%apply int *INOUT { int *a };
%apply int &INOUT { int &a };
// Create 'instances' of variable arguments functions:
%extend AdvancedClass {
static double wrapped_average(double a)
{ return AdvancedClass::average(a, -1.0); }
static double wrapped_average(double a, double b)
{ return AdvancedClass::average(a, b, -1.0); }
static double wrapped_average(double a, double b, double c)
{ return AdvancedClass::average(a, b, c, -1.0); }
static double wrapped_average(double a, double b, double c, double d)
{ return AdvancedClass::average(a, b, c, d, -1.0); }
};
// Exposing std::strings, vectors and maps:
%include "std_vector.i"
%include "std_map.i"
%include "std_string.i"
// Create 'instances' of used std::vectors and maps
%template(FloatVector) std::vector<float>;
%template(Float2StrMap) std::map<float, string>;
// Exposed headers:
%include <some_lib.h>
// Export template 'instances'
%template(TemplatedClass_int) TemplatedClass<int>;
9.2.2. some_lib_swig_interface_python.i
// Some_lib interface file - python specifics:
%module(directors="1") some_lib_python
%{
%}
%include "some_lib_swig_interface.i"
9.2.3. some_lib_swig_interface_java.i
// Some_lib interface file - java specifics:
%module(directors="1") some_lib_java
%{
// Wrapping multiple inheritance with an aggreate:
#include <some_lib.h>
class WrappedMultipleClass: public MultipleClass {
public: RealClassCpp aggregate;
};
%}
// seeing arrays transparently:
%include "arrays_java.i"
// To avoid getter and setter name clashes:
%rename foo foo_attr;
// To be able to use operators in java:
%rename(add) AdvancedClass::operator+;
%include "some_lib_swig_interface.i"
// Exporting our multiple inheritance wrapper
class WrappedMultipleClass: public MultipleClass {
public: RealClassCpp aggregate;
};
9.2.4. swig_run.sh
SOME_LIB_DIR=C:/Projetos/Relatorio/some_lib
INCLUDE_DIRS=${SOME_LIB_DIR}/include
PYTHON_INTERFACE_FILE=some_lib_swig_interface_python.i
JAVA_INTERFACE_FILE=some_lib_swig_interface_java.i
PYTHON_OUTPUT_FILE=some_lib_python_wrappers.cpp
PYTHON_OUTPUT_DIR=some_lib_python_wrappers
PYTHON_TEST_DIR=python_tests
JAVA_OUTPUT_FILE=some_lib_java_wrappers.cpp
JAVA_OUTPUT_DIR=some_lib_java_wrappers
JAVA_TEST_DIR=java_tests
JAVA_PACKAGE=some_package
EXCLUDE_WARNS=-w514,813
echo "Creating python wrapper code"
swig -python -c++ ${EXCLUDE_WARNS} -o ${PYTHON_OUTPUT_FILE} -I${INCLUDE_DIRS}
${PYTHON_INTERFACE_FILE}
mv *.cpp *.h ${PYTHON_OUTPUT_DIR}
mv *.py ${PYTHON_TEST_DIR}
echo "Creating java
swig -java
wrapper code"
-c++ ${EXCLUDE_WARNS} -package ${JAVA_PACKAGE} -o
${JAVA_OUTPUT_FILE} -I${INCLUDE_DIRS} ${JAVA_INTERFACE_FILE}
mv *.cpp
${JAVA_OUTPUT_DIR}
rm ${JAVA_TEST_DIR}/${JAVA_PACKAGE}/*.java
${JAVA_TEST_DIR}/${JAVA_PACKAGE}/*.class -rf
mv *.java ${JAVA_TEST_DIR}/${JAVA_PACKAGE}
echo "Done"
9.3. Código fonte: testes de some_lib em python
9.3.1. some_lib_python_tests.py
#*******************************************************************************
# File: some_lib_python_tests.py
# Testa em python as funcionalidades de some_lib exportadas pelo SWIG
# Ver arquivo de interface some_lib_swig_interface.i
#*******************************************************************************
import some_lib_python
#*******************************************************************************
# Imprime todos os membros de root, comecando com identacao ident, percorrendo
# a arvore ateh o nivel indicado por sub_lvel.
# Ignora membros que iniciem por '_', ou terminem com 'Ptr'
# Os nos folha sao impressos na mesma linha
#*******************************************************************************
def print_tree(root, ident, sub_level):
if sub_level == 1:
str = ''
for entry in dir(root):
if entry.startswith('_') or entry.endswith('Ptr'):
str += entry + ', '
if len(str) > 0:
print' ' * ident + ' - ' + str
return
for entry in dir(root):
if entry.startswith('_') or entry.endswith('Ptr'):
print ' ' * ident, '+', entry
entry = getattr(root, entry)
print_tree(entry, ident + 2, sub_level - 1)
continue
continue
#*******************************************************************************
# Helper printing functions
#*******************************************************************************
def print_header(string):
print
print
'===============================================================================
='
print string
print '-------------------------------------------------------------------------------'
def print_footer():
print
'===============================================================================
='
def print_spearator():
print
print
'_______________________________________________________________________________
_'
print
#*******************************************************************************
# Imprime cabecalho e a arvore dos membros exportados de some_lib
#*******************************************************************************
def print_start():
print
print 'some_lib_swig_tests.py'
print 'Testa em python as funcionalidades de some_lib exportadas pelo SWIG'
print
print 'Na maioria dos casos, um teste eh considerado bem sucedido se o valor'
print 'impresso for igual ao numero do teste sendo realizado, as vezes com'
print 'incrementos sucessivos de 0.1 entre sub-testes'
print 'Outros, pela sua natureza, sao bem sucedidos quanto algum erro'
print 'acontece durante o teste, como tentar modificar uma constante'
print_header('Arvore dos membros exportados:')
print_tree(some_lib_python, 2, 2)
print_footer()
print
#*******************************************************************************
# Testa leitura e escrita em variaveis globais de some_lib, com tipos variados
# de dados
# Nota: Variaveis globais sao acessadas atraves de modulo.cvar
# Nota: Para acessar array, inclua carrays.i no arquivo de interface, e defina
# %array_class(TYPE, NAME), onde TYPE eh o tipo do array e NAME eh um nome
# qualquer a ser dado ao array. Crie um array em pytohn usando a funcao da
# classe NAME frompointer, como mostra o exemplo a seguir
#*******************************************************************************
def test_globals():
print_header('Teste 1: Testando leitura e escrita em variaveis C' +
' globais de diferentes tipos')
print 'Lendo variavel inteira
some_lib_python.cvar.base_int
print 'Lendo variavel ponto flutuante simples
some_lib_python.cvar.base_float
print 'Lendo variavel ponto flutuante double
some_lib_python.cvar.base_double
print 'Lendo variavel caracter
some_lib_python.cvar.base_char
print 'Lendo string (null terminated chars)
some_lib_python.cvar.base_string
base_int:
', \
base_float:
', \
base_double: ', \
base_char:
', \
base_string: ', \
array = some_lib_python.INT_ARRAY_frompointer(some_lib_python.cvar.int_array)
print 'Lendo array de inteiros
int_array:
', \
array[0], array[1], array[2], array[3], array[4]
print
print 'Alterando valor de base_float para 1.1'
some_lib_python.cvar.base_float = 1.1
print 'Lendo variavel ponto flutuante simples base_float:
some_lib_python.cvar.base_float
print_footer()
', \
#*******************************************************************************
# Testa leitura de #defines e enums, e acesso ilegal a constantes
#*******************************************************************************
def test_constants():
print_header('Teste 2: Testando leitura de #defines e enums,' +
' e acesso ilegal a constantes')
print 'Lendo enum size some_size:
', some_lib_python.cvar.some_size,'(big)'
print 'Lendo constante #define RED: ', hex(some_lib_python.RED)
print 'Testando escrita ilegal em constante const_int:'
try:
some_lib_python.cvar.const_int = 2
except TypeError, value:
print ' TypeError:', value
print_footer()
#*******************************************************************************
# Testa chamadas a funcoes
#*******************************************************************************
def test_functions():
print_header('Teste 3: Testando chamadas a funcoes C')
print 'Chamando funcao looseFunction: ', some_lib_python.looseFunction()
print_footer()
#*******************************************************************************
# Testa uso de structs
#*******************************************************************************
def test_structs():
print_header('Teste 4: Testando utilizacao de structs')
struct = some_lib_python.SomeStruct()
print 'Criando struct SomeStruct e acessando membro struct_member: ', \
struct.struct_member
print_footer()
#*******************************************************************************
# Testa uso de classes:
# Funcoes e atributos, de instancia e de classe, publicos e privados
#*******************************************************************************
def test_classes():
print_header('Teste 5: Testando utilizacao de classes: ' +
'\n\t Funcoes e atributos, de instancia e de classe, publicos e privados')
print 'Criando instancia de SomeClass - some_instance'
some_instance = some_lib_python.SomeClass()
print 'Chamando funcao
some_instance.getFoo():
', \
some_instance.getFoo()
print 'Alterando foo para 5.1 chamando some_instance.setFoo(5.1)'
some_instance.setFoo(5.1)
print 'Lendo atributo publico
some_instance.foo:
', \
some_instance.foo
print
print 'Chamando funcao de classe
SomeClass_getBar():
', \
some_lib_python.SomeClass_getBar()
try:
print 'Chamando funcao privada
some_instance.getPrivate():', \
some_instance.getPrivate()
except AttributeError, value:
print '\n AttributeError:', value
print_footer()
#*******************************************************************************
# Testa conceitos basicos de heranca
#*******************************************************************************
def test_inheritance():
print_header('Teste 6: Testando conceitos basicos de heranca')
print 'Criando instancia de OtherClass (subclasse de SomeClass)'
other_instance = some_lib_python.OtherClass()
print 'Lendo antiga variavel nao modificada other_instance.foo:
', \
other_instance.foo
print 'Chamando funcao reimplementada
other_instance.getFoo():', \
other_instance.getFoo()
print_footer()
#*******************************************************************************
# Testa classes abstratas e polimorfismo
# Nota: Atravez do polimorfismo pode-se chamar codigo python a partir de C++,
# como eh feito neste exemplo
#*******************************************************************************
def test_polimorphysm():
print_header('Teste 7: Testando classes abstratas e polimorfismo')
print 'Criando instancia de classe abstrata AbstractClass:'
try:
abstract_instance = some_lib_python.AbstractClass()
except RuntimeError, value:
print ' RuntimeError:', value
print
print 'Definindo RealClassPython (subclasse de RealClassCpp) em python'
class RealClassPython(some_lib_python.RealClassCpp):
def __init__(self):
some_lib_python.RealClassCpp.__init__(self)
def getFloat(self):
return some_lib_python.RealClassCpp.getFloat(self) + 0.1
print 'Criando instancias de subclasses RealClassCpp e RealClassPython'
real_instance_cpp
= some_lib_python.RealClassCpp()
real_instance_python = RealClassPython()
print 'callGetFloat => funcao em C++,',\
'chama getFloat de uma instancia de AbstractClass'
print
print 'Chamando callGetFloat(real_instance_cpp):
', \
some_lib_python.callGetFloat(real_instance_cpp)
print 'Chamando callGetFloat(real_instance_python):', \
some_lib_python.callGetFloat(real_instance_python)
print_footer()
#*******************************************************************************
# Testa diferentes tipos de referencia
# NOTA: Em python nao existe o conceito de argumentos como saida, portanto
# variaveis que tenham sido definidas como saida no arquivo de inteface (como
# neste caso), terao seus valores adicionados a lista de retorno.
# NOTA: Tipos basicos adicionam complexidade em se tratando se referencias ou
# ponteiros. Ja uma instancia de uma classe poderia ser passada para C++, tanto
# para funcoes pedindo parametros por valor, como por referencia ou ponteiro.
#*******************************************************************************
def test_references():
print_header('Teste 8: Testando diferentes tipos de referencia' +
'\n\t (por valor, ponteiro ou referencia)')
print 'Criando variavel inteira com valor 8'
int_var = 8
print 'Chamando funcao que tenta zerar variavel por valor'
print 'Retorno da chamada de AdvancedClass_resetInt_Value(int_var):
some_lib_python.AdvancedClass_resetInt_Value(int_var)
print
print 'Criando variavel inteira com valor 8'
int_var = 8
print 'Chamando funcao que tenta zerar variavel por ponteiro'
print 'Retorno da chamada de AdvancedClass_resetInt_Pointer(int_var):
some_lib_python.AdvancedClass_resetInt_Pointer(int_var)
', \
', \
print
print 'Criando variavel inteira com valor 8'
int_var = 8
print 'Chamando funcao que tenta zerar variavel por referencia'
print 'Retorno da chamada de AdvancedClass_resetInt_Reference(int_var):', \
some_lib_python.AdvancedClass_resetInt_Reference(int_var)
print_footer()
#*******************************************************************************
# Testa argumentos padroes e argumentos multiplos
# NOTA: Python nao usa o conceito de argumentos variaveis porque usa tuplas,
# entao funcoes com varios argumentos deve ter 'instancias' com um
# numero pre-determinado de argumentos e tipos exportados, como no nosso exemplo
#*******************************************************************************
def test_arguments():
print_header('Teste 9: Testando argumentos padroes e argumentos multiplos')
print 'Chamando funcao print_ com argumento " Teste 9":'
some_lib_python.AdvancedClass_print_(" Teste 9")
print 'Chamando funcao print_ sem argumentos:'
some_lib_python.AdvancedClass_print_()
print
print 'Chamando wrapped_average(1, 2):
', \
some_lib_python.AdvancedClass_wrapped_average(1, 2)
print 'Chamando wrapped_average(1, 2, 3, 4): ', \
some_lib_python.AdvancedClass_wrapped_average(1, 2, 3, 4)
try:
print 'Chamando wrapped_average(1, 2, 3, 4, 5, 6) - nao exportada:', \
some_lib_python.AdvancedClass_wrapped_average(1, 2, 3, 4, 5, 6)
except TypeError, value:
print '\n TypeError:', value
print_footer()
#*******************************************************************************
# Testa o uso de callbacks
# NOTA: Callbacks podem ser feitos em C++, dentro da propria interface,
# ou pode-se utilizar Polimorfismo para chama-los em Python. Este exemplo mostra
# as duas alternativas
#*******************************************************************************
def test_callbacks():
print_header('Teste 10: Testando o uso de callbacks')
print 'Criando instancia de advanced class com valor 10'
advanced_instance = some_lib_python.AdvancedClass(10)
print 'Definindo callback como sendo funcao C++ definida no arquivo .i'
advanced_instance.setCallback(some_lib_python.increment_callback)
print 'Requisitando chamada aa funcao de callback:', \
advanced_instance.callCallback()
print
print 'Definindo classe PythonCallable (subclasse de Callable) em python'
class PythonCallable(some_lib_python.Callable):
def __init__(self):
some_lib_python.Callable.__init__(self)
def call(self, parm):
return parm + 0.2
print 'Definindo o callback polimorfico como sendo', \
'uma instancia de PythonCallable'
python_callback = PythonCallable()
advanced_instance.setPolyCallback(python_callback)
print 'Requisitando chamada aa funcao de callback polimorfico:', \
advanced_instance.callPolyCallback()
print_footer()
#*******************************************************************************
# Testa o uso de sobrecarga de operadores
#*******************************************************************************
def test_operators():
print_header('Teste 11: Testando o uso de sobrecarga de operadores')
print 'Criando uma instancia de AdvancedClass com valor 5 e outra com 6'
advanced_instance_5 = some_lib_python.AdvancedClass(5)
advanced_instance_6 = some_lib_python.AdvancedClass(6)
print 'advanced_result = advanced_instance_5 + advanced_instance_6'
advanced_result = advanced_instance_5 + advanced_instance_6
print 'Valor de advanced_result.getValue():', advanced_result.getValue()
print_footer()
#*******************************************************************************
# Testa o uso de templates
# NOTA: Como nao existe o conceito de templates em python, precisamos criar
# 'instancias' de templates, como neste exemplo
#*******************************************************************************
def test_templates():
print_header('Teste 12: Testando o uso de templates')
print 'Criando instancia de TemplatedClass<int>'
templated_instance = some_lib_python.TemplatedClass_int()
templated_instance.setValue(12)
print 'Definindo valor inteiro 12 e lendo:', templated_instance.getValue()
print 'Criando instancia de TemplatedClass<float> - nao exportada'
try:
templated_instance = some_lib_python.TemplatedClass_float()
except AttributeError, value:
print ' AttributeError:', value
print_footer()
#*******************************************************************************
# Testa heranca multipla
#*******************************************************************************
def test_multiple_inheritance():
print_header('Teste 13: Testando heranca multipla')
print 'Criando instancia de MultipleClass',\
' - subclasse de SomeClass e de RealClassCpp'
multiple_instance = some_lib_python.MultipleClass()
print 'Chamando funcao herdada de SomeClass
em 5:' + \
' multiple_instance.getFoo(): ', multiple_instance.getFoo()
print 'Chamando funcao herdada de RealClassCpp em 7:' + \
' multiple_instance.getFloat():', multiple_instance.getFloat()
print_footer()
#*******************************************************************************
# Testa uso de std::vectors e std::maps
# como listas e dicionarios nativos de python
#*******************************************************************************
def test_std():
print_header('Teste 14: Testando std::vectors e maps' +\
' como listas e mapas nativos de python')
print 'Criando instancia de StdClass' + \
' - contendo membros std::vector e std::map'
std_instance = some_lib_python.StdClass()
print
print 'Adicionando valores 14.0 e 14.1 ao std::vector de C++,', \
'com sintaxe python'
std_instance.float_vector.append(14.0)
std_instance.float_vector.append(14.1)
print 'Percorrendo vetor com sintaxe python:'
print ' for item in std_instance.float_vector: print item'
for item in std_instance.float_vector: print item
print
print 'Adicionando ao std::map com sintaxe python:'
print ' map[14.2] = "Hello 14.2"'
print ' map[14.3] = "Hello 14.3"'
std_instance.float2str_map[14.2] = 'Hello 14.2'
std_instance.float2str_map[14.3] = 'Hello 14.3'
print 'Imprimindo items() do mapa - como em um dicionario python:'
print std_instance.float2str_map.items()
print_footer()
#*******************************************************************************
# MAIN
#*******************************************************************************
print_start()
print_spearator()
print_header('Testando conceitos basicos de C estruturado:' +
'\nVariaveis globais, funcoes, structs, etc.')
test_globals()
test_constants()
test_functions()
test_structs()
print_spearator()
print_header('Testando conceitos basicos de C++ orientado a objetos:' +
'\nClasses, heranca, polimorfismo, etc.')
test_classes()
test_inheritance()
test_polimorphysm()
print_spearator()
print_header('Testando conceitos avancados:' +
'\nTemplates, heranca multipla, callbacks, operadores, etc...')
test_references()
test_arguments()
test_callbacks()
test_operators()
test_templates()
test_multiple_inheritance()
test_std()
9.4. Código fonte: testes de some_lib em java
9.4.1. some_lib_JavaTests.java
/*
*******************************************************************************
* File: some_lib_JavaTests.java
* Testa em java as funcionalidades de some_lib exportadas pelo SWIG
* Ver arquivo de interface some_lib_swig_interface.i
******************************************************************************/
import
import
import
import
import
import
import
import
import
some_package.AdvancedClass;
some_package.OtherClass;
some_package.RealClassCpp;
some_package.SomeClass;
some_package.SomeStruct;
some_package.StdClass;
some_package.TemplatedClass_int;
some_package.WrappedMultipleClass;
some_package.some_lib_java;
public class some_lib_JavaTests {
/*
****************************************************************************
* Helper printing functions
***************************************************************************/
private static void print(String string)
{ System.out.println(string); }
private static void print(String string, int aint)
{ System.out.println(string + aint); }
private static void print(String string, char achar)
{ System.out.println(string + achar); }
private static void print(String string, float afloat)
{ System.out.println(string + afloat); }
private static void print(String string, double adouble)
{ System.out.println(string + adouble); }
private static void print(String string, String str2)
{ System.out.println(string + str2); }
private static void printHeader(String string) {
print("");
print("=========================================================================
=======");
print(string);
print("-------------------------------------------------------------------------------");
}
private static void printFooter() {
print("=========================================================================
=======");
}
private static void printSeparator() {
print("");
print("_________________________________________________________________________
_______");
print("");
}
/*
****************************************************************************
* Imprime cabecalho e a arvore dos membros exportados de some_lib
***************************************************************************/
public void printStart() {
print("");
print("some_lib_JavaTests.java");
print("Testa em java funcionalidades de some_lib exportadas pelo SWIG");
print("");
print("Na maioria dos casos, um teste eh considerado bem sucedido se o");
print("valor impresso for igual ao numero do teste sendo realizado,");
print("as vezes com incrementos sucessivos de 0.1 entre sub-testes");
print("Outros, pela sua natureza, sao bem sucedidos quanto algum erro");
print("acontece durante o teste, como tentar modificar uma constante");
printFooter();
}
/*
****************************************************************************
* MAIN
* Nota: Alguns erros que foram testados em python dinamicamente (como
* escrita em constante, tentativa de instanciacao de classe abstrata, entre
* outros) sao detectados em Java em compile-time, e nao foram testados aqui
* como foram em python.
***************************************************************************/
public static void main(String[] args) {
System.loadLibrary("some_lib_java");
some_lib_JavaTests tester = new some_lib_JavaTests();
tester.printStart();
printSeparator();
printHeader("Testando conceitos basicos de C estruturado:" +
"\nVariaveis globais, funcoes, structs, etc.");
tester.testGlobals();
tester.testConstants();
tester.testFunctions();
tester.testStructs();
printSeparator();
printHeader("Testando conceitos basicos de C++ orientado a objetos:" +
"\nClasses, heranca, polimorfismo, etc.");
tester.testClasses();
tester.testInheritance();
tester.testPolimorphysm();
printSeparator();
printHeader("Testando conceitos avancados:" +
"\nTemplates, heranca multipla, callbacks, operadores, etc.");
tester.testReferences();
tester.testArguments();
tester.testCallbacks();
tester.testOperators();
tester.testTemplates();
tester.testMultipleInheritance();
tester.testStd();
}
/*
****************************************************************************
* Testa leitura e escrita em variaveis globais de some_lib,
* com tipos variados
* Nota: Variaveis globais sao acessadas atraves de getters e setters,
* metodos de classe de uma classe wrapper com o nome do modulo exportado.
* Nota: Para acessar arrays em java de forma transparente, incluir
* arrays_java.i no arquivo de interface.
***************************************************************************/
private void testGlobals() {
printHeader("Teste 1: Testando leitura e escrita em variaveis C" +
" globais de diferentes tipos");
print("Lendo variavel inteira
some_lib_java.getBase_int());
print("Lendo variavel ponto flutuante simples
some_lib_java.getBase_float());
print("Lendo variavel ponto flutuante double
some_lib_java.getBase_double());
print("Lendo variavel caracter
some_lib_java.getBase_char());
print("Lendo string (null terminated chars)
some_lib_java.getBase_string());
base_int:
",
base_float:
",
base_double: ",
base_char:
",
base_string: ",
int[] intArray = some_lib_java.getInt_array();
System.out.println("Lendo array de inteiros
int_array:
intArray[0] + " " + intArray[1] + " " +
intArray[2] + " " + intArray[3] + " " + intArray[4]);
print("");
print("Alterando valor de base_float para 1.1");
some_lib_java.setBase_float(1.1f);
print("Lendo variavel ponto flutuante simples base_float:
some_lib_java.getBase_float());
"+
",
printFooter();
}
/*
****************************************************************************
* Testa leitura de #defines e enums
***************************************************************************/
private void testConstants() {
printHeader("Teste 2: Testando leitura de #defines e enums," +
" e acesso ilegal a constantes");
System.out.println("Lendo enum size some_size:
" +
some_lib_java.getSome_size()
+ " (big)");
print("Lendo constante #define RED: " +
Integer.toHexString(some_lib_java.RED));
printFooter();
}
/*
****************************************************************************
* Testa chamadas a funcoes
***************************************************************************/
private void testFunctions() {
printHeader("Teste 3: Testando chamadas a funcoes C");
print("Chamando funcao looseFunction: ", some_lib_java.looseFunction());
printFooter();
}
/*
****************************************************************************
* Testa uso de structs
***************************************************************************/
private void testStructs() {
printHeader("Teste 4: Testando utilizacao de structs");
SomeStruct struct = new SomeStruct();
print("Criando struct SomeStruct e acessando membro struct_member: ",
struct.getStruct_member());
printFooter();
}
/*
****************************************************************************
* Testa uso de classes
* Nota: Ao gerar wrappers para Java, o SWIG sempre cria getters e setters
* para os attributos ao invez de deixa-los publicos, seguindo o padrao Beans
* Por isso o acesso direto ao atributo foo nao eh feito neste teste como
* foi feito em python.
***************************************************************************/
private void testClasses() {
printHeader("Teste 5: Testando utilizacao de classes");
print("Criando instancia de SomeClass - some_instance");
SomeClass someInstance = new SomeClass();
print("Chamando funcao
some_instance.getFoo():
someInstance.getFoo());
print("Alterando foo para 5.1 chamando some_instance.setFoo(5.1):
someInstance.setFoo(5.1f);
print("Lendo atributo publico
some_instance.getFoo_attr():
someInstance.getFoo());
print("");
print("Chamando funcao de classe
SomeClass.getBar():
SomeClass.getBar());
printFooter();
}
/*
",
");
",
",
****************************************************************************
* Testa conceitos basicos de heranca
***************************************************************************/
private void testInheritance() {
printHeader("Teste 6: Testando conceitos basicos de heranca");
print("Criando instancia de OtherClass (subclasse de SomeClass)");
OtherClass otherInstance = new OtherClass();
print("Lendo antiga variavel nao modificada otherInstance.getFoo_attr():",
otherInstance.getFoo_attr());
print("Chamando funcao reimplementada
otherInstance.getFoo():
",
otherInstance.getFoo());
printFooter();
}
/*
****************************************************************************
* Testa polimorfismo
* Nota: Atravez do polimorfismo pode-se chamar codigo Java a partir de C++,
* como eh feito neste exemplo
***************************************************************************/
private void testPolimorphysm() {
printHeader("Teste 7: Testando classes abstratas e polimorfismo");
// NOTA: A criacao de instancia de classe abstrata nao gerou erro em Java,
// no entanto tentar acessar algum metodo dessa instancia gerou um acesso
// ilegal e matou o programa.
print("Criando instancia de classe abstrata AbstractClass:");
AbstractClass abstractInstance = new AbstractClass();
print("float: ", abstractInstance.getFloat());
//
//
//
print("");
print("Criando instancias de subclasses RealClassCpp e RealClassJava");
RealClassCpp realInstanceCpp = new RealClassCpp();
RealClassJava realInstanceJava = new RealClassJava();
print("callGetFloat => funcao em C++," +
" chama getFloat de uma instancia de AbstractClass");
print("");
print("Chamando callGetFloat(realInstanceCpp): ",
some_lib_java.callGetFloat(realInstanceCpp));
// TODO: not working:
print("Chamando callGetFloat(realInstanceJava): ",
some_lib_java.callGetFloat(realInstanceJava));
printFooter();
}
/*
****************************************************************************
* Testa diferentes tipos de referencia
* NOTA: Tipos basicos adicionam complexidade em se tratando se referencias
* ou ponteiros. Ja uma instancia de uma classe poderia ser passada para C++,
* tanto para funcoes pedindo parametros por valor,
* como por referencia ou ponteiro.
* NOTA: Funcoes em C++ com argumentos referencia ou ponteiro para tipo
* basico esperam receber um array, como fazemos neste exemplo.
***************************************************************************/
private void testReferences() {
printHeader("Teste 8: Testando diferentes tipos de referencia" +
"\n\t (por valor, ponteiro ou referencia)");
print("Criando um array de apenas um inteiro com valor 8");
int [] intArray = new int[1];
intArray[0] = 8;
print("Chamando funcao que tenta zerar variavel por valor");
AdvancedClass.resetInt_Value(intArray[0]);
print("Valor de intArray[0]: ", intArray[0]);
print("");
print("Criando um array de apenas um inteiro com valor 8");
intArray[0] = 8;
print("Chamando funcao que tenta zerar variavel por ponteiro");
AdvancedClass.resetInt_Pointer(intArray);
print("Valor de intArray[0]: ", intArray[0]);
print("");
print("Criando um array de apenas um inteiro com valor 8");
intArray[0] = 8;
print("Chamando funcao que tenta zerar variavel por referencia");
AdvancedClass.resetInt_Reference(intArray);
print("Valor de intArray[0]: ", intArray[0]);
printFooter();
}
/*
****************************************************************************
* Testa argumentos padroes e argumentos multiplos
* NOTA: Java nao usa o conceito de argumentos variaveis,
* entao funcoes com varios argumentos deve ter 'instancias' com um
* numero pre-determinado de argumentos e tipos exportados,
* como no nosso exemplo.
***************************************************************************/
private void testArguments() {
printHeader("Teste 9: Testando argumentos padrao e argumentos multiplos");
print("Chamando funcao print_ com argumento \" Teste 9\":");
AdvancedClass.print_(" Teste 9");
print("Funcao print_ sem argumentos indisponivel em java");
print("");
print("Chamando wrapped_average(1, 2):
",
AdvancedClass.wrapped_average(1, 2));
print("Chamando wrapped_average(1, 2, 3, 4): ",
AdvancedClass.wrapped_average(1, 2, 3, 4));
print("Funcao wrapped_average(1, 2, 3, 4, 5, 6) nao disponivel em java");
printFooter();
}
/*
****************************************************************************
* Testa o uso de callbacks
* NOTA: Callbacks podem ser feitos em C++, dentro da propria interface,
* ou pode-se utilizar Polimorfismo para chama-los em Java.. Este exemplo
* mostra as duas alternativas
***************************************************************************/
private void testCallbacks() {
printHeader("Teste 10: Testando o uso de callbacks");
print("Criando instancia de advanced class com valor 10");
AdvancedClass advancedInstance = new AdvancedClass(10);
print("Definindo callback como sendo funcao C++ definida no arquivo .i");
advancedInstance.setCallback(some_lib_java.increment_callback);
print("Requisitando chamada aa funcao de callback:
",
advancedInstance.callCallback());
print("");
print("Definindo o callback polimorfico como sendo" +
" uma instancia de JavaCallable");
JavaCallable javaCallback = new JavaCallable();
advancedInstance.setPolyCallback(javaCallback);
// TODO: This is not working
print("Requisitando chamada aa funcao de callback polimorfico: ",
advancedInstance.callPolyCallback());
printFooter();
}
/*
****************************************************************************
* Testa o uso de sobrecarga de operadores
***************************************************************************/
private void testOperators() {
printHeader("Teste 11: Testando o uso de sobrecarga de operadores");
print("Criando uma instancia de AdvancedClass com valor 5 e outra com 6");
AdvancedClass advancedInstance_5 = new AdvancedClass(5);
AdvancedClass advancedInstance_6 = new AdvancedClass(6);
print("advancedResult = advanced_instance_5 + advanced_instance_6" +
" (using renamed add)");
AdvancedClass advancedResult = advancedInstance_5.add(advancedInstance_6);
print("Valor de advancedResult.getValue(): ", advancedResult.getValue());
printFooter();
}
/*
****************************************************************************
* Testa o uso de templates
* NOTA: Como nao existe o conceito de templates em java, precisamos criar
* 'instancias' de templates, como neste exemplo
***************************************************************************/
private void testTemplates() {
printHeader("Teste 13: Testando o uso de templates");
print("Criando instancia de TemplatedClass<int>");
TemplatedClass_int templatedInstance = new TemplatedClass_int();
templatedInstance.setValue(13);
print("Definindo valor inteiro 13 e lendo: ",
templatedInstance.getValue());
print("Classe TemplatedClass<float> nao disponivel em java");
printFooter();
}
/*
****************************************************************************
* Testa heranca multipla
***************************************************************************/
private void testMultipleInheritance() {
printHeader("Teste 14: Testando heranca multipla");
print("Criando instancia de MultipleClass" +
" - subclasse de SomeClass e de RealClassCpp");
WrappedMultipleClass multipleInstance = new WrappedMultipleClass();
print("Chamando funcao herdada de SomeClass
em 5:" +
"\n multipleInstance.getFoo():
",
multipleInstance.getFoo());
print("Chamando funcao agregada de RealClassCpp em 7:" +
"\n multipleInstance.getAggregate().getFloat(): ",
multipleInstance.getAggregate().getFloat());
printFooter();
}
/*
****************************************************************************
* Testa uso de std::vectors e std::maps como listas
* e dicionarios nativos de java
* NOTA: A funcao add do vetor aparentemente nao estava alocando espaço,
* causando a funcao set(index, value) a gerar IndexOutOfRange. Foi feito
* um 'hack' em some_lib adicionando uma funcao em C++ addFloat(float)
* para resolver esse problema
***************************************************************************/
private void testStd() {
printHeader("Teste 15: Testando uso std::vectors e maps em java");
print("Criando instancia de StdClass" +
" - contendo membros std::vector e std::map");
StdClass stdInstance = new StdClass();
print("");
print("Adicionando valores 15.0 e 15.1 ao std::vector de C++," +
" usando 'hack' addFloat()");
stdInstance.addFloat(15.1f);
stdInstance.addFloat(15.2f);
print("Percorrendo vetor com size() e get():");
for (int i = 0; i < stdInstance.getFloat_vector().size(); i++) {
System.out.println(stdInstance.getFloat_vector().get(i));
}
print("");
print("Adicionando 15.2->\"Hello 15.2\" e
" usando set()");
stdInstance.getFloat2str_map().set(15.2f,
stdInstance.getFloat2str_map().set(15.3f,
print("Imprimindo os items do mapa usando
15.3->\"Hello 15.3\"," +
"Hello 15.2");
"Hello 15.3");
get()");
// TODO: std::strings nao estao sendo traduzidas corretamente (Oh, the pain!)
System.out.println(stdInstance.getFloat2str_map().get(15.2f));
System.out.println(stdInstance.getFloat2str_map().get(15.3f));
printFooter();
}
}
9.4.2. RealClassJava.java
import some_package.RealClassCpp;
/**
* SubClasse de RealClassCpp implementando metodo polimorfico a ser chamado
* de C++
*/
public class RealClassJava extends RealClassCpp {
public float getFloat() {
return ((RealClassCpp)this).getFloat() + 0.1f;
}
}
9.4.2. JavaCallable.java
import some_package.Callable;
/**
* SubClasse de Callable implementando classe polimorfica com metodo de callback
* a ser chamado a partir do C++
*/
public class JavaCallable extends Callable {
public float call(float parm) {
return parm + 0.2f;
}
}
9.5. Resultado dos testes de some_lib em python
some_lib_swig_tests.py
Testa em python as funcionalidades de some_lib exportadas pelo SWIG
Na maioria dos casos, um teste eh considerado bem sucedido se o valor
impresso for igual ao numero do teste sendo realizado, as vezes com
incrementos sucessivos de 0.1 entre sub-testes
Outros, pela sua natureza, sao bem sucedidos quanto algum erro
acontece durante o teste, como tentar modificar uma constante
================================================================================
Arvore dos membros exportados:
-------------------------------------------------------------------------------+ AbstractClass
- getFloat,
+ AdvancedClass
- average, callCallback, callPolyCallback, getValue, print_,
resetInt_Pointer, resetInt_Reference, resetInt_Value,
setCallback, setPolyCallback, setValue, throwException, wrapped_average,
+ AdvancedClass_average
+ AdvancedClass_print_
+ AdvancedClass_resetInt_Pointer
+ AdvancedClass_resetInt_Reference
+ AdvancedClass_resetInt_Value
+ AdvancedClass_wrapped_average
+ BLUE
+ Callable
- call,
+ Float2StrMap
- clear, has_key, items, keys, values,
+ FloatVector
- append, clear, pop,
+ GREEN
+ INT_ARRAY
- cast, frompointer,
+ INT_ARRAY_frompointer
+ MultipleClass
- foo, getBar, getFloat, getFoo, setFoo,
+ OtherClass
- foo, getBar, getFoo, setFoo,
+ RED
+ RealClassCpp
- getFloat,
+ SomeClass
- foo, getBar, getFoo, setFoo,
+ SomeClass_getBar
+ SomeException
- str,
+ SomeStruct
- struct_member,
+ StdClass
- float2str_map, float_vector,
+ TemplatedClass_int
- getValue, setValue,
+ big
+ callGetFloat
+ const_int
+ cvar
+ increment_callback
- capitalize, center, count, decode, encode, endswith, expandtabs, find,
index, isalnum, isalpha, isdigit, islower,
isspace, istitle, isupper, join, ljust, lower, lstrip, replace, rfind, rindex,
rjust, rsplit, rstrip, split, splitlines
, startswith, strip, swapcase, title, translate, upper, zfill,
+ looseFunction
+ medium
+ small
+ types
- BooleanType, BufferType, BuiltinFunctionType, BuiltinMethodType,
ClassType, CodeType, ComplexType, DictProxyType,
DictType, DictionaryType, EllipsisType, FileType, FloatType, FrameType,
FunctionType, GeneratorType, InstanceType, IntT
ype, LambdaType, ListType, LongType, MethodType, ModuleType, NoneType,
NotImplementedType, ObjectType, SliceType, String
Type, StringTypes, TracebackType, TupleType, TypeType, UnboundMethodType,
UnicodeType, XRangeType,
+ weakref_proxy
================================================================================
________________________________________________________________________________
================================================================================
Testando conceitos basicos de C estruturado:
Variaveis globais, funcoes, structs, etc.
-------------------------------------------------------------------------------================================================================================
Teste 1: Testando leitura e escrita em variaveis C globais de diferentes tipos
--------------------------------------------------------------------------------
Lendo
Lendo
Lendo
Lendo
Lendo
Lendo
variavel inteira
variavel ponto flutuante simples
variavel ponto flutuante double
variavel caracter
string (null terminated chars)
array de inteiros
base_int:
base_float:
base_double:
base_char:
base_string:
int_array:
1
1.0
1.0
1
1
1 2 3 4 5
Alterando valor de base_float para 1.1
Lendo variavel ponto flutuante simples base_float:
1.10000002384
================================================================================
================================================================================
Teste 2: Testando leitura de #defines e enums, e acesso ilegal a constantes
-------------------------------------------------------------------------------Lendo enum size some_size:
2 (big)
Lendo constante #define RED: 0xff0000
Testando escrita ilegal em constante const_int:
TypeError: Variable const_int is read-only.
================================================================================
================================================================================
Teste 3: Testando chamadas a funcoes C
-------------------------------------------------------------------------------Chamando funcao looseFunction: 3
================================================================================
================================================================================
Teste 4: Testando utilizacao de structs
-------------------------------------------------------------------------------Criando struct SomeStruct e acessando membro struct_member: 4
================================================================================
________________________________________________________________________________
================================================================================
Testando conceitos basicos de C++ orientado a objetos:
Classes, heranca, polimorfismo, etc.
-------------------------------------------------------------------------------================================================================================
Teste 5: Testando utilizacao de classes:
Funcoes e atributos, de instancia e de classe, publicos e privados
-------------------------------------------------------------------------------Criando instancia de SomeClass - some_instance
Chamando funcao
some_instance.getFoo():
5.0
Alterando foo para 5.1 chamando some_instance.setFoo(5.1)
Lendo atributo publico
some_instance.foo:
5.09999990463
Chamando funcao de classe
SomeClass_getBar():
5
Chamando funcao privada
some_instance.getPrivate():
AttributeError: getPrivate
================================================================================
================================================================================
Teste 6: Testando conceitos basicos de heranca
-------------------------------------------------------------------------------Criando instancia de OtherClass (subclasse de SomeClass) - other_instance
Lendo antiga variavel nao modificada other_instance.foo:
5.0
Chamando funcao reimplementada
other_instance.getFoo(): 6.0
================================================================================
================================================================================
Teste 7: Testando classes abstratas e polimorfismo
-------------------------------------------------------------------------------Criando instancia de classe abstrata AbstractClass:
RuntimeError: No constructor defined
Definindo RealClassPython (subclasse de RealClassCpp) em python
Criando instancias de subclasses RealClassCpp e RealClassPython
callGetFloat => funcao em C++, chama getFloat de uma instancia de AbstractClass
Chamando callGetFloat(real_instance_cpp):
7.0
Chamando callGetFloat(real_instance_python): 7.09999990463
================================================================================
________________________________________________________________________________
================================================================================
Testando conceitos avancados:
Templates, heranca multipla, callbacks, operadores, etc...
-------------------------------------------------------------------------------================================================================================
Teste 8: Testando diferentes tipos de referencia
(por valor, ponteiro ou referencia)
-------------------------------------------------------------------------------Criando variavel inteira com valor 8
Chamando funcao que tenta zerar variavel por valor
Retorno da chamada de AdvancedClass_resetInt_Value(int_var):
8
Criando variavel inteira com valor 8
Chamando funcao que tenta zerar variavel por ponteiro
Retorno da chamada de AdvancedClass_resetInt_Pointer(int_var):
(8, 0)
Criando variavel inteira com valor 8
Chamando funcao que tenta zerar variavel por referencia
Retorno da chamada de AdvancedClass_resetInt_Reference(int_var): (8, 0)
================================================================================
================================================================================
Teste 9: Testando argumentos padroes e argumentos multiplos
-------------------------------------------------------------------------------Chamando funcao print_ com argumento " Teste 9":
Teste 9
Chamando funcao print_ sem argumentos:
Hello World
Chamando wrapped_average(1, 2):
1.5
Chamando wrapped_average(1, 2, 3, 4): 2.5
Chamando wrapped_average(1, 2, 3, 4, 5, 6) - nao exportada:
TypeError: No matching function for overloaded 'AdvancedClass_wrapped_average'
================================================================================
================================================================================
Teste 10: Testando o uso de callbacks
-------------------------------------------------------------------------------Criando instancia de advanced class com valor 10
Definindo callback como sendo funcao C++ definida no arquivo .i
Requisitando chamada aa funcao de callback: 10.1000003815
Definindo classe PythonCallable (subclasse de Callable) em python
Definindo o callback polimorfico como sendo uma instancia de PythonCallable
Requisitando chamada aa funcao de callback polimorfico: 10.1999998093
================================================================================
================================================================================
Teste 11: Testando o uso de sobrecarga de operadores
-------------------------------------------------------------------------------Criando uma instancia de AdvancedClass com valor 5 e outra com 6
advanced_result = advanced_instance_5 + advanced_instance_6
Valor de advanced_result.getValue(): 11.0
================================================================================
================================================================================
Teste 12: Testando o uso de excecoes
-------------------------------------------------------------------------------Forcando SomeException a ser jogada em C++, para trata-la em python:
StandardError catched: new_AdvancedClass() takes exactly 1 argument (0 given)
================================================================================
================================================================================
Teste 13: Testando o uso de templates
-------------------------------------------------------------------------------Criando instancia de TemplatedClass<int>
Definindo valor inteiro 13 e lendo: 13
Criando instancia de TemplatedClass<float> - nao exportada
AttributeError: 'module' object has no attribute 'TemplatedClass_float'
================================================================================
================================================================================
Teste 14: Testando heranca multipla
-------------------------------------------------------------------------------Criando instancia de MultipleClass - subclasse de SomeClass e de RealClassCpp
Chamando funcao herdada de SomeClass
em 5: multiple_instance.getFoo():
5.0
Chamando funcao herdada de RealClassCpp em 7: multiple_instance.getFloat(): 7.0
================================================================================
================================================================================
Teste 15: Testando std::vectors e maps como listas e mapas nativos de python
-------------------------------------------------------------------------------Criando instancia de StdClass - contendo membros std::vector e std::map
Adicionando valores 15.0 e 15.1 ao std::vector de C++, com sintaxe python
Percorrendo vetor com sintaxe python:
for item in std_instance.float_vector: print item
15.0
15.1000003815
Adicionando ao std::map com sintaxe python:
map[15.2] = "Hello 15.2"
map[15.3] = "Hello 15.3"
Imprimindo items() do mapa - como em um dicionario python:
[(15.199999809265137, 'Hello 15.2'), (15.300000190734863, 'Hello 15.3')]
================================================================================
9.6. Resultado dos testes de some_lib em java
some_lib_JavaTests.java
Testa em java funcionalidades de some_lib exportadas pelo SWIG
Na maioria dos casos, um teste eh considerado bem sucedido se o
valor impresso for igual ao numero do teste sendo realizado,
as vezes com incrementos sucessivos de 0.1 entre sub-testes
Outros, pela sua natureza, sao bem sucedidos quanto algum erro
acontece durante o teste, como tentar modificar uma constante
================================================================================
________________________________________________________________________________
================================================================================
Testando conceitos basicos de C estruturado:
Variaveis globais, funcoes, structs, etc.
-------------------------------------------------------------------------------================================================================================
Teste 1: Testando leitura e escrita em variaveis C globais de diferentes tipos
-------------------------------------------------------------------------------Lendo variavel inteira
base_int:
Lendo variavel ponto flutuante simples base_float:
1
1.0
Lendo variavel ponto flutuante double
base_double: 1.0
Lendo variavel caracter
base_char:
Lendo string (null terminated chars)
base_string: 1
Lendo array de inteiros
int_array:
1
1 2 3 4 5
Alterando valor de base_float para 1.1
Lendo variavel ponto flutuante simples base_float:
1.1
================================================================================
================================================================================
Teste 2: Testando leitura de #defines e enums, e acesso ilegal a constantes
-------------------------------------------------------------------------------Lendo enum size some_size:
2 (big)
Lendo constante #define RED: ff0000
================================================================================
================================================================================
Teste 3: Testando chamadas a funcoes C
-------------------------------------------------------------------------------Chamando funcao looseFunction: 3
================================================================================
================================================================================
Teste 4: Testando utilizacao de structs
-------------------------------------------------------------------------------Criando struct SomeStruct e acessando membro struct_member: 4
================================================================================
________________________________________________________________________________
================================================================================
Testando conceitos basicos de C++ orientado a objetos:
Classes, heranca, polimorfismo, etc.
-------------------------------------------------------------------------------================================================================================
Teste 5: Testando utilizacao de classes
-------------------------------------------------------------------------------Criando instancia de SomeClass - some_instance
Chamando funcao
some_instance.getFoo():
5.0
Alterando foo para 5.1 chamando some_instance.setFoo(5.1):
Lendo atributo publico
some_instance.getFoo_attr(): 5.1
Chamando funcao de classe
SomeClass.getBar():
5
================================================================================
================================================================================
Teste 6: Testando conceitos basicos de heranca
-------------------------------------------------------------------------------Criando instancia de OtherClass (subclasse de SomeClass)
Lendo antiga variavel nao modificada otherInstance.getFoo_attr():5.0
Chamando funcao reimplementada
otherInstance.getFoo():
6.0
================================================================================
================================================================================
Teste 7: Testando classes abstratas e polimorfismo
-------------------------------------------------------------------------------Criando instancias de subclasses RealClassCpp e RealClassJava
callGetFloat => funcao em C++, chama getFloat de uma instancia de AbstractClass
Chamando callGetFloat(realInstanceCpp):
7.0
Chamando callGetFloat(realInstanceJava): 7.0
================================================================================
________________________________________________________________________________
================================================================================
Testando conceitos avancados:
Templates, heranca multipla, callbacks, operadores, etc.
-------------------------------------------------------------------------------================================================================================
Teste 8: Testando diferentes tipos de referencia
(por valor, ponteiro ou referencia)
-------------------------------------------------------------------------------Criando um array de apenas um inteiro com valor 8
Chamando funcao que tenta zerar variavel por valor
Valor de intArray[0]: 8
Criando um array de apenas um inteiro com valor 8
Chamando funcao que tenta zerar variavel por ponteiro
Valor de intArray[0]: 0
Criando um array de apenas um inteiro com valor 8
Chamando funcao que tenta zerar variavel por referencia
Valor de intArray[0]: 0
================================================================================
================================================================================
Teste 9: Testando argumentos padrao e argumentos multiplos
-------------------------------------------------------------------------------Chamando funcao print_ com argumento "
Teste 9":
Teste 9
Funcao print_ sem argumentos indisponivel em java
Chamando wrapped_average(1, 2):
1.5
Chamando wrapped_average(1, 2, 3, 4): 2.5
Funcao wrapped_average(1, 2, 3, 4, 5, 6) nao disponivel em java
================================================================================
================================================================================
Teste 10: Testando o uso de callbacks
-------------------------------------------------------------------------------Criando instancia de advanced class com valor 10
Definindo callback como sendo funcao C++ definida no arquivo .i
Requisitando chamada aa funcao de callback:
10.1
Definindo o callback polimorfico como sendo uma instancia de JavaCallable
Requisitando chamada aa funcao de callback polimorfico: 0.0
================================================================================
================================================================================
Teste 11: Testando o uso de sobrecarga de operadores
-------------------------------------------------------------------------------Criando uma instancia de AdvancedClass com valor 5 e outra com 6
advancedResult = advanced_instance_5 + advanced_instance_6 (using renamed add)
Valor de advancedResult.getValue(): 11.0
================================================================================
================================================================================
Teste 12: Testando o uso de excecoes
-------------------------------------------------------------------------------Forcando SomeException a ser jogada em C++, tratando-a em java:
================================================================================
================================================================================
Teste 13: Testando o uso de templates
-------------------------------------------------------------------------------Criando instancia de TemplatedClass<int>
Definindo valor inteiro 13 e lendo: 13
Classe TemplatedClass<float> nao disponivel em java
================================================================================
================================================================================
Teste 14: Testando heranca multipla
--------------------------------------------------------------------------------
Criando instancia de MultipleClass - subclasse de SomeClass e de RealClassCpp
Chamando funcao herdada
de SomeClass
em 5:
multipleInstance.getFoo():
5.0
Chamando funcao agregada de RealClassCpp em 7:
multipleInstance.getAggregate().getFloat(): 7.0
================================================================================
================================================================================
Teste 15: Testando uso std::vectors e maps em java
-------------------------------------------------------------------------------Criando instancia de StdClass - contendo membros std::vector e std::map
Adicionando valores 15.0 e 15.1 ao std::vector de C++, usando 'hack' addFloat()
Percorrendo vetor com size() e get():
15.1
15.2
Adicionando 15.2->"Hello 15.2" e 15.3->"Hello 15.3", usando set()
Imprimindo os items do mapa usando get()
some_package.SWIGTYPE_p_string@de6ced
some_package.SWIGTYPE_p_string@c17164
================================================================================
9.7. Interface Pyste + Boost.Python para some_lib
9.7.1. some_lib_boost_interface.pyste
# TODO: acessar arrays como python lists
#int_array = Var('int_array', 'some_lib.h')
# Exporting global wrappers:
Function('set_base_int',
'some_lib_adapter_code.h')
Function('get_base_int',
'some_lib_adapter_code.h')
Function('set_base_float', 'some_lib_adapter_code.h')
Function('get_base_float', 'some_lib_adapter_code.h')
Function('set_base_double', 'some_lib_adapter_code.h')
Function('get_base_double', 'some_lib_adapter_code.h')
Function('set_base_char',
'some_lib_adapter_code.h')
Function('get_base_char',
'some_lib_adapter_code.h')
# Functions returning pointers must have explicit policy defined:
Function('set_base_string', 'some_lib_adapter_code.h')
f = Function('get_base_string', 'some_lib_adapter_code.h')
set_policy(f, return_value_policy(return_opaque_pointer))
Enum('size',
'some_lib.h')
Var ('some_size', 'some_lib.h')
Var ('const_int', 'some_lib.h')
Function('looseFunction', 'some_lib.h')
Function('callGetFloat', 'some_lib.h')
Class('SomeStruct',
Class('SomeClass',
Class('OtherClass',
Class('AbstractClass',
Class('RealClassCpp',
Class('Callable',
Class('MultipleClass',
Class('StdClass',
'some_lib.h')
'some_lib.h')
'some_lib.h')
'some_lib.h')
'some_lib.h')
'some_lib.h')
'some_lib.h')
'some_lib.h')
c = Class('AdvancedClass', 'some_lib.h')
exclude(c.average)
# Wrapper went nuts with variable arguments...
# TODO: Try to get callbacks working
exclude(c.setCallback)
Function('wrapped_average',
'some_lib_adapter_code.h')
Function('increment_callback', 'some_lib_adapter_code.h')
T = Template("TemplatedClass", "some_lib.h")
T('int', 'TemplatedClass_int')
9.7.2. some_lib_adapter_code.h
/*******************************************************************************
* File: some_lib_adapter_code.py
* Codigo adaptador para exportar as funcionalidades de some_lib, contornando
* as limitacoes da Boost.Python ou do Pyste.
******************************************************************************/
#include <string.h>
#include <some_lib.h>
int
get_base_int
()
float get_base_float ()
double get_base_double()
char
get_base_char ()
char *get_base_string()
void
void
void
void
void
{
{
{
{
{
return
return
return
return
return
base_int;
base_float;
base_double;
base_char;
base_string;
set_base_int
(int
value)
set_base_float (float value)
set_base_double(double value)
set_base_char (char
value)
set_base_string(char *value)
{
{
{
{
{
}
}
}
}
}
base_int
= value; }
base_float = value; }
base_double = value; }
base_char
= value; }
strcpy(base_string, value); }
double wrapped_average(double a) {
return AdvancedClass::average(a, -1.0); }
double wrapped_average(double a, double b) {
return AdvancedClass::average(a, b, -1.0); }
double wrapped_average(double a, double b, double c) {
return AdvancedClass::average(a, b, c, -1.0); }
double wrapped_average(double a, double b, double c, double d) {
return AdvancedClass::average(a, b, c, d, -1.0); }
float increment_callback(float value) { return value + 0.1f; }
9.7.3. pyste_run.sh
SOME_LIB_DIR=C:/Projetos/Relatorio/some_lib
INCLUDE_DIRS=${SOME_LIB_DIR}/include
INTERFACE_FILE=some_lib_boost_interface.pyste
OUTPUT_FILE=some_lib_boost_wrapper.cpp
OUTPUT_DIR=some_lib_wrappers
echo "Creating python boost wrapper code"
python C:\\Python24\\Scripts\\pyste.py --module=some_lib_boost -out=${OUTPUT_FILE} -I ${INCLUDE_DIRS} ${INTERFACE_FILE}
mv *.cpp ${OUTPUT_DIR}
echo "Done"
9.7.2. some_lib_boost_wrapper.cpp
// Boost Includes ==============================================================
#include <boost/python.hpp>
#include <boost/cstdint.hpp>
// Includes ====================================================================
#include <some_lib.h>
#include <some_lib_adapter_code.h>
// Using =======================================================================
using namespace boost::python;
// Declarations ================================================================
BOOST_PYTHON_OPAQUE_SPECIALIZED_TYPE_ID(char)
namespace
{
struct AbstractClass_Wrapper: AbstractClass
{
float getFloat() {
return call_method< float >(py_self, "getFloat");
}
PyObject* py_self;
};
struct RealClassCpp_Wrapper: RealClassCpp
{
RealClassCpp_Wrapper(PyObject* py_self_):
RealClassCpp(), py_self(py_self_) {}
float getFloat() {
return call_method< float >(py_self, "getFloat");
}
float default_getFloat() {
return RealClassCpp::getFloat();
}
PyObject* py_self;
};
struct Callable_Wrapper: Callable
{
Callable_Wrapper(PyObject* py_self_):
Callable(), py_self(py_self_) {}
float call(float p0) {
return call_method< float >(py_self, "call", p0);
}
float default_call(float p0) {
return Callable::call(p0);
}
PyObject* py_self;
};
BOOST_PYTHON_FUNCTION_OVERLOADS(AdvancedClass_print_overloads_0_1,
AdvancedClass::print_, 0, 1)
}// namespace
// Module ======================================================================
BOOST_PYTHON_MODULE(some_lib_boost)
{
def("set_base_int", &set_base_int);
def("get_base_int", &get_base_int);
def("set_base_float", &set_base_float);
def("get_base_float", &get_base_float);
def("set_base_double", &set_base_double);
def("get_base_double", &get_base_double);
def("set_base_char", &set_base_char);
def("get_base_char", &get_base_char);
def("set_base_string", &set_base_string);
def("get_base_string", &get_base_string, return_value_policy<
return_opaque_pointer >());
enum_< size >("size")
.value("small", small)
.value("big", big)
.value("medium", medium)
;
scope().attr("some_size") = some_size;
scope().attr("const_int") = const_int;
def("looseFunction", &looseFunction);
def("callGetFloat", &callGetFloat);
class_< SomeStruct, boost::noncopyable >("SomeStruct", init<
>())
.def_readwrite("struct_member", &SomeStruct::struct_member)
;
class_< SomeClass, boost::noncopyable >("SomeClass", init<
>())
.def_readwrite("foo", &SomeClass::foo)
.def("getFoo", &SomeClass::getFoo)
.def("setFoo", &SomeClass::setFoo)
.def("getBar", &SomeClass::getBar)
.staticmethod("getBar")
;
class_< OtherClass, bases< SomeClass > , boost::noncopyable >("OtherClass",
init<
>())
.def("getFoo", &OtherClass::getFoo)
;
class_< AbstractClass, boost::noncopyable, AbstractClass_Wrapper
>("AbstractClass", no_init)
.def("getFloat", pure_virtual(&AbstractClass::getFloat))
;
class_< RealClassCpp, bases< AbstractClass > , boost::noncopyable,
RealClassCpp_Wrapper >("RealClassCpp", init<
>())
.def("getFloat", (float (RealClassCpp::*)() )&RealClassCpp::getFloat,
(float (RealClassCpp_Wrapper::*)())&RealClassCpp_Wrapper::default_getFloat)
;
class_< Callable, boost::noncopyable, Callable_Wrapper >("Callable", init<
>())
.def("call", &Callable::call, &Callable_Wrapper::default_call)
;
class_< MultipleClass, bases< SomeClass, RealClassCpp > , boost::noncopyable
>("MultipleClass", init<
>())
;
class_< StdClass, boost::noncopyable >("StdClass", init<
>())
;
class_< AdvancedClass, boost::noncopyable >("AdvancedClass", init< float
>())
.def("setValue", &AdvancedClass::setValue)
.def("getValue", &AdvancedClass::getValue)
.def("resetInt_Value", &AdvancedClass::resetInt_Value)
.def("resetInt_Pointer", &AdvancedClass::resetInt_Pointer)
.def("resetInt_Reference", &AdvancedClass::resetInt_Reference)
.def("print_", &AdvancedClass::print_,
AdvancedClass_print_overloads_0_1())
.def("callCallback", &AdvancedClass::callCallback)
.def("setPolyCallback", &AdvancedClass::setPolyCallback)
.def("callPolyCallback", &AdvancedClass::callPolyCallback)
.def("throwException", &AdvancedClass::throwException)
.staticmethod("resetInt_Value")
.staticmethod("resetInt_Reference")
.staticmethod("print_")
.staticmethod("resetInt_Pointer")
.def( self + self )
;
def("wrapped_average", (double (*)(double))&wrapped_average);
def("wrapped_average", (double (*)(double, double))&wrapped_average);
def("wrapped_average", (double (*)(double, double,
double))&wrapped_average);
def("wrapped_average", (double (*)(double, double, double,
double))&wrapped_average);
def("increment_callback", &increment_callback);
class_< TemplatedClass<int>, boost::noncopyable >("TemplatedClass_int",
init<
>())
.def("setValue", &TemplatedClass<int>::setValue)
.def("getValue", &TemplatedClass<int>::getValue)
;
}
9.8. Código fonte: testes de some_lib com Boost.Python
9.8.1. some_lib_boost_tests.py
#*******************************************************************************
# File: some_lib_boost_tests.py
# Testa em python as funcionalidades de some_lib exportadas pelo SWIG
# Ver arquivo de interface some_lib_swig_interface.i
#*******************************************************************************
import some_lib_boost
#*******************************************************************************
# Imprime todos os membros de root, comecando com identacao ident, percorrendo
# a arvore ateh o nivel indicado por sub_lvel.
# Ignora membros que iniciem por '_', ou terminem com 'Ptr'
# Os nos folha sao impressos na mesma linha
#*******************************************************************************
def print_tree(root, ident, sub_level):
if sub_level == 1:
str = ''
for entry in dir(root):
if (entry.startswith('_')
or
entry.startswith('func_') or
entry.endswith('Ptr')):
continue
str += entry + ', '
if len(str) > 0:
print' ' * ident + ' - ' + str
return
for entry in dir(root):
if entry.startswith('_') or entry.endswith('Ptr'):
continue
print ' ' * ident, '+', entry
entry = getattr(root, entry)
print_tree(entry, ident + 2, sub_level - 1)
#*******************************************************************************
# Helper printing functions
#*******************************************************************************
def print_header(string):
print
print
'===============================================================================
='
print string
print '-------------------------------------------------------------------------------'
def print_footer():
print
'===============================================================================
='
def print_spearator():
print
print
'_______________________________________________________________________________
_'
print
#*******************************************************************************
# Imprime cabecalho e a arvore dos membros exportados de some_lib
#*******************************************************************************
def print_start():
print
print 'some_lib_tests.py'
print 'Testa as funcionalidades de some_lib exportadas pela Boost.Python'
print
print 'Na maioria dos casos, um teste eh considerado bem sucedido se o valor'
print 'impresso for igual ao numero do teste sendo realizado, as vezes com'
print 'incrementos sucessivos de 0.1 entre sub-testes'
print 'Outros, pela sua natureza, sao bem sucedidos quanto algum erro'
print 'acontece durante o teste, como tentar modificar uma constante'
print_header('Arvore dos membros exportados:')
print_tree(some_lib_boost, 2, 2)
print_footer()
print
#*******************************************************************************
# Testa leitura e escrita em variaveis globais de some_lib, com tipos variados
# de dados
# Nota: Variaveis globais exportadas pela Boost sao somente leitura.
# Modificacoes em seu valor serao vistas em python mas nao em C++. Se for
# necessario modifica-las em C++, exportar funcoes de acesso, como fizemos aqui.
#*******************************************************************************
def test_globals():
print_header('Teste 1: Testando leitura e escrita em variaveis C' +
' globais de diferentes tipos')
print 'Lendo variavel inteira
base_int:
', \
some_lib_boost.get_base_int()
print 'Lendo variavel ponto flutuante simples base_float:
', \
some_lib_boost.get_base_float()
print 'Lendo variavel ponto flutuante double
base_double: ', \
some_lib_boost.get_base_double()
print 'Lendo variavel caracter
base_char:
', \
some_lib_boost.get_base_char()
# TODO: Tentar acessar C char* como python string
print 'Lendo string (null terminated chars)
base_string: ', \
some_lib_boost.get_base_string()
# TODO: Tentar acessar C array como python list
#
array = some_lib_boost.int_array
#
print 'Lendo array de inteiros
#
int_array:
array[0], array[1], array[2], array[3], array[4]
print
print 'Alterando valor de base_float para 1.1'
some_lib_boost.set_base_float(1.1)
', \
print 'Lendo variavel ponto flutuante simples base_float:
', \
some_lib_boost.get_base_float()
print_footer()
#*******************************************************************************
# Testa leitura de #defines e enums, e acesso ilegal a constantes
#*******************************************************************************
def test_constants():
print_header('Teste 2: Testando leitura de #defines e enums,' +
' e acesso ilegal a constantes')
print 'Lendo enum size some_size:
', some_lib_boost.some_size
# TODO: check if this can be exported
#
print 'Lendo constante #define RED: ', hex(some_lib_boost.RED)
print 'Testando escrita ilegal em constante const_int:'
try:
# TODO: Ver se nao teria como levantar uma excecao com isso
some_lib_boost.const_int = 2
print '
Escrita em const realizada em python, mas nao propagada para C++'
except TypeError, value:
print '
TypeError:', value
print_footer()
#*******************************************************************************
# Testa chamadas a funcoes
#*******************************************************************************
def test_functions():
print_header('Teste 3: Testando chamadas a funcoes C')
print 'Chamando funcao looseFunction: ', some_lib_boost.looseFunction()
print_footer()
#*******************************************************************************
# Testa uso de structs
# NOTA: Construtores de structs e classes nao sao exportados automaticamente
# pelo Pyste, eh necessario criar o construtor explicitamente,
# ou fazer uma classe wrapper se nao for possivel mexer no
# codigo original da lib sendo exportada
#*******************************************************************************
def test_structs():
print_header('Teste 4: Testando utilizacao de structs')
struct = some_lib_boost.SomeStruct()
print 'Criando struct SomeStruct e acessando membro struct_member: ', \
struct.struct_member
print_footer()
#*******************************************************************************
# Testa uso de classes:
# Funcoes e atributos, de instancia e de classe, publicos e privados
#*******************************************************************************
def test_classes():
print_header('Teste 5: Testando utilizacao de classes: ' +
'\n\t Funcoes e atributos, de instancia e de classe, publicos e privados')
print 'Criando instancia de SomeClass - some_instance'
some_instance = some_lib_boost.SomeClass()
print 'Chamando funcao
some_instance.getFoo():
', \
some_instance.getFoo()
print 'Alterando foo para 5.1 chamando some_instance.setFoo(5.1)'
some_instance.setFoo(5.1)
print 'Lendo atributo publico
some_instance.foo:
', \
SomeClass.getBar():
', \
some_instance.foo
print
print 'Chamando funcao de classe
some_lib_boost.SomeClass.getBar()
try:
print 'Chamando funcao privada
some_instance.getPrivate():', \
some_instance.getPrivate()
except AttributeError, value:
print '\n
AttributeError:', value
print_footer()
#*******************************************************************************
# Testa conceitos basicos de heranca
#*******************************************************************************
def test_inheritance():
print_header('Teste 6: Testando conceitos basicos de heranca')
print 'Criando instancia de OtherClass (subclasse de SomeClass)'
other_instance = some_lib_boost.OtherClass()
print 'Lendo antiga variavel nao modificada other_instance.foo:
', \
other_instance.foo
print 'Chamando funcao reimplementada
other_instance.getFoo():', \
other_instance.getFoo()
print_footer()
#*******************************************************************************
# Testa classes abstratas e polimorfismo
# Nota: Atravez do polimorfismo pode-se chamar codigo python a partir de C++,
# como eh feito neste exemplo
#*******************************************************************************
def test_polimorphysm():
print_header('Teste 7: Testando classes abstratas e polimorfismo')
print 'Criando instancia de classe abstrata AbstractClass:'
try:
abstract_instance = some_lib_boost.AbstractClass()
except RuntimeError, value:
print '
RuntimeError:', value
print
print 'Definindo RealClassPython (subclasse de RealClassCpp) em python'
class RealClassPython(some_lib_boost.RealClassCpp):
def __init__(self):
some_lib_boost.RealClassCpp.__init__(self)
def getFloat(self):
return some_lib_boost.RealClassCpp.getFloat(self) + 0.1
print 'Criando instancias de subclasses RealClassCpp e RealClassPython'
real_instance_cpp
= some_lib_boost.RealClassCpp()
real_instance_python = RealClassPython()
print 'callGetFloat => funcao em C++,',\
'chama getFloat de uma instancia de AbstractClass'
print
print 'Chamando callGetFloat(real_instance_cpp):
', \
some_lib_boost.callGetFloat(real_instance_cpp)
print 'Chamando callGetFloat(real_instance_python):', \
some_lib_boost.callGetFloat(real_instance_python)
print_footer()
#*******************************************************************************
# Testa diferentes tipos de referencia
#*******************************************************************************
def test_references():
print_header('Teste 8: Testando diferentes tipos de referencia' +
'\n\t (por valor, ponteiro ou referencia)')
print 'Criando variavel inteira com valor 8'
int_var = 8
print 'Chamando funcao que tenta zerar variavel por valor'
print 'Retorno da chamada de AdvancedClass.resetInt_Value(int_var):
', \
some_lib_boost.AdvancedClass.resetInt_Value(int_var)
# TODO: How to access int* and int& # Boost.Python.ArgumentError:
#
Python argument types in AdvancedClass.resetInt_Pointer(int)
# did not match C++ signature: resetInt_Pointer(int *)
print
print 'Criando variavel inteira com valor 8'
int_var = 8
print 'Chamando funcao que tenta zerar variavel por ponteiro'
#
#
print 'Retorno da chamada de AdvancedClass.resetInt_Pointer(int_var):
', \
some_lib_boost.AdvancedClass.resetInt_Pointer(int_var)
print
print 'Criando variavel inteira com valor 8'
int_var = 8
print 'Chamando funcao que tenta zerar variavel por referencia'
#
#
print 'Retorno da chamada de AdvancedClass.resetInt_Reference(int_var):', \
some_lib_boost.AdvancedClass.resetInt_Reference(int_var)
print_footer()
#*******************************************************************************
# Testa argumentos padroes e argumentos multiplos
# NOTA: Python nao usa o conceito de argumentos variaveis porque usa tuplas,
# entao funcoes com varios argumentos deve ter 'instancias' com um
# numero pre-determinado de argumentos e tipos exportados, como no nosso
exemplo.
#*******************************************************************************
def test_arguments():
print_header('Teste 9: Testando argumentos padroes e argumentos multiplos')
print 'Chamando funcao print_ com argumento "
some_lib_boost.AdvancedClass.print_("
Teste 9":'
Teste 9")
print 'Chamando funcao print_ sem argumentos:'
some_lib_boost.AdvancedClass.print_()
print
print 'Chamando wrapped_average(1, 2):
', \
some_lib_boost.wrapped_average(1, 2)
print 'Chamando wrapped_average(1, 2, 3, 4): ', \
some_lib_boost.wrapped_average(1, 2, 3, 4)
try:
print 'hamando wrapped_average(1, 2, 3, 4, 5, 6) - nao exportada:', \
some_lib_boost.wrapped_average(1, 2, 3, 4, 5, 6)
except TypeError, value:
print '\n
TypeError:', value
print_footer()
#*******************************************************************************
# Testa o uso de callbacks
# NOTA: Callbacks podem
ser feitos em C++, dentro da propria interface,
# ou pode-se utilizar Polimorfismo para chama-los em Python. Este exemplo mostra
# as duas alternativas
#*******************************************************************************
def test_callbacks():
print_header('Teste 10: Testando o uso de callbacks')
print 'Criando instancia de advanced class com valor 10'
advanced_instance = some_lib_boost.AdvancedClass(10)
print 'Definindo callback como sendo funcao C++ definida no arquivo .i'
# TODO: Try to fix callbacks calling
#
advanced_instance.setCallback(some_lib_boost.increment_callback)
#
print 'Requisitando chamada aa funcao de callback:',
#
advanced_instance.callCallback()
print
print 'Definindo classe PythonCallable (subclasse de Callable) em python'
class PythonCallable(some_lib_boost.Callable):
def __init__(self):
some_lib_boost.Callable.__init__(self)
def call(self, parm):
return parm + 0.2
print 'Definindo o callback polimorfico como sendo', \
'uma instancia de PythonCallable'
python_callback = PythonCallable()
advanced_instance.setPolyCallback(python_callback)
print 'Requisitando chamada aa funcao de callback polimorfico:', \
advanced_instance.callPolyCallback()
print_footer()
#*******************************************************************************
# Testa o uso de sobrecarga de operadores
#*******************************************************************************
def test_operators():
print_header('Teste 11: Testando o uso de sobrecarga de operadores')
print 'Criando uma instancia de AdvancedClass com valor 5 e outra com 6'
advanced_instance_5 = some_lib_boost.AdvancedClass(5)
advanced_instance_6 = some_lib_boost.AdvancedClass(6)
print 'advanced_result = advanced_instance_5 + advanced_instance_6'
# TODO: Not working - try to fix operator overload
try:
advanced_result = advanced_instance_5 + advanced_instance_6
print 'Valor de advanced_result.getValue():', advanced_result.getValue()
except:
print '
Error calling overloaded operator +'
print_footer()
#*******************************************************************************
# Testa o uso de templates
# NOTA: Como nao existe o conceito de templates em python, precisamos criar
# 'instancias' de templates, como neste exemplo
#*******************************************************************************
def test_templates():
print_header('Teste 12: Testando o uso de templates')
print 'Criando instancia de TemplatedClass<int>'
templated_instance = some_lib_boost.TemplatedClass_int()
templated_instance.setValue(12)
print 'Definindo valor inteiro 12 e lendo:', templated_instance.getValue()
print 'Criando instancia de TemplatedClass<float> - nao exportada'
try:
templated_instance = some_lib_boost.TemplatedClass_float()
except AttributeError, value:
print '
AttributeError:', value
print_footer()
#*******************************************************************************
# Testa heranca multipla
#*******************************************************************************
def test_multiple_inheritance():
print_header('Teste 13: Testando heranca multipla')
print 'Criando instancia de MultipleClass',\
' - subclasse de SomeClass e de RealClassCpp'
multiple_instance = some_lib_boost.MultipleClass()
print 'Chamando funcao herdada de SomeClass
' multiple_instance.getFoo():
em 5:' + \
', multiple_instance.getFoo()
print 'Chamando funcao herdada de RealClassCpp em 7:' + \
' multiple_instance.getFloat():', multiple_instance.getFloat()
print_footer()
#*******************************************************************************
# Testa uso de std::vectors e std::maps
# como listas e dicionarios nativos de python
#*******************************************************************************
def test_std():
print_header('Teste 14: Testando std::vectors e maps' +\
' como listas e mapas nativos de python')
print 'Criando instancia de StdClass' + \
' - contendo membros std::vector e std::map'
std_instance = some_lib_boost.StdClass()
print
print 'Adicionando valores 14.0 e 14.1 ao std::vector de C++,', \
'com sintaxe python'
# TODO: Compilacao do wrapper se perde se deixarmos std headers em some_lib.h
#
std_instance.float_vector.append(14.0)
#
std_instance.float_vector.append(14.1)
print 'Percorrendo vetor com sintaxe python:'
print '
#
for item in std_instance.float_vector: print item'
for item in std_instance.float_vector: print item
print
print 'Adicionando ao std::map com sintaxe python:'
print '
map[14.2] = "Hello 14.2"'
print '
map[14.3] = "Hello 14.3"'
#
std_instance.float2str_map[14.2] = 'Hello 14.2'
#
std_instance.float2str_map[14.3] = 'Hello 14.3'
print 'Imprimindo items() do mapa - como em um dicionario python:'
#
print std_instance.float2str_map.items()
print_footer()
#*******************************************************************************
# MAIN
#*******************************************************************************
print_start()
print_spearator()
print_header('Testando conceitos basicos de C estruturado:' +
'\nVariaveis globais, funcoes, structs, etc.')
test_globals()
test_constants()
test_functions()
test_structs()
print_spearator()
print_header('Testando conceitos basicos de C++ orientado a objetos:' +
'\nClasses, heranca, polimorfismo, etc.')
test_classes()
test_inheritance()
test_polimorphysm()
print_spearator()
print_header('Testando conceitos avancados:' +
'\nTemplates, heranca multipla, callbacks, operadores, etc...')
test_references()
test_arguments()
test_callbacks()
test_operators()
test_templates()
test_multiple_inheritance()
test_std()
9.9. Resultado dos testes de some_lib com Boost.Python
some_lib_tests.py
Testa as funcionalidades de some_lib exportadas pela Boost.Python
Na maioria dos casos, um teste eh considerado bem sucedido se o valor
impresso for igual ao numero do teste sendo realizado, as vezes com
incrementos sucessivos de 0.1 entre sub-testes
Outros, pela sua natureza, sao bem sucedidos quanto algum erro
acontece durante o teste, como tentar modificar uma constante
================================================================================
Arvore dos membros exportados:
-------------------------------------------------------------------------------+ AbstractClass
- getFloat,
+ AdvancedClass
- callCallback, callPolyCallback, getValue, print_, resetInt_Pointer,
resetInt_Reference, resetInt_Value, setPolyCa
llback, setValue,
+ Callable
- call,
+ MultipleClass
- foo, getBar, getFloat, getFoo, setFoo,
+ OtherClass
- foo, getBar, getFoo, setFoo,
+ RealClassCpp
- getFloat,
+ SomeClass
- foo, getBar, getFoo, setFoo,
+ SomeStruct
- struct_member,
+ StdClass
+ TemplatedClass_int
- getValue, setValue,
+ callGetFloat
+ const_int
+ get_base_char
+ get_base_double
+ get_base_float
+ get_base_int
+ get_base_string
+ increment_callback
+ looseFunction
+ set_base_char
+ set_base_double
+ set_base_float
+ set_base_int
+ set_base_string
+ size
- big, medium, name, small, values,
+ some_size
- big, medium, name, small, values,
+ wrapped_average
================================================================================
________________________________________________________________________________
================================================================================
Testando conceitos basicos de C estruturado:
Variaveis globais, funcoes, structs, etc.
--------------------------------------------------------------------------------
================================================================================
Teste 1: Testando leitura e escrita em variaveis C globais de diferentes tipos
-------------------------------------------------------------------------------Lendo variavel inteira
base_int:
1
Lendo variavel ponto flutuante simples base_float:
1.0
Lendo variavel ponto flutuante double
base_double:
1.0
Lendo variavel caracter
base_char:
1
Lendo string (null terminated chars)
base_string:
<char * object at
0x0099D040>
Alterando valor de base_float para 1.1
Lendo variavel ponto flutuante simples base_float:
1.10000002384
================================================================================
================================================================================
Teste 2: Testando leitura de #defines e enums, e acesso ilegal a constantes
-------------------------------------------------------------------------------Lendo enum size some_size:
big
Testando escrita ilegal em constante const_int:
Escrita em const realizada em python, mas nao propagada para C++
================================================================================
================================================================================
Teste 3: Testando chamadas a funcoes C
-------------------------------------------------------------------------------Chamando funcao looseFunction:
3
================================================================================
================================================================================
Teste 4: Testando utilizacao de structs
-------------------------------------------------------------------------------Criando struct SomeStruct e acessando membro struct_member:
4
================================================================================
________________________________________________________________________________
================================================================================
Testando conceitos basicos de C++ orientado a objetos:
Classes, heranca, polimorfismo, etc.
--------------------------------------------------------------------------------
================================================================================
Teste 5: Testando utilizacao de classes:
Funcoes e atributos, de instancia e de classe, publicos e privados
-------------------------------------------------------------------------------Criando instancia de SomeClass - some_instance
Chamando funcao
some_instance.getFoo():
5.0
Alterando foo para 5.1 chamando some_instance.setFoo(5.1)
Lendo atributo publico
some_instance.foo:
5.09999990463
Chamando funcao de classe
SomeClass.getBar():
5
Chamando funcao privada
some_instance.getPrivate():
AttributeError: 'SomeClass' object has no attribute 'getPrivate'
================================================================================
================================================================================
Teste 6: Testando conceitos basicos de heranca
-------------------------------------------------------------------------------Criando instancia de OtherClass (subclasse de SomeClass)
Lendo antiga variavel nao modificada other_instance.foo:
Chamando funcao reimplementada
5.0
other_instance.getFoo(): 6.0
================================================================================
================================================================================
Teste 7: Testando classes abstratas e polimorfismo
-------------------------------------------------------------------------------Criando instancia de classe abstrata AbstractClass:
RuntimeError: This class cannot be instantiated from Python
Definindo RealClassPython (subclasse de RealClassCpp) em python
Criando instancias de subclasses RealClassCpp e RealClassPython
callGetFloat => funcao em C++, chama getFloat de uma instancia de AbstractClass
Chamando callGetFloat(real_instance_cpp):
7.0
Chamando callGetFloat(real_instance_python): 7.09999990463
================================================================================
________________________________________________________________________________
================================================================================
Testando conceitos avancados:
Templates, heranca multipla, callbacks, operadores, etc...
-------------------------------------------------------------------------------================================================================================
Teste 8: Testando diferentes tipos de referencia
(por valor, ponteiro ou referencia)
-------------------------------------------------------------------------------Criando variavel inteira com valor 8
Chamando funcao que tenta zerar variavel por valor
Retorno da chamada de AdvancedClass.resetInt_Value(int_var):
8
Criando variavel inteira com valor 8
Chamando funcao que tenta zerar variavel por ponteiro
Criando variavel inteira com valor 8
Chamando funcao que tenta zerar variavel por referencia
================================================================================
================================================================================
Teste 9: Testando argumentos padroes e argumentos multiplos
-------------------------------------------------------------------------------Chamando funcao print_ com argumento "
Teste 9":
Teste 9
Chamando funcao print_ sem argumentos:
Hello World
Chamando wrapped_average(1, 2):
1.5
Chamando wrapped_average(1, 2, 3, 4):
2.5
hamando wrapped_average(1, 2, 3, 4, 5, 6) - nao exportada:
TypeError: Python argument types in
some_lib_boost.wrapped_average(int, int, int, int, int, int)
did not match C++ signature:
wrapped_average(double, double, double, double)
wrapped_average(double, double, double)
wrapped_average(double, double)
wrapped_average(double)
================================================================================
================================================================================
Teste 10: Testando o uso de callbacks
-------------------------------------------------------------------------------Criando instancia de advanced class com valor 10
Definindo callback como sendo funcao C++ definida no arquivo .i
Definindo classe PythonCallable (subclasse de Callable) em python
Definindo o callback polimorfico como sendo uma instancia de PythonCallable
Requisitando chamada aa funcao de callback polimorfico: 10.1999998093
================================================================================
================================================================================
Teste 11: Testando o uso de sobrecarga de operadores
-------------------------------------------------------------------------------Criando uma instancia de AdvancedClass com valor 5 e outra com 6
advanced_result = advanced_instance_5 + advanced_instance_6
Error calling overloaded operator +
================================================================================
================================================================================
Teste 12: Testando o uso de templates
-------------------------------------------------------------------------------Criando instancia de TemplatedClass<int>
Definindo valor inteiro 12 e lendo: 12
Criando instancia de TemplatedClass<float> - nao exportada
AttributeError: 'module' object has no attribute 'TemplatedClass_float'
================================================================================
================================================================================
Teste 13: Testando heranca multipla
-------------------------------------------------------------------------------Criando instancia de MultipleClass
- subclasse de SomeClass e de RealClassCpp
Chamando funcao herdada de SomeClass
em 5: multiple_instance.getFoo():
5.0
Chamando funcao herdada de RealClassCpp em 7: multiple_instance.getFloat(): 7.0
================================================================================
================================================================================
Teste 14: Testando std::vectors e maps como listas e mapas nativos de python
-------------------------------------------------------------------------------Criando instancia de StdClass - contendo membros std::vector e std::map
Adicionando valores 14.0 e 14.1 ao std::vector de C++, com sintaxe python
Percorrendo vetor com sintaxe python:
for item in std_instance.float_vector: print item
Adicionando ao std::map com sintaxe python:
map[14.2] = "Hello 14.2"
map[14.3] = "Hello 14.3"
Imprimindo items() do mapa - como em um dicionario python:
================================================================================
9.10. Apocalypse - Source Code
Blah blah
9.11. Apocalypse – Interface SWIG
Blah blah
9.12. Apocalypse – Interface Pyste + Boost.Python
Blah blah
9.13. The Zen of Python
The Zen of Python, by Tim Peters:
1. Beautiful is better than ugly.
2. Explicit is better than implicit.
3. Simple is better than complex.
4. Complex is better than complicated.
5. Flat is better than nested.
6. Sparse is better than dense.
7. Readability counts.
8. Special cases aren't special enough to break the rules.
9. Although practicality beats purity.
10. Errors should never pass silently.
11. Unless explicitly silenced.
12. In the face of ambiguity, refuse the temptation to guess.
13. There should be one-- and preferably only one --obvious way to do it.
14. Although that way may not be obvious at first unless you're Dutch.
15. Now is better than never.
16. Although never is often better than right now.
17. If the implementation is hard to explain, it's a bad idea.
18. If the implementation is easy to explain, it may be a good idea.
19. NameSpaces are one honking great idea -- let's do more of those!
10. Artigo
"Duas retas paralelas se encontram no inferno"
Julio SzeremetaAliando Performance e Produtividade no Desenvolvimento de
Jogos
Fábio Luís Stange
Ciências da Computação, 2005
Departamento de Informática e Estatística – INE
Universidade Federal de Santa Catarina – UFSC, Brasil, 88040-900
[email protected]
Resumo
Blah
Palavras-chave: blah, blah.
Abstract
Bleh
Key-words: bleh, bleh.
Introdução
Blah
Download