Acessar um .dll usando JNI

Fala pessoal,
Li bastante coisa aqui no fórum e na internet, mas não to entendendo aonde está dando errado.

Eu preciso criar um programa em Java que carregue uma .dll de uma câmera digital (criada em C++), e use uma função dela, chamada InicializaSDK, e estou usando JNI pra isso.

Tentei criar uma wrapper para carregar, mas tá dando um erro… aí vai o que eu tentei fazer:

1- Primeiro criei o…
Inicia.java:

[code]public class Inicia{
public native void InicializaSDK();

public static void main (String[] args){
	Inicia c = new Inicia();
	c.InicializaSDK();
}

static{
	System.load("C://j2sdk1.4.2_12//bin//CDSDK.dll");
}

}[/code]

2- Depois, compilei com o javac e usei o javah pra criar o…
Inicia.h:

[code]#include <jni.h>
/* Header for class Inicia */

#ifndef _Included_Inicia
#define _Included_Inicia
#ifdef __cplusplus
extern “C” {
#endif
/*

  • Class: Inicia
  • Method: InicializaSDK
  • Signature: ()V
    */
    JNIEXPORT void JNICALL Java_Inicia_InicializaSDK
    (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif
[/code]

3- Então, escrevi o arquivo…
Inicia.cpp:

[code]#include <jni.h>
#include <stdio.h>
#include “Inicia.h”

JNIEXPORT void JNICALL Java_Inicia_InicializaSDK
(JNIEnv *env, jobject obj){
return InicializaSDK();
}[/code]

E daí quando tento usar o “gcc -shared Inicia.cpp -o CDSDK.dll” ele dá erro.
Alguém sabe onde eu estou errando, ou o quê eu devo fazer?
Valeu, abração!

Eu não queria ser chato, mas você tem como usar o Microsoft Visual Studio 2003 ou 2005? É que não sei se é possível escrever uma DLL JNI com o mingw (gcc para Windows).

Teria sim! Ou com o DevC++ também.

Eu vou postar aqui uma DLL JNI que fiz faz algum tempo com o MS VC++ 6.0. O 2003 ou 2005 são parecidos, você só precisa abrir uma linha de comando (No caso do Visual Studio 2005, Start -> Programs -> Microsoft Visual Studio 2005 -> Tools -> Visual Studio Tools -> Visual Studio 2005 Command Prompt, e ele abrirá uma linha de comando em que você pode usar o “cl” (compilador de linha de comando), “nmake”, etc.

TESTEJNI.CPP

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "TesteJNI.h"

/**
 * @param env O JNI Environment
 * @param isto "this" do Java
 * @param mo Um objeto MeuObjeto.
 * @return Só para enfeitar, vamos retornar o comprimento de mo.cadeia, em caracteres.
 */
JNIEXPORT jint JNICALL Java_TesteJNI_alteraObjeto
  (JNIEnv *env, jobject isto, jobject mo) {
    // Queremos simplesmente fazer 
    // mo.numero = mo.numero + 10
    // mo.cadeia = mo.cadeia + "!!!!"
    
    //-- Puxar a classe MeuObjeto
    jclass jcMeuObjeto = env->GetObjectClass (mo);
    //-- Puxar os campos:
    jfieldID jfiNumero = env->GetFieldID(jcMeuObjeto, "numero", "I");
    jfieldID jfiCadeia = env->GetFieldID(jcMeuObjeto, "cadeia", "Ljava/lang/String;");
    //-- Puxar os valores e efetuar as atribuições correspondentes:
    int numeroAntigo = env->GetIntField (mo, jfiNumero);
    int numeroNovo = numeroAntigo + 10;
    env->SetIntField (mo, jfiNumero, numeroNovo);
    
    jstring jsCadeia = (jstring) env->GetObjectField (mo, jfiCadeia);
    const char *str = env->GetStringUTFChars (jsCadeia, NULL);
    const char *bangs = "!!!!";
    // Vamos fazer a concatenação em C - você ainda se lembra como fazer isso?
    char *result;
    if (str == NULL) {
        //-- Vamos imitar o comportamento do Java, e usar a string "null"
        result = (char*) calloc (1, strlen ("null") + strlen (bangs) + 1); 
        strcpy (result, "null");
        strcat (result, bangs);
    } else {
        result = (char*) calloc (1, strlen (str) + strlen (bangs) + 1);
        strcpy (result, str);
        strcat (result, bangs);
        env->ReleaseStringUTFChars (jsCadeia, str);
    }
    //-- Agora efetuando a atribuição
    jsCadeia = env->NewStringUTF (result);
    free (result);
    env->SetObjectField (mo, jfiCadeia, jsCadeia);
    
    
    //-- Retornando 0 só para enfeitar
    int lenCadeia = 0;
    return lenCadeia;
}

TESTEJNI.H

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class TesteJNI */

#ifndef _Included_TesteJNI
#define _Included_TesteJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     TesteJNI
 * Method:    alteraObjeto
 * Signature: (LMeuObjeto;)I
 */
JNIEXPORT jint JNICALL Java_TesteJNI_alteraObjeto
  (JNIEnv *, jobject, jobject);

#ifdef __cplusplus
}
#endif
#endif
class MeuObjeto {
    public int numero;
    public String cadeia;
    public String toString () {
        return "numero = " + numero + ", cadeia = " + cadeia;
    }
}

class TesteJNI {
    public native int alteraObjeto (MeuObjeto obj);
    
    /**
     * Compilar com (se for usar o Microsoft Visual C++ 6.0):
     * javac TesteJNI.java
     * javah TesteJNI
     * cl -LD -MT -I%JAVA_HOME%\include -I%JAVA_HOME%\include\win32 TesteJNI.cpp
     *    jvm.lib -link /libpath:%JAVA_HOME%\lib
     */
    public static void main(String[] args) {
        System.loadLibrary ("testejni");
        MeuObjeto mo = new MeuObjeto();
        TesteJNI tjni = new TesteJNI();
        mo.numero = 10;
        mo.cadeia = "Alo, mundo";
        System.out.println ("Antes: " + mo);
        tjni.alteraObjeto (mo);
        System.out.println ("Depois: " + mo);
    }
}

Use as opções de linha de comando especificadas no comentário do programa Java.

Thingol,
Valeu pela ajuda velho!
Mas então, a minha idéia era apenas chamar aquela função (void: void) já existente na CDSDK.dll (essa, uma .dll já existente também, instalada com o sw da câmera digital, provavelmente não foi criada usando os argumentos de JNI), e não preciso fazer nenhuma operação com essa função (pelo menos por enquanto não), só chamar sua execução.

E isso eu não captei ainda… e isso que me falaram de criar a wrapper.
Any idea?

Nada ainda aqui =/

Pois é, eu postei algo que eu já tinha, só para lhe mostrar as opções de compilação.

Sim sim, essa parte de compilar eu “sei”.
O que me falta é isso de acessar uma .dll já existente pra usar uma função dela.
Sabe alguma maneira Thingol?

Você conhece suficientemente C? Basicamente você tem de chamar a tal função da DLL a partir da DLL JNI que você vai criar.

Se a tal DLL veio com um arquivo .H e um arquivo .LIB é fácil chamar o método InicializaSDK a partir de seu programa C.
Senão, você terá de chamar as APIs do Windows “LoadLibrary”, “GetProcAddress”, e “FreeLibrary”.

Conheço o suficiente pra fazer isso sim acho.
E a DLL veio sim com .H e o .LIB, mas eu nunca manipulei .LIB, não sei como fazê-lo, e não sei como (ou se) devo “linkar” o arquivo DLL JNI com o DLL que contém a função.

O que eu consegui imaginar foi o que eu postei aqui mesmo, mas sei que meu arquivo .cpp está errado, e acho que é exatamente por não estar “linkando” com o DLL ou com o LIB que você disse.

To meio perdido nessa parte.

Então tá.

  1. Crie uma classe Java com apenas um método estático (digamos que seja “public native void inicializaSdk();”), e talvez um método estático para carregar a DLL (System.loadLibrary ou System.load), e a compile.
  2. Use o javap para criar o arquivo .h.
  3. Crie um arquivo em C ou C++ que inclua o .h criado no passo 1, e que implemente o tal método que está definido no .h. Esse arquivo em C ou C++ deve incluir também o .h que define o tal método InicializeSDK do seu toolkit.
  4. Compile esse arquivo em C ou C++ e o linke com
    o tal .LIB que você recebeu. Isso irá gerar a tal DLL JNI.

Parte 1) Classe .java criada

[code]public class Inicia{
public native void InicializaSDK();

public static void main (String[] args){
	Inicia c = new Inicia();
	c.InicializaSDK();
}

static{
	System.load("C://j2sdk1.4.2_12//bin//CDSDK.dll");
}

}[/code]

Parte 2) Arquivo .h criado com o javah

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Inicia */

