quinta-feira, 19 de dezembro de 2013

Objetos Java via JNI

Existem trechos de código na linguagem C que não podem ser substituídos por fortes razões. No meu caso, a codificação é de terceiros para interpretação de arquivos binários com formato bem específico. Uma vez que possuo o código, eu poderia reescrevê-lo, mas assumiria riscos desnecessários. Portanto, meu código fonte teria que ser desenvolvido também em C ou C++ ou então teria que fazer a ponte com JNI para Java.

Motivação

Assim, tenho a seguinte situação: preciso ler um conjunto de dados em C e transferir para o Java. Os dados são modelados como objetos em Java. Ler atributo por atributo em C via JNI para instanciar o objeto em Java seria inviável. Também, ler um conjunto de atributos por vez para instanciar o objeto Java em C pode haver um overhead para máquina mais lentas. O ideal parece ler os atributos e instanciar objetos Java via JNI em C e retornar um array ou uma lista destes objetos pelo método nativo declarado em Java. Ufa! deu para entender? Vamos para a prática mesmo...

Objetivo

Eu costumo produzir códigos como "prova de conceito", para testar se a ideia principal funciona (ou seja, validar os pontos críticos). Neste exemplo, é fazer com que código em C/C++ "instancie" objetos definidos no Java via JNI. Em outras palavras, uma classe Java é definida,  no código C/C++ ela é instanciada via JNI e tem seus atributos modificados. Um método nativo declarado em Java é implementado no C/C++ para retornar um array destes objetos instanciados; outro método para retornar uma lista e um outro para retornar apenas uma instância de objeto. Assim, uma comparação é feita para determinar qual método é mais eficiente.

Pré-requisito

O projeto desenvolvido como exemplo emprega Maven, a utilização do nar-maven-plugin e JUnit para teste unitário.  No artigo "JNI: Java e C++ Num Único Projeto" deste blog está a descrição do uso do nar-maven-plugin para Maven.

Ambiente

O projeto foi compilado e testado na plataforma Windows 7 64 bits utilizando os seguintes sistemas:

Para o Maven, Mingw e Java, seus diretórios de instalação (com \BIN) devem ser incluídos manualmente no PATH do Windows.

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. O teste unitário em JUnit não é apresentado neste artigo mas está disponível para download.


O código também serve para aqueles mais versados nas tecnologias envolvidas aqui, para que possam aprender mais rapidamente, apenas analisando os códigos, sem ter que ler todo o artigo.

Desenvolvimento

A parte mais fácil deste projeto é o código Java. Duas classes são definidas: um Java Bean para representar o dado lido e outra com os métodos nativos que representam a interface JNI com o código C++.

Código Java

O código para a classe Java de dados é muito simples e dispensa comentários:

public class DataObject {
       
    public long id;   

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }
            
}

Os métodos nativos estão declarados na classe DataLoader. O primeiro retorna uma única instância do objeto DataObject, o segundo retorna uma array e o terceiro uma lista. Esta classe também tem um bloco estático que carrega a DLL JNI pela invocação de um método estático da Classe NarSystem. Esta outra classe é gerada automaticamente pelo nar-maven-plugin (veja o artigo recomendado sobre este assunto).

public class DataLoader {
    
    static {
        NarSystem.loadLibrary();
    }
    
    public native DataObject loadObject(int id);
    
    public native DataObject[] loadArray();
    
    public native List<DataObject> loadList();
    
}

Código C++

Compilando o programa, o nar-maven-plugin vai gerar os cabeçalhos C JNI para implementação das funções (correlacionados com os métodos nativos Java) em C++.

A primeira função cria apenas uma instância de DataObject cujo atributo 'id' é passado como argumento. Basicamente, um identificador da classe é localizado, bem como um identificador do seu construtor default; um identificador do atributo 'id' também é recuperado. Com estes identificadores, cria-se a instância do Objeto Java na JVM via JNI e atribui-se um valor ao atributo 'id' do objeto.

