JNI - Java Native Interface
Por Caio Paes
(caio.paes@ccc.ufcg.edu.br)
Entenda o uso de mais uma palavra reservada de Java: native. Veja como Java pode utilizar funções implementadas em outras linguagens de programação.

A Coluna Java deste mês tenta esclarecer os "segredos" de mais uma palavra chave não tão utilizada pelos programadores Java. Trata-se da palavra reservada native. Essa palavra é usada na declaração de métodos que não são implementados em nenhuma classe Java. Ela serve para indicar à Java Virtual Machine (JVM) que, quando o método for chamado, o código que se deseja executar está em uma biblioteca, implementada com linguagem de programação nativa de um sistema, normalmente C e C++.

O uso da palavra reservada native faz parte, portanto, da camada de interface da plataforma Java com bibliotecas nativas, a Java Native Interface (JNI). A JNI é utilizada para realizar comunicação com sistemas legados, quando não existem bindings (ligação/conexão de mapeamento) de bibliotecas para a linguagem Java e quando há necessidade de realizar operações com melhor desempenho.

A seguir, veja um exemplo de como utilizar a JNI para executar operações em um código Java, utilizando uma biblioteca escrita em C++.

HelloWorldJNI.java

package petnews.colunajava;
public class HelloWorldJNI {
        
        private String javaString;
        
        HelloWorldJNI() {
                javaString = "Eu fui instanciada no codigo Java!";
        }
        
        // Declaracaoo dos metodos nativos
        
        /**
         * Metodo implementado na linguagem C++ que retorna uma String dado um valor inteiro passado como parametro.
         */
        public native String mensagem(int i);
        
        /**
         * Metodo implementado na linguagem C++ que modifica o valor da variavel 'javaString'.
         */
        public native void mudarCampoJavaString();
        public String getJavaString() {
                return this.javaString;
        }
        
        static {
                System.loadLibrary("helloworldnativo");
        }
        
        public static void main(String[] args) {
                HelloWorldJNI hello = new HelloWorldJNI();
                System.out.println(hello.getJavaString());
                System.out.println(hello.mensagem(1));
                System.out.println(hello.mensagem(2));
                System.out.println(hello.mensagem(3));
                hello.mudarCampoJavaString();
                System.out.println(hello.getJavaString());
        }
}

Além do uso da palavra reservada native, na declaração dos métodos, observe a necessidade de um bloco que abra a biblioteca que utilizaremos. Nesse caso, os métodos String mensagem(int) e void mudarCampoJavaString() possuem suas implementações na biblioteca helloworldnativo. O próximo passo consiste em extrair um cabeçalho que fará o mapeamento entre as assinaturas dos métodos Java nativos e a sua implementação em C/C++. Para isso, utilizaremos a ferramenta javah, disponível com o Java Development Kit (JDK).

No terminal, acesse o diretório raiz do seu código-fonte e utilize o comando javah para gerar o cabeçalho da sua biblioteca nativa. Veja o exemplo a seguir.

$ javah -classpath bin/ petnews.colunajava.HelloWorldJNI

O cabeçalho gerado tem o nome petnews_colunajava_HelloWorldJNI.h e o seguinte código:

petnews_colunajava_HelloWorldJNI.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class petnews_colunajava_HelloWorldJNI */
#ifndef _Included_petnews_colunajava_HelloWorldJNI
#define _Included_petnews_colunajava_HelloWorldJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     petnews_colunajava_HelloWorldJNI
 * Method:    mensagem
 * Signature: (I)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_petnews_colunajava_HelloWorldJNI_mensagem
  (JNIEnv *, jobject, jint);
/*
 * Class:     petnews_colunajava_HelloWorldJNI
 * Method:    mudarCampoJavaString
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_petnews_colunajava_HelloWorldJNI_mudarCampoJavaString
  (JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif

O cabeçalho acima apresenta o mapeamento dos tipos utilizados no programa Java para os tipos presentes na linguagem C/C++, definidos no cabeçalho jni.h.

De posse da assinatura das funções que devem ser implementadas, o próximo passo consiste em escrever a sua implementação.

helloWorldnativo.cpp

#include <stdio.h>
#include "petnews_colunajava_HelloWorldJNI.h"
#include <jni.h>
JNIEXPORT jstring JNICALL Java_petnews_colunajava_HelloWorldJNI_mensagem
(JNIEnv *env, jobject obj, jint ji) {
    
    printf("%s\n", "Passei pela funcao nativa Java_petnews_colunajava_HelloWorldJNI_mensagem!");
    
    // Cria um jstring (definido em jni.h)
    jstring result;
    
    switch (ji) {
        case 1:
            result = env->NewStringUTF("Um!");
            break;
        case 2:
            result = env->NewStringUTF("Dois!");
            break;
        case 3:
            result = env->NewStringUTF("Tres!");
            break;
        default:
            result = env->NewStringUTF("Sei lah!");
            break;
    }
    
    return result; //retorna o jstring
}
JNIEXPORT void JNICALL Java_petnews_colunajava_HelloWorldJNI_mudarCampoJavaString
(JNIEnv *env, jobject obj) {
    
    // jclass obtem uma referencia para a classe Java do obj => HelloWorldJNI
    jclass ref_class = env->GetObjectClass(obj);
    
    // jfield obtem uma referencia para o atributo "javaString", usando a referencia anterior.
    jfieldID fid = env->GetFieldID(ref_class, "javaString", "Ljava/lang/String;");
    
    // Criamos um jstring com o novo valor que sera atribuido
    jstring novo_valor = env->NewStringUTF("Eu fui instanciada no codigo C++");
    
    // O codigo abaixo altera o valor do atributo javaString para "Eu fui instanciada no codigo C++".
    env->SetObjectField(obj, fid, novo_valor);
    
}

As funções são bem simples. A primeira recebe um inteiro e retorna o número, por extenso, se ele for 1, 2, ou 3. Nos demais casos, a string retornada é "Sei lah!". A segunda função faz uma operação mais interessante e curiosa. Ela modifica o valor da variável javaString que é de acesso privado na classe Java HelloWorldJNI.

Agora, quando o código de helloworldnativo.cpp for compilado em uma biblioteca nativa, ele estará pronto e será utilizado quando o programa Java executar.

Veja, a seguir, o exemplo de saída obtido quando executa-se o main da classe HelloWorldJNI

Eu fui instanciada no codigo Java!
Passei pela funcao nativa Java_petnews_colunajava_HelloWorldJNI_mensagem!
Um!
Passei pela funcao nativa Java_petnews_colunajava_HelloWorldJNI_mensagem!
Dois!
Passei pela funcao nativa Java_petnews_colunajava_HelloWorldJNI_mensagem!
Tres!
Eu fui instanciada no codigo C++

Você executou o código, não modificou nada e sua saída foi diferente? Não se preocupe. O uso do stream de escrita é compartilhado para a execução do código java e do código presente na biblioteca. Daí, as impressões na tela podem ter ordens diferentes.

O uso da JNI se mostra necessário, em alguns casos, como mencionado anteriormente. Vale lembrar, que o seu uso traz algumas desvantagens, tais como: perda de portabilidade da aplicação e a necessidade do aprendizado de outra linguagem de programação.

Jornal PETNews - Edição: Jeymisson Oliveira - Revisão: Savyo Nóbrega e Joseana Fechine
Grupo PET Computação UFCG, 2012. All rights reserved.