#ifndef _Included_Inicia
#define _Included_Inicia
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     Inicia
 * Method:    InicializaSDK
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_Inicia_InicializaSDK
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

Parte 3) Arquivo .cpp (essa parte eu não tenho certeza se está certa; eu copiei a implementação da função InicializaSDK() que estava no arquivo .cpp (o original, onde está a implementação dessa função) e coloquei em formato de JNI, mas não tenho certeza se é essa mesma a idéia. Acho que a intenção é acessar a InicializaSDK() direto da .dll ou da .lib que eu possuo, e não copiar sua implementação pra um novo .cpp, aí que eu estou me perdendo)

[code]#include <jni.h>
#include <stdio.h>
#include <windows.h>
#include “cdAPI.h” //header que define “cdError”, “cdVersionInfo”, “cdHEnum”, “CDStartSDK()”, “CDGetSDKVersion()”, “CDFinishSDK()”
#include “Inicia.h” //header criado usando o javah

JNIEXPORT void JNICALL Java_Inicia_InicializaSDK
(JNIEnv *env, jobject obj){

 //daqui pra baixo está a copia da implementação da função InicializaSDK(), encontrada no .cpp original.
 static HWND   Form1;
 cdError err;
 cdVersionInfo Ver; 
 cdHEnum hEnumD;
  
 memset(&Ver, 0, sizeof(cdVersionInfo));
 
 char temp[256];
 
 
 Ver.Size = sizeof(cdVersionInfo);
 Ver.MajorVersion = 7;
 Ver.MinorVersion = 3;

 err = CDStartSDK(&Ver, 0); 
 
 err=CDGetSDKVersion(&Ver);
 sprintf(temp,"size:%d major:%d minor:%d release:%d chVersion:%s",Ver.Size,Ver.MajorVersion,Ver.MinorVersion,Ver.ReleaseVersion,Ver.chVersion);


 SetWindowText(Form1,temp);    

 if(err != cdOK) 
   CDFinishSDK();

}
[/code]

