Além de uma configuração fácil, as vantagens práticas de se usar o nar-maven-plugin para projetos com JNI são:
- Os cabeçalhos JNI em C++/C são gerados automaticamente;
- Os códigos C++ e Java são compilados juntos;
- Os artefatos gerados, sendo o JAR e DLL, sempre estarão casados com o mesmo número de versão;
- O nome da DLL (que inclui o número da versão conforme o padrão Maven) é automaticamente ajustado no código Java para seu carregamento na JVM;
- Projetos que dependem da biblioteca Java com JNI fazem apenas uma referência de dependência que inclui tanto o JAR quanto a DLL;
Além das vantagens de se usar o nar-maven-plugin, ainda existem outras vantagens de se usar o Maven também para o projeto C++/C.
O detalhamento da utilização da tecnologia JNI também não é abordado neste artigo, além no necessário para se entender o projeto.
Objetivos do Projeto JNI
O projeto apresentado aqui é para fins didáticos com o objetivo de testar o nar-maven-plugin, compilando código Java e C++ num único projeto, mais a execução de um teste unitário JUnit.A parte Java do projeto constitui apenas de uma classe com a definição de um método nativo para chamada do código em C++. Assim a parte C++ consiste na implementação da função JNI cujo cabeçalho é gerado pelo plugin. Um teste unitário é criado para validar a codificação.
Tanto o código C+++ quanto o código Java não serão detalhado neste artigo, mas estão disponíveis para download para melhor entendimento do projeto.
Preparação do Ambiente
O projeto foi compilado e testado na plataforma Windows 7 64 bits utilizando os seguintes sistemas:- Maven 3
- Mingw 4.8.1
- JDK 7 (32 bits)
- JUnit 4.11
- Nar Maven Plugin 3.0
O nar-maven-plugin e o JUnit serão baixados automaticamente pelo Maven.
Embora a plataforma de desenvolvimento seja 64 bits, o MingW compila código apenas para plataforma 32 bits. Por este motivo, é importante que o JDK também seja 32 bits para que seja possível o carregamento da DLL pela JVM.
Download
O código do projeto está disponível para download pelo Google Drive. Embora seja simples, ele serve de modelo inicial para projetos reais.O código também serve para aqueles mais versados nas tecnologias envolvidas aqui, para que possam aprender mais rapidamente sem ter que ler todo o artigo, apenas analisando os códigos.
Configuração do Projeto JNI
O arquivo pom.xml contem todas as configurações do projeto. O arquivo completo não é apresentado aqui, mas as partes mais relevantes são apresentadas e comentadas, ou seja, as configurações mínimas exigidas pelo Maven estão suprimidas. Obtenha o arquivo pom.xml completo fazendo o download do projetoPackaging
A primeira coisa que se deve fazer num projeto que utiliza o nar-maven-plugin é alterar a configuração (obrigatória) de packaging do Maven. Num projeto java, esta configuração é definida como JAR, mas com o nar-maven-plugin ela tem que ser definida como NAR, conforme abaixo:<!-- Importante que JAR seja substituído por NAR --> <packaging>nar</packaging>
Dependências
Apenas a dependência do JUnit para a execução do teste unitário deve ser descrita no arquivo de configuação pom.xml. A configuração de dependência é padrão do Maven; veja que o escopo da dependência é apenas para teste:<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies>
Plugin
A configuração do nar-maven-plugin segue abaixo:<build> <plugins> <plugin> <groupId>com.github.maven-nar</groupId> <artifactId>nar-maven-plugin</artifactId> <version>3.0.0</version> <extensions>true</extensions> <configuration> <cpp> <name>g++</name> </cpp> <linker> <name>g++</name> <options> <option>-Wl,--kill-at</option> </options> </linker> <libraries> <library> <type>jni</type> <narSystemPackage>com.prototype.jni.project</narSystemPackage> </library> </libraries> <javah> <jniDirectory>${basedir}\src\main\c++</jniDirectory> <classDirectory>${project.build.outputDirectory}\com\prototype\jni\project</classDirectory> </javah> </configuration> </plugin> </plugins> </build>
A declaração do plugin é bastante trival e segue os convenções do Maven. A partir da tag <configuration> estão as configurações particulares do plugin. Estas configurações, suas particularidades e funcionamento do plugin são descritos abaixo
O nar-maven-plugin tem compiladores e linkers definidos como padrão (default) de acordo com o sistema operacional. No caso do Windows, o padrão é o Microsoft Visual C++. Se este fosse o caso do projeto apresentado, não seria necessário definir o nome do compilador C++ e do linker (que seria mvsc). Portanto, como o Mingw é o compilador e o linker utilizados no projeto, o plugin precisa saber disso, informando o nome como g++ para o compilador (tag <cpp>) e para o linker (tag <linker>).
Ainda sobre o linker, no caso especificamente do g++ (ou gcc), é importante passar a opção -Wl,--kill-at para que a DLL seja gerada corretamente. Sem esta opção, a máquina virtual Java, ao carregar a DLL, não encontra as funções que espera encontrar, pois o linker adicionar um sufixo @número ao nome de cada função. Na opção descrita, note que após a vírgula não existe espaço em branco, portanto estamos falando de uma única opção e não de duas opções aqui!
A tag <libraries> contém a tag <library> com o tipo de binário gerado. No caso deste projeto, o tipo válido corresponde a JNI (em minúsculo) o que significa que o código Java deve ser compilado e gerado um arquivo do tipo JAR e o código C++ (ou C) deve ser compilado e gerado um arquivo do tipo DLL. Ainda, aqui também se define o package para a classe java NarSystem cujo código é gerado automaticamente pelo plugin para carregamento da DLL JNI. Informações adicionais sobre esta classe serão apresentadas mais adiante, quando a execução do plugin é abordada.
A geração automática do cabeçalho JNI a partir de classes Java com métodos nativos é feita com a configuração da tag <javah>. Aqui, é importante configurar duas coisas: o caminho onde os cabeçalhos C++/C serão gerados (tag <jniDirectory>) e o caminho onde os byte codes das classes Java (com métodos nativos) estão localizados (tag <classDirectory>).
Propriedades
Uma propriedade em especial precisa ser declarada no arquivo pom.xml (completamente fora da declaração e configuração do plugin). A propriedade <skipTests> deve ser definida como false para que o plugin surefire do Maven não execute os testes unitários. Este plugin não precisa ser declarado no arquivo pom.xml pois é padrão do Maven.<properties> <skipTests>true</skipTests> </properties>
Com esta propriedade definida como falsa, a execução dos testes unitários fica por conta do nar-maven-plugin. A razão disto é que ele inclui no classpath o caminho da DLL JNI antes do início da execução do teste.
Construção do Projeto e Funcionamento do Plugin
Uma vez o arquivo pom.xml configurado, a classe Java, com pelo menos um método nativo, já pode ser criada num determinado pacote (package) java. Uma vez compilado o projeto, o nar-maven-plugin invoca o javah do JDK para criar automaticamente os cabeçalhos JNI (arquivos com a extensão H) no diretório \src\main\c++, conforme fora configurado pela tag <jniDirectory>.Para cada classe com métodos nativos, tem-se um arquivo gerado com a assinatura das funções que devem ser criadas em C++ ou C. Um arquivo adicional com cabeçalho é criado pelo plugin para uma classe 'mágica' chamada de NarSystem. Este cabeçalho contém a assinatura de uma função que corresponde ao método runUnitTestsNative da classe NarSystem. Esta função pode ser implementada para que testes unitários em C++ ou em C possam se executados, porém isso não é obrigatório e não será discutido neste artigo.
A classe NarSystem é gerada no diretório \target\nar\nar-generated, mais os diretórios que compõem o pacote (package) Java onde está a classe, conforme configurado no arquivo pom.xml pela a tag <narSystemPackage>. Analisando esta classe, que é bem simples e intuitiva, ela é composta por dois métodos estáticos e um método nativo e por isso o cabeçalho JNI adicional é gerado automaticamente. Um método desta classe destina-se ao carregamento da DLL e o outro a execução dos testes unitários em C++ ou C. A vantagem na utilização desta classe para carregamento da DLL é que o número da versão do projeto que compõe o nome da DLL é corrigido automaticamente pelo pelo nar-maven-plugin, para seu carregamento adequado.
Embora a compilação tenha ocorra com sucesso até este ponto, o projeto ainda não está completo. Faltam as implementações das funções que são chamadas pelo Java. O código da implementação deve estar na pasta \src\main\c++ (ou pelo menos a partir dela) pois este é o diretório padrão que o plugin espera encontrar código C++. Este diretório, além de ser o padrão, foi exatamente o configurado pela tag <jniDirectory> no arquivo pom.xml.
Para finalizar, o teste unitário precisa ser criado para testar o código Java que invoca o código C++. O importante aqui está no carregamento da DLL JNI pelo Java. A classe NarSystem com o método estático loadLibrary faz este trabalho, mantendo o nome da DLL sincronizado com o nome do JAR e o número da versão do projeto. Este método deve ser chamado assim que o programa Java for carregado.
Uma vez tudo compilado, o nar-maven-plugin adiciona ao classpath (Java) o diretório da DLL para rodar o teste unitário.
Caso o teste seja executado com sucesso, o plugin compacta o binário JAR e DLL num único arquivo com extensão NAR e o instala no repositório local Maven. Um outro projeto que tenha dependência deste projeto precisa também utilizar o nar-maven-plugin, que se encarrega de descompactar o arquivo NAR e disponibilizar tanto o arquivo JAR quanto o arquivo DLL para uso.
Conclusão
O nar-maven-plugin é ideal para a criação de código Java e C++ que emprega a tecnologia JNI. Ele mantem um único projeto para códigos de ambos compiladores dentro dos padrões Maven e sua configuração é de baixa complexidade.A automatização da geração de cabeçalhos com assinaturas de funções que fazem interface com Java e C++ garante que elas sempre estarão casadas. Todavia, isso não suficiente para evitar erros no projeto: mesmo que os cabeçalhos gerados com as assinaturas das funções C++ e as respectivas implementações das funções C++ estejam descasados, o código C++ compila sem erros.
O plugin ainda permite convenientemente que o nome da DLL gerado siga o padrão Maven e esteja casado com o nome do binário Java (JAR). Esta padronização de nomes tem consequência no carregamento da DLL pelo programa Java, pois caso ocorra alteração do número da versão do projeto, o nome da DLL é também alterado (e seu nome no programa Java também deveria ser alterado). O nar-maven-plugin mantem isso funcional criando automaticamente no projeto a classe NarSystem para carregar a DLL no programa Java, ajustando adequadamente o nome da DLL.
Nenhum comentário:
Postar um comentário