JNIEXPORT jobject JNICALL Java_com_prototype_jni_DataLoader_loadObject
  (JNIEnv *env, jobject, jint id) {

    jclass dataObjectClassId = env->FindClass("com/prototype/jni/DataObject");   
    jmethodID dataObjectContructorId = env->GetMethodID(dataObjectClassId, "<init>", "()V");    
    jobject dataObject = NULL;   
    
    jfieldID fid = env->GetFieldID(dataObjectClassId, "id", "J"); 
    
    dataObject = env->NewObject(dataObjectClassId, dataObjectContructorId);
    env->SetLongField(dataObject, fid, (jlong) id);
    
    return dataObject;   
}

A segunda função retorna um array de instâncias de DataObject. A idéia para instanciar tanto o array de DataObject quanto as instância de DataObject é a mesma apresentada anteriormente. Aqui, 100.000 instância são colocadas no array retornado pela função.

JNIEXPORT jobjectArray JNICALL Java_com_prototype_jni_DataLoader_loadArray
  (JNIEnv *env, jobject) {
    
    int size = 100000;
    
    jclass dataObjectArrayClassId= env->FindClass("com/prototype/jni/DataObject");
    jobjectArray dataObjectArray = env->NewObjectArray(size, dataObjectArrayClassId, NULL);
    
    jclass dataObjectClassId = env->FindClass("com/prototype/jni/DataObject");   
    jmethodID dataObjectContructorId = env->GetMethodID(dataObjectClassId, "<init>", "()V");    
    jobject dataObject = NULL;   
    
    jfieldID fid = env->GetFieldID(dataObjectClassId, "id", "J");
    
    for (int i = 0; i < size; i++) {
        dataObject = env->NewObject(dataObjectClassId, dataObjectContructorId);
        env->SetLongField(dataObject, fid, (jlong) i);
        // Add to the array
        env->SetObjectArrayElement(dataObjectArray, i, dataObject);
    }

    return dataObjectArray;    
}

A função que retorna uma lista não apresenta muita diferença das demais. Vale a pena mencionar que o método nativo Java retorna uma lista (generics) de DataObject e 'generics' é uma característica do compilador Java. Via JNI, a instância do ArrayList recebe instâncias de qualquer objeto. Neste caso uma exceção do tipo java.lang.ClassCastException ocorrerá somente quando houver uma iteração da lista.

JNIEXPORT jobject JNICALL Java_com_prototype_jni_DataLoader_loadList
  (JNIEnv *env, jobject) {
    
    jclass arrayListClassId = env->FindClass( "java/util/ArrayList" );  
    jmethodID arrayListContructorId = env->GetMethodID(arrayListClassId, "<init>", "()V");
    jobject arrayListObject = env->NewObject(arrayListClassId, arrayListContructorId);

    jmethodID addMethodID = env->GetMethodID(arrayListClassId, "add", "(Ljava/lang/Object;)Z");
          
    jclass dataObjectClassId = env->FindClass("com/prototype/jni/DataObject");   
    jmethodID dataObjectContructorId = env->GetMethodID(dataObjectClassId, "<init>", "()V");    
    jobject dataObject = NULL;
    
    jfieldID fid = env->GetFieldID(dataObjectClassId, "id", "J");
    
    for (int i = 0; i < 100000; i++) {
        dataObject = env->NewObject(dataObjectClassId, dataObjectContructorId);       
        env->SetLongField(dataObject, fid, (jlong) i);
        // Add to the list
        env->CallBooleanMethod(arrayListObject, addMethodID, dataObject);
    }
    
    return arrayListObject;
}

Testes

Três testes foram realizados para validar o código criado e computar o tempo para se criar 100.000 instância de DataObject via JNI.

No primeiro teste, o código JNI é invocado para cada instância criada do objeto, e esta instância é adicionada numa lista em Java.

O segundo teste obtém um array de instâncias de DataObject via JNI. Este array é então, em Java, convertido numa lista do tipo ArrayList (pois seria desta forma que o conjunto de instância seria manipulado no código posteriormente).

Finalmente, no terceiro teste, a lista de DataObject é criada via JNI e recuperada em Java.

O tempo calculado para cada teste varia de máquina para máquina e portanto é muito relativo. Criar o array de DataObjects é sempre mais rápido que criar a lista via JNI, mesmo convertendo em Java o array numa lista. Como era esperado, obter cada instância de DataObject via JNI para depois adicioná-las numa lista em Java é muito oneroso.

quinta-feira, 12 de dezembro de 2013

JNI: Java e C++ Num Único Projeto Maven

Desenvolver projetos que empreguem a tecnologia JNI necessariamente não significa ter um projeto para Java e um outro para C++ (ou C) separados. Com Maven e o plugin nar-maven-plugin é possível ter os dois projetos como se fossem um só. O plugin permite a compilação do código Java, a geração dos cabeçalhos JNI em C++/C, a compilação do código C++/C e a execução dos testes unitários, tudo isso com pouca configuração.

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:
  1. Os cabeçalhos JNI em C++/C são gerados automaticamente;
  2. Os códigos C++ e Java são compilados juntos;
  3. Os artefatos gerados, sendo o JAR e DLL, sempre estarão casados com o mesmo número de versão;
  4. 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;
  5. Projetos que dependem da biblioteca Java com JNI fazem apenas uma referência de dependência que inclui tanto o JAR quanto a DLL;
Cada item descrito acima merece uma descrição mais elaborada que será apresentada no restante do artigo. Outra grande vantagem, que não será explorada neste artigo, é possibilidade de gerar diferentes versões de DLL para cada tipo de plataforma de sistema operacional.

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:
Para o Maven, Mingw e Java, seus diretórios de instalação (com \BIN) devem ser incluídos manualmente no PATH do Windows.

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 projeto

 Packaging

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.

segunda-feira, 9 de dezembro de 2013

C++ e Maven: nar-maven-plugin

No meu trabalho, desenvolvo códigos em C, C++ e Java principalmente, além de outras linguagens. Para Java, já não consigo mais dispensar a utilização do Maven. Pesquisei sobre como usar Maven também para compilar códigos em C e C++. Foi então que encontrei o nar-maven-plugin.

Este plugin para Maven está atualmente ativo graças a uma grupo de desenvolvedores, mas nem sempre foi assim: ele esteve meio parado por um tempo e seu desenvolvedor original doou o código para um outro grupo de desenvolvedores continuar o trabalho. Tutoriais sobre o plugin ainda são escassos e muitos fazem referência a uma versão mais antiga e algumas configurações não se aplicam mais para a versão atual. O mailing list no momento tem poucas postagens, mas quando o assunto é relevante, os desenvolvedores estão sempre atentos e respondem com rapidez.

Uma outra opção é usar o native-maven-plugin. Este tem a sua última versão, a 1.0-alpha-7, liberada em março de 2011. Os tutoriais sobre este plugin são poucos e achei sua configuração pouco intuitiva. Por todos estes motivos desfavoráveis, optei por usar o nar-maven-plugin, principalmente por estar mais ativo no momento e ser mais um pouco mais fácil configurá-lo.

Objetivos dos Testes

A intenção aqui é criar dois projetos em C++, sendo um para gerar uma biblioteca estática e outro com uma biblioteca dinâmica que utiliza a biblioteca estática.

Os projetos são bem simples e estão estruturados de acordo com Maven 3. O foco está na utilização do nar-maven-plugin e não exatamente na utilização do Maven. Portanto, é esperado que se conheça o básico sobre Maven e sua utilização.

Preparação para os Testes

