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