Parte 4) Compilei o projeto e linkei com a .LIB e a .A já existentes, assim criando a .dll, mas quando eu rodo (usando javac):

Exception in thread "main" java.lang.UnsatisfiedLinkError: InicializaSDK at Inicia.InicializaSDK(Native Method) at Inicia.main(Inicia.java:6)

Eu estou errando no passo 3 mesmo? Eu tenho que fazer a chamada diretamente da minha .LIB ou da .DLL? Ou estou fazendo corretamente?
Abraço!

Opa!
Meu orientador disse que a idéia é só chamar a função InicializaSDK() mesmo, e não declarar tudo de novo.

Dessa forma o passo 3 ficaria:

[code]#include <stdio.h>
#include <jni.h>
#include “Inicia.h”

JNIEXPORT void JNICALL Java_Inicia_InicializaSDK
(JNIEnv *env, jclass cls){
InicializaSDK();
return;
}
[/code]

Só que assim ele não compila, diz que ‘InicializaSDK’ undeclared (first use this function) =/
E ah Thingol, eu tenho a .DLL, a .LIB e a .A; a .H que eu tenho define alguns métodos do corpo da função InicializaSDK() (CDStartSDK() e CDFinishSDK() como visto no post anterior), mas não nenhum que defina a própria função que eu desejo.

Abraço!

Hum…
Você está mostrando um arquivo .A. O que ele está fazendo aí?
Qual é o compilador que você está usando? Nunca vi ninguém dizendo que teve sucesso ao usar o mingw (gcc para Windows) para escrever DLLs JNI, por exemplo.

O .A eu coloquei junto como parâmetro apenas pq estava na mesma pasta que o .LIB, não sei se é correto fazer isso.
Estou criando a .DLL JNI com o DevC++ mesmo.

Pra linkar a .LIB e a .A eu cliquei em “Opções de Projeto”, depois na aba “Parâmetros”, e na janela “Linker” eu adicionei ambos e dei OK, só isso ehehehhe

Não tenho a menor idéia como se usa o DevC++. Por acaso isso é uma IDE que usa o mingw?

Na verdade eu nunca usei o mingw.
O DevC++, quando você escolhe “Novo Projeto” ele te pergunta o que você quer criar (um executável de Windows, uma .A, uma .DLL, etc), e nessa hora eu escolho por criar uma .DLL, adiciono um arquivo .H (criado usando o javah) e o .C ou .CPP (que é onde eu devo chamar a função, e logo, onde eu to com problema) e compilo o projeto, assim criando uma .DLL.

Eu estava fazendo os exemplos do tutorial da Sun e funcionou pra todos a .DLL criada, mas nenhum desses exemplos chamava uma função de uma .DLL já existente.

Tente colocar a DLL no path. Sabe aquele erro básico que a gente tem no inicio de não conseguir chamar o ‘javac’ por não está no PATH? É mais ou menos o que acontece aqui. O carregamento da DLL é problema do SO, então ele precisa saber onde procurar as DLLs.

:slight_smile:

Cuidado ao usar MinGw e Cygwin. Eles servem básicamente, não somente pra isso, para compilar e executar códigos desenvolvidos para linux. Então, quando vc gera uma lib através destes dois, você estará gerando uma no padrão linux, tem como faze-la rodar em windows, mas é muita complicação.

Aqui vou tirar minha camiseta preta da Microsoft da gaveta e pedir para você baixar o MS Visual Studio (C++) 2005 Express. Realmente não consigo lhe ajudar com DLLs JNI geradas pelo DevC++. A Sun gera o JDK com o compilador da Microsoft (para o JDK 6 eles usam o Visual Studio .NET 2003, se não me engano).