Os testes foram realizados no Windows 7 64 bits utilizando os seguintes sistemas:
  • Maven 3
  • Mingw 4.8.1
  • Nar-maven-plugin-3.0.0
    Tanto para o Maven quanto para o Mingw, os diretórios de instalação (com \BIN) devem ser incluídos manualmente no PATH do Windows.

    O nar-maven-plugin será baixado automaticamente pelo Maven de acordo com a configuração apresentada abaixo.

    Download

    O código dos projetos usados para testes estão disponíveis para download. São simples mas servem de modelo para evoluir para projetos maiores.


    Projeto Biblioteca Estática

    Este projeto serve de base para os demais. Abaixo, o arquivo pom.xml do projeto da biblioteca estática é apresentado com a configuração mínima para a utilização do nar-maven-plugin.

    <project xmlns="http://maven.apache.org/POM/4.0.0" 
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>prototype</groupId>
        <artifactId>lib-project</artifactId>
        <version>1.0-SNAPSHOT</version>
      
        <!-- Importante que a opção JAR seja substituída por NAR -->
        <packaging>nar</packaging>
    
        <name>lib-project</name>
    
        <!-- Repositório onde está armazenado o plugin -->
        <pluginRepositories>
            <pluginRepository>
                <id>Sonatype OSS</id>
                <url>http://oss.sonatype.org/content/groups/public</url>
            </pluginRepository>
        </pluginRepositories>
    
        <build>
            <plugins>            
                <plugin>
                    <groupId>com.github.maven-nar</groupId>
                    <artifactId>nar-maven-plugin</artifactId>
                    <version>3.0.0</version>
                    <extensions>true</extensions>
                    <configuration>
                        <!-- Importante definir o nome do compilador C++ caso não seja 
                             o default definido pelo NAR plugin de acordo com a plataforma. 
                             No caso da plataforma Windows, o default é o
                             Microsoft Visual C++. -->                                                                                                   
                        <cpp>                                                        
                            <name>g++</name>  
                        </cpp>
                        <!-- Idem para o linker -->             
                        <linker>
                            <name>g++</name>
                        </linker>
                        <!-- Definir o tipo de binário gerado, neste caso uma biblioteca estática -->
                        <libraries>
                            <library>
                                <type>static</type>
                            </library>
                        </libraries>                    
                    </configuration>                
                </plugin>
            </plugins>
        </build>
        
    </project>
    

    Alguns pontos devem ser observados aqui sobre o arquivo pom.xml:
    1. A configuração de packaging deve ser alterada de JAR para NAR (em letras minúsculas lá no XML), caso contrário o plugin não funcionará;
    2. A localização do repositório do plugin deve ser especificada; desde dezembro de 2013, o plugin tem sido armazenado no Sonatype OSS.
    3. O compilador padrão do plugin é de acordo com a plataforma: para Windows, o padrão é o Microsoft Visual C++ e portanto, caso o exemplo seja utilizado com este compilador, não é necessário definir o nome. Como o exemplo aqui está usando o Mingw, temos que definir o nome do compilador e linker como mostra a configuração no arquivo XML.
    4. Determinar o tipo de arquivo binário gerado, nesta caso uma biblioteca estática.
    Ainda, vale a pena citar a estrutura de arquivos que o plugin espera: por convenção, os arquivos fontes devem estar na pasta \src\main\c++ e os arquivos *.H que acompanham a biblioteca estática (e usados pela biblioteca dinâmica) devem para estar na pasta \src\main\include. É possível modificar ou acrescentar novas pastas. Consulte as opções de configuração oferecidas pelo plugin no site do projeto.

    Uma vez a compilação ocorra com sucesso, o plugin compacta a biblioteca estática e todos os arquivos contidos na pasta \src\main\include do projeto num único arquivo com a extensão NAR e os instala no repositório maven local.

    Projeto Biblioteca Dinâmica

    O arquivo pom.xml deste projeto é basicamente o mesmo do projeto anterior, com alteração no tipo de binário gerado e incluindo a dependência do projeto anterior para 'linkar'.

    <project xmlns="http://maven.apache.org/POM/4.0.0" 
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>prototype</groupId>
        <artifactId>shared-project</artifactId>
        <version>1.0-SNAPSHOT</version>
      
        <packaging>nar</packaging>
    
        <name>shared-project</name>
    
        <!-- Repositório onde está armazenado o plugin -->
        <pluginRepositories>
            <pluginRepository>
                <id>Sonatype OSS</id>
                <url>http://oss.sonatype.org/content/groups/public</url>
            </pluginRepository>
        </pluginRepositories>
    
        <dependencies>
            <!--  dependência do projeto biblioteca estática  -->
            <dependency>           
                <groupId>prototype</groupId>
                <artifactId>lib-project</artifactId>
                <version>1.0-SNAPSHOT</version>
                <!-- Importante definir o tipo como NAR -->
                <type>nar</type>
            </dependency>
        </dependencies>
      
        <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>
                        </linker>
                        <!-- Gera binário como biblioteca dinâmica -->
                        <libraries>
                            <library>
                                <type>shared</type>
                            </library>
                        </libraries>                    
                    </configuration>                
                </plugin>
            </plugins>
        </build>
        
    </project>
    

    Note que os arquivos *.H da biblioteca estática não precisam ser colocados explicitamente no projeto, conforme explicado anteriormente. Ao compilar um projeto que use esta dependência, o plugin descompacta o arquivo NAR da biblioteca estática e automaticamente inclui os arquivos *.H no caminho de pesquisa para o compilador. O mesmo acontece com o binário da biblioteca estática usado pelo linker.

    Considerações Finais

    A configuração do arquivo pom.xml para compilar código em C++ é muito simples com o nar-maven-plugin.

    O nar-maven-plugin recentemente voltou a ser mantido por um grupo de desenvolvedores e seu mailing list está ativo.

    A utilização do plugin é mais interessante quando se tem código Java que faz acesso código em C++ (ou em C) via JNI; o plugin é capaz de gerar automaticamente os headers e compilar juntos tanto o código Java quando o código em C++. Vou apresentar uma exemplo no próximo artigo.

    quinta-feira, 21 de março de 2013

    Layout Ajax com JSF

    Esta informação está contida em outros blogs mas decidi escrevê-la aqui também porque sempre achei informações incompletas, sendo um pouco num blog e outro pouco noutro.

    A ideia é criar um layout de website com área de cabeçalho, menu à esquerda e centro com conteúdo. Até aí é fácil e o Netbeans dá uma mão para a gente (na versão 7.3, crie um facelet e você verá). Neste layout, a intenção é permitir que o usuário clique no menu posicionado à esquerda e o conteúdo da página (no centro) seja atualizado sem que toda a página seja recarregada.

    Primeiro, é necessário criar um controlador (bean) para manter a página corrente a ser carregada. Veja, no código abaixo, a declaração bem simples do IndexController;  perceba que o atributo currentPage é inicializado com o nome da página com o conteúdo inicial do site.

    @ManagedBean
    public class IndexController {
    
        private String currentPage = "home.xhtml"; // página com conteúdo inicial
    
        public String getCurrentPage() {
            return currentPage;
        }
    
        public void setCurrentPage(String currentPage) {
            this.currentPage = currentPage;
        }
    }
    

    Supondo que a página inicial seja index.xhtml, com o layout do site devidamente estruturado (junto com CSS), insira o código abaixo na área reservada para a descrição do menu. Aqui, o menu está bastante simples e é basicamente composto por dois links onde cada um deve abrir as páginas inserir.xhtml e listar.xhtml.

    <h:form>
       <f:ajax render=":contentForm:reload">
          <h:commandlink action="#{indexController.setCurrentPage('inserir.xhtml')}" value="Inserir"/>
          <h:commandlink action="#{indexController.setCurrentPage('listar.xhtml')}" value="Listar"/>
       </f:ajax>
    </h:form>
    

    Ainda no mesmo arquivo index.xhtml, agora na área reservada para o conteúdo do website propriamente dito, insira o código abaixo.

    <h:form id="contentForm">
        <h:panelGroup id="reload">
            <ui:include src="#{indexController.currentPage}"/>
        </h:panelGroup>
    </h:form> 

    Os códigos demonstrados acima fazem referência à 3 arquivos: home.xhtml, inserir.xhtml e listar.xhtml. Estes arquivos precisam ser criados com o conteúdo específico. Abaixo está o exemplo de como tais arquivos devem ser criados:

    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets">
        <ui:composition>
             Coloque aqui o conteúdo a ser exibido no website! 
        </ui:composition>
    </html>
    

    Com esta abordagem, a URL do website não modifca toda vez que o usuário clica no menu!

    Agora suponha que o conteúdo exibido no website tenha um botão ou um link que leve à outra página. Naturalmente a ideia aqui é atualizar comente a área de conteúdo. O código abaixo mostra um exemplo de como seria isto:

    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:ui="http://java.sun.com/jsf/facelets"
          xmlns:h="http://java.sun.com/jsf/html"
          xmlns:f="http://java.sun.com/jsf/core">
        <ui:composition>
            <f:ajax render=":contentForm:reload">
                <h:commandLink value="Atualizar" action="#{indexController.setCurrentPage('/ui/alterar.xhtml')}"/>               
            </f:ajax>
        </ui:composition>
    </html>
    

    Veja que no código acima não está declarado a tag <h:form> para permitir a declaração do <h:commandLink>. Aqui, o form com o identificador 'contentForm' já fora delcarado em index.html e recebe toda a atualização da página e por isso não precisa ser declarado novamente.

    Referências:

    sexta-feira, 25 de janeiro de 2013

    Problema com Placa de Vídeo na Atualização do Windows 8

    Após a atualização do meu notebook para Windows 8, a placa de vídeo AMD Radeon não foi instalada corretamente e frequentemente apresentava erro no aplicativo ATI Catalyst Control Center.

    O gerenciador de dispositivos exibia o problema na placa de vídeo cujo nome era AMD Radeon HD 7000M. Baixei seu driver direto do site da Sony e também direto do site da AMD, todos sem sucesso.

    Todavia, meu notebook tem um aplicativo da Sony que faz atualização do sistema como um todo. Resolvi usar este aplicativo e para a minha surpresa, foi identificada uma outra versão da placa de vídeo sendo a AMD Radeon HD 6470M / 6630M.

    Devido a este erro no nome da placa de vídeo apresentado pelo gerenciado de dispositivos do Windows 8, eu erroneamente estava baixando e instalando a versão errada do driver da AMD Radeon HD.

    Fica aí a dica para aqueles que estão com o mesmo problema.

    Atualização para Windows 8

    Como profissional da área de tecnologia da informação, tenho a obrigação de experimentar as novidades do mercado, à medida do possível. Próximo do final do mês de janeiro de 2013, data limite para término da promoção de atualização do para o Windows 8, resolvi experimentar e mal sabia o que viria pela frente.


    Até então, estava com o Windows 7 Professional e Office 2010, tudo legalizado. Por causa do Office 2010, o Thunderbird (cheio de mensagens de e-mail de várias contas), outros softwares que adquiri e outros tantos gratuitos que estavam devidamente configurados, resolvi fazer a atualização para Windows 8 com a opção para manter dados de usuário e aplicativos instalados. O assistente de atualização da Microsoft indicava que somente um aplicativo não era compatível; então, comprei a licença do Windows 8 e comecei a instalação.

    Após longos minutos esperando a finalização do download, a instalação começou, foi até 90% e reiniciou a máquina. Para minha surpresa (que ingênuo!), houve uma mensagem após o reinício indicando "falha na instalação do Windows". O sistema recuperou a instalação do Windows 7 sem problemas (ufa!), mas não deu nenhum indicativo do problema encontrado.

    Vasculhei fóruns, inclusive em inglês, e encontrei uma legião de usuário do Windows na mesma situação com a mesma mensagem de erro, porém somente alguns mais insistentes conseguiram resolver o problema, mas cada um de uma forma diferente. Segui algumas dicas, mas continuei com a mesma mensagem de erro após tentar novamente e algumas vezes mais.

    Encontrei um artigo no site da Microsoft chamado "Noções Básicas de Falhas e Arquivos de Logs", onde entendi como poderia localizar arquivos de registros da instalação do Windows 8. Os arquivos não fornecem muitas informações objetivas para usuários, ou seja, eles podem servir para alguma coisa para o pessoal de desenvolvimento de suporte da Microsoft, mas não muito para nós. No meu caso, explorei o arquivo setuperr.log e entendi  que o erro era porque o sistema não localizava um determinado arquivo no sistema.Veja abaixo alguns registros de erro:

    2013-01-23 20:37:33, Error                 CONX   [CDeviceBiosBlocks::Generate] Unable to open SDB C:\Users\ALEXAN~1\AppData\Local\Temp\8f2f2112-0329-428a-bb95-b48bc77e51d0\WebSetupExpanded\drvmain.sdb.[gle=0x000000cb]
    2013-01-23 20:37:33, Error                 CONX   [wmain] [0x80004005] QueryAppBlock encountered an error gathering bios blocks.
    2013-01-23 20:37:33, Error                 CONX   [ConX::Compatibility::Wica::GetCompatibilityInfo] Query bios block returned an error - 1.[gle=0x00000715]
    

    Pelo caminho e nome do arquivo indicado no registro, verifiquei que o arquivo não existia mesmo no meu sistema.

    Apelei então para a instalação usando mídia de DVD. O assistente de atualização permite que seja gerado um arquivo no formato ISO para depois gerar a mídia de instalação. Aqui, é necessário ter um software adequado para gravar o arquivo ISO como um DVD (eu tenho o Ashampoo Burning Studio). Comecei agora a instalação pelo DVD e consegui avançar; o error gerado antes não mais surgiu, todavia um novo problema surgiu no mesmo estilo do anterior: "falha na instalação do Windows", sem mais detalhes. Abri de novo o mesmo arquivo de registros de erro e identifiquei um novo problema:

    2013-01-24 21:57:27, Error                        Error READ, 0x0000000B while gathering/applying object: File, C:\Users\Alexandre\AppData\Local\Microsoft\Windows Mail. Will return 0
    2013-01-24 21:57:27, Error                 MIG    Error 11 while applying object C:\Users\Alexandre\AppData\Local\Microsoft\Windows Mail. Shell application requested abort
    2013-01-24 21:57:27, Error      [0x08097b] MIG    Abandoning apply due to error for object: C:\Users\Alexandre\AppData\Local\Microsoft\Windows Mail
    2013-01-24 21:57:28, Error                        pUpgLayerDoOnlineApply: Apply operation failed. Error: 0x0000002C
    

    O máximo que entendi destes registros de erro tudo foi um problema qualquer na pasta de dados de aplicativo do Windows Mail. Nem sabia o que era isso, mas supus que fosse alguma coisa ligada ao Windows Live, o qual desinstalei imediatamente, uma vez que ele também não é compatível com o Windows 8. Ainda, fui bisbilhotar a pasta indicada no registro de erro e percebi que ela estava diferente das outras, com as letras (fonte) em verde. Esta característica indicava que a pasta estava criptografada (o Windows 7 Professional permite isso, mas não as versões inferiores do mesmo Windows). Desfiz a criptografia e comecei novamente a atualização para Windows 8.

    Finalmente consegui completar a instalação e configuração do Windows 8, depois de vários dias e muitas horas de persistência e paciência. A fabricante do meu notebook (Sony) disponibilizou um pacote de drivers para Windows 8, além de um excelente tutorial de como fazer a migração para Windows 8; baixei o pacote e fiz a instalação. Vale aqui um elogio para a Sony pela facilidade em localizar, baixar e instalar todos os drivers para meu notebook.

    Quanto a Microsoft, fica o desapontamento quanto ao suporte técnico; nos fóruns da própria empresa, nenhuma dica realmente útil vinda dos próprios profissionais da Microsoft. As  informações relevantes geralmente têm sido publicadas pelos próprios usuários do sistema que conseguiram resolver alguns dos problemas encontrados.

    Quanto ao Windows 8, me acostumei rápido com a nova interface gráfica e achei o sistema um pouco mais rápido, comparado ao Windows 7. Os aplicativos instalados anteriormente parecem realmente compatíveis com o novo sistema operacional; estava especialmente preocupado com o Office 2010, pois é uma versão de 32 bits num sistema de 64 bits e encontrei vários relatos de incompatibilidade - por sorte, não foi o meu caso!