Problema com DLL - JNI [RESOLVIDO]

Bom dia,

Estou com um problema ao carregar uma DLL para uma catraca da Topdata. O fabricante nos forneceu uma dll, e no manual há exemplos em VB, C, C# e Delphi, mas nenhum em Java. Toda a vez que tento chamar algum método, dá essa exceção:

Exception in thread "main" java.lang.UnsatisfiedLinkError: turnstile.ControlaCatraca.InicializaCom(II)I
        at turnstile.ControlaCatraca.InicializaCom(Native Method)
        at turnstile.ControlaCatraca.main(ControlaCatraca.java:26)
Java Result: 1

Abaixo o código:

package turnstile;

public class ControlaCatraca {
    
    static {
        //System.loadLibrary("Inner2K");
        System.load("C:\\WINDOWS\\system32\\Inner2K.dll");
    }

    private static native int InicializaCom(int Porta,int Velocidade);
    private static native int Envia_Dados(int inner, byte[] BufferComm, int Comando, int num, int protocolo);    
    private static native int Recebe_Dados(int inner, byte[] BufferComm, int Comando, int num, int protocolo);
    private static native void FinalizaCom();
    
    public static void main(String[] args) {
        System.out.println("java.library.path="+System.getProperty("java.library.path"));
        int isInicializado = InicializaCom(5432, 300);
    }
}

Como a DLL foi fornecida pelo fabricante e não usei o javah, baixei esse programa, Anywhere PE Viewer para ver as assinaturas dos métodos:

Export table
Characteristics=0, TimeDateStamp=1135082476, MajorVersion=0, MinorVersion=0, Name=36788, Base=1, NumberOfFunctions=14, NumberOfNames=14, AddressOfFunctions=36648, AddressOfNames=36704, AddressOfNameOrdinals=36760,
TimeDateStamp:Tue Dec 20 10:41:16 BRST 2005
CloseComm (Ordinal: 1, Entry Point RVA: 1e30h (7.728))
Envia_Dados (Ordinal: 2, Entry Point RVA: 1c50h (7.248))
Envia_Resposta (Ordinal: 3, Entry Point RVA: 1d60h (7.520))
FinalizaCom (Ordinal: 4, Entry Point RVA: 1c40h (7.232))
InicializaCom (Ordinal: 5, Entry Point RVA: 1c00h (7.168))
InicializaComModem (Ordinal: 6, Entry Point RVA: 1c20h (7.200))
Le_Modem (Ordinal: 7, Entry Point RVA: 1da0h (7.584))
Modem_Str (Ordinal: 8, Entry Point RVA: 1d80h (7.552))
OpenComm (Ordinal: 9, Entry Point RVA: 1db0h (7.600))
Recebe_Dados (Ordinal: 10, Entry Point RVA: 1c80h (7.296))
Recebe_Dados_E_Nao_Responde (Ordinal: 11, Entry Point RVA: 1d30h (7.472))
Recebe_Dados_Magic (Ordinal: 12, Entry Point RVA: 1cb0h (7.344))
ReceiveData (Ordinal: 13, Entry Point RVA: 1e00h (7.680))
SendData (Ordinal: 14, Entry Point RVA: 1dd0h (7.632))
Generated with Anywhere PE Viewer/APEVPX (http://www.ucware.com/)

Agora pergunto: o que pode estar acontecendo?

Atenciosamente,

Marco

Opa,

É o seguinte.:

Se você tentar usar a DLL do fabricante diretamente
não vai funcionar, porque os métodos que estão contidos
nessa DLL não satisfazem o padrão de assinatura reconhecido pelo JNI.
A solução seria ou recompilar a DLL adequando as assinaturas dos métodos, porém acho que você não tem acesso ao código fonte, ou fabricar uma DLL que sirva de interface entre o seu código Java e a DLL do fabricante. Desta forma DLL de interface, seria customizada por você.

[quote=Dejava]Opa,

É o seguinte.:

Se você tentar usar a DLL do fabricante diretamente
não vai funcionar, porque os métodos que estão contidos
nessa DLL não satisfazem o padrão de assinatura reconhecido pelo JNI.
A solução seria ou recompilar a DLL adequando as assinaturas dos métodos, porém acho que você não tem acesso ao código fonte, ou fabricar uma DLL que sirva de interface entre o seu código Java e a DLL do fabricante. Desta forma DLL de interface, seria customizada por você.
[/quote]

Tem razão, não tenho o fonte. A segunda opção que vc mencionou, de uma DLL chamando outra DLL, vc já fez isso? Quero dizer, dá certo?

Dá um pouco de trabalho e pode dar certo. (Não posso dizer “vai dar certo” porque JNI sempre envolve efetuar conversões de dados chatas e outras coisas chatas).

Se puder usar uma biblioteca chamada Jenie ( http://www.servertec.com/products/jenie/jenie.html ) você pode chamar a sua DLL sem ter de criar você mesmo o código JNI, que requer um certo grau de “expertise” em C e Windows.

Dá certo sim, eu já fiz isso com sensores opticos de
impressões digitais. Fiz uma DLL que acessava a DLL do
fabricante do sensor. Você vai ter que manjar um pouco de
C/C++ também. Quanto as conversões de tipos, você
pode dar uma olhada nesses tutoriais.:

http://www.javafree.org/javabb/viewtopic.jbb?t=13913
http://homepages.dcc.ufmg.br/~bigonha/Cursos/Ap/Native/JavaNativeMethod.html
http://gcc.gnu.org/java/papers/native++.html
http://java.sun.com/j2se/1.5.0/docs/guide/jni/index.html

[quote=thingol]Dá um pouco de trabalho e pode dar certo. (Não posso dizer "vai dar certo" porque JNI sempre envolve efetuar conversões de dados chatas e outras coisas chatas).

Se puder usar uma biblioteca chamada Jenie ( http://www.servertec.com/products/jenie/jenie.html ) você pode chamar a sua DLL sem ter de criar você mesmo o código JNI, que requer um certo grau de "expertise" em C e Windows. [/quote]

Thingol, achei a idéia de acessar a DLL sem JNI muito interessante, além de tentadora. Fiz essa classe usando a Jenie, mas como não tenho o fonte da DLL, fica difícil adivinhar o que está acontecendo. Por exemplo, o método enviaDados() sempre retorna 21, indicando que não houve resposta da catraca.

A minha dúvida é: No método enviaDados(), tem um parâmetro buffer do tipo String. Quando passo isso para a Jenie, devo fazer dessa maneira: new Pointer(new AnsiString(buffer))? É de outra forma, ou to errando em alguma outra parte?

PS: Testei os exempos que vem com a Jenie, muito bom mesmo. []s

public class JenieCatraca {
    
    public static void main(String[] args) throws Exception {
        
        if(inicializaCom(1, 300)) {
            System.out.println("Conectou!");
        } else {
            System.out.println("!@%$&!@#");
        }
        
        // montando a string de buffer a ser enviado para a catraca
        String buffer = "1010500300000014";
        while(buffer.length() &lt 50) {
            buffer = buffer.concat("0");
        }
        
        long retorno = enviaDados(1, buffer, 100, 50, 0);
        if(retorno == 31) {
            System.out.println("Enviou!");
        } else {
            System.out.println("!@%$&!@# - retorno = " + retorno);
        }
    }
    
    /**
     * Inicializa a porta serial do PC e deve ser chamada antes da comunicação com o Inner.
     *
     * @param porta Porta que se deseja utilizar para comunicação com os Inners (1 a 4)
     * @param velocidade Taxa de transmissão. Valores válidos 300, 2400 e 9600
     * @return true - se a porta foi inicializada corretamente;
     *         false - não foi possível inicializar a porta.
     */
    public static boolean inicializaCom(int porta, int velocidade) 
            throws NativeException {
        
        Dll dllCatraca = null;
        BOOL retorno = null;
        try {
            dllCatraca = new Dll("InnerTCP2");
            
            retorno = new BOOL();
            dllCatraca.getFunction("InicializaCom").call(new UINT32(2), new UINT32(300), retorno);
            
        } finally {
            dllCatraca.release();
        }
        
        return retorno.getValue();
    }
    
    /**
     * Utilizada para enviar comandos e buffers de dados para o Inner.
     *
     * @param nrInner O número da catraca (1 a 32)
     * @param buffer Buffer a ser transmitido
     * @param comando Comando de transmissão
     * @param num Número de bytes do buffer a ser transmitido
     * @param protocolo Protocolo de comunicação do equipamento. Utilizar como igual a 0 (zero)
     * @return 1 - se a porta de comunicação não foi inicializada antes;
     *         21 - se não recebeu resposta;
     *         31 - se transmitiu OK;
     *         33 - se ocorreu um problema na transmissão.
     */
    public static long enviaDados(int nrInner, String buffer, int comando, long num, int protocolo)
            throws NativeException {
        
        Dll dllCatraca = null;
        UINT32 retorno = null;
        try {
            dllCatraca = new Dll("InnerTCP2");
            
            retorno = new UINT32();
            dllCatraca.getFunction("Envia_Dados").call(
                    new NativeParameter[] {new UINT32(nrInner), new Pointer(new AnsiString(buffer)), 
                    new UINT32(comando), new ULONG(num), new UINT32(protocolo)}, retorno);
        } finally {
            dllCatraca.release();
        }
        
        return retorno.getValue();
    }
}

[quote=Dejava]Dá certo sim, eu já fiz isso com sensores opticos de
impressões digitais. Fiz uma DLL que acessava a DLL do
fabricante do sensor. Você vai ter que manjar um pouco de
C/C++ também. Quanto as conversões de tipos, você
pode dar uma olhada nesses tutoriais.:

http://www.javafree.org/javabb/viewtopic.jbb?t=13913
http://homepages.dcc.ufmg.br/~bigonha/Cursos/Ap/Native/JavaNativeMethod.html
http://gcc.gnu.org/java/papers/native++.html
http://java.sun.com/j2se/1.5.0/docs/guide/jni/index.html
[/quote]

Olá Dejava, também estou num projeto com leitoras de impressão digital, verificando esta e depois liberando a catraca.

Estava dando uma olhada na Jenie que o Thingol indicou, mas quero tentar também com JNI, pois não tenho certeza se funcionou com a minha DLL.

Vc teria algum exemplo de código em C/C++ em que chama uma DLL, pode ser a de biometria mesmo? O meu C/C++ é de faculdade e nem tenho idéia de como chamar uma DLL usando C/C++.

Valeu pelos links. []s

Só uma pergunta - como é a declaração desse método nas linguagens suportadas (C, C#, VB, Delphi)? Deve haver alguma sutileza para poder usar a Jenie.

[quote=thingol][quote]
Por exemplo, o método enviaDados() sempre retorna 21, indicando que não houve resposta da catraca.
[/quote]
Só uma pergunta - como é a declaração desse método nas linguagens suportadas (C, C#, VB, Delphi)? Deve haver alguma sutileza para poder usar a Jenie. [/quote]

Thingol, abaixo as assinaturas dos métodos:

Visual Basic 6:

Public Declare Function InicializaCom Lib “inner2k.dll” _
(ByVal Porta As Long, ByVal velocidade As Long) As Long

Public Declare Sub FinalizaCom Lib “inner2k.dll” ()

Public Declare Function Envia_Dados Lib “inner2k.dll” _
(ByVal inner As Long, ByVal Buffer As String, _
ByVal Comando As Long, ByVal num As Long, ByVal protocolo As Long) As Long

Public Declare Function Recebe_Dados Lib “inner2k.dll” _
(ByVal inner As Long, ByVal Buffer As String, _
ByVal Comando As Long, ByVal num As Long, ByVal protocolo As Long) As Long

Delphi:

function InicializaCom(nPorta,
nVelocidade: integer): boolean; stdcall; external ‘Inner2K.DLL’;

procedure FinalizaCom; stdcall; external ‘Inner2K.DLL’;

function Envia_Dados(nInner : integer;
pBuffer : pointer;
nComando : integer;
nNumBytes : longint;
nProtocolo : integer): integer; stdcall; external ‘Inner2K.DLL’;

function Recebe_Dados(nInner : integer;
pBuffer : pointer;
nComando : integer;
nNumBytes : longint;
nProtocolo : integer): integer; stdcall; external ‘Inner2K.DLL’;

Visual Basic .NET:

Public Declare Function InicializaCom Lib “INNER2K.DLL” (ByVal Porta As Integer, _
ByVal Velocidade As Integer) As Integer

Public Declare Function Envia_Dados Lib “INNER2K.DLL” (ByVal Inner As Integer, ByVal _
Buffer As String, ByVal Comando As Integer, ByVal Num As Integer, ByVal Protocolo As _
Integer) As Integer

Public Declare Function Recebe_Dados Lib “INNER2K.DLL” (ByVal Inner As Integer, _
ByVal Buffer As String, ByVal Comando As Integer, ByVal Num As Integer, ByVal _
Protocolo As Integer) As Integer

Public Declare Sub FinalizaCom Lib “INNER2K.DLL”

C#:

[DllImport(“inner2k.dll”)]

Private Static extern int InicializaCom(int Porta,int Velocidade);

Private Static extern int Envia_Dados(int inner, byte[] BufferComm, int Comando, int
num, int protocolo);

Private Static extern int Recebe_Dados(int inner, byte[] BufferComm, int Comando, int
num, int protocolo);

Private Static extern void FinalizaCom();

Visual C++ 6.0:

//Prototipos das funcoes da dll
typedef int (CALLBACK *makeInicializaCom)(int,int);
typedef int (CALLBACK *makeEnviaDados) (int,unsigned char[],int,int,int);
typedef int (CALLBACK *makeRecebeDados) (int,unsigned char[],int,int,int);
typedef int (CALLBACK *makeFinalizaCom) (void);
//Handle para a dll
HMODULE hInnerDll;
//Handles para cada funcao da dll ja formatada igual aos prototipos
makeInicializaCom fInicializaCom;
makeEnviaDados fEnviaDados;
makeRecebeDados fRecebeDados;
makeFinalizaCom fFinalizaCom;
hInnerDll = (HMODULE) LoadLibrary("Inner2K");
if(hInnerDll){
fInicializaCom=(makeInicializaCom)GetProcAddress(hInnerDll,"InicializaCom");
fEnviaDados =(makeEnviaDados) GetProcAddress(hInnerDll,"Envia_Dados");
fRecebeDados =(makeRecebeDados) GetProcAddress(hInnerDll,"Recebe_Dados");
fFinalizaCom =(makeFinalizaCom) GetProcAddress(hInnerDll,"FinalizaCom");
if((!fInicializaCom) || (!fFinalizaCom) || (!fEnviaDados) || (!fRecebeDados)){
FreeLibrary(hInnerDll);
}
}
Para chamar as funções em seu código proceda da seguinte forma:
Ret = (*fInicializaCom)(int Porta, int Velocidade);
Ret = (*fEnviaDados)(int Inner, unsigned char *BufferComm, int Comando, int num, int
protocolo);
Ret = (*fRecebeDados) (int Inner, unsigned char *BufferComm, int Comando, int num, int
protocolo);
(*fFinalizaCom)();

Dá a impressão (embora nunca tenha mexido com a Jenie) que no seu caso você não precisa fazer isto:

new Pointer(new AnsiString(buffer)),

(você estaria passando um ponteiro para a referência a uma string - algo como char ** buffer)

mas sim isto:

new AnsiString(buffer),

(algo como char *buffer). Mas é só um chute.

[quote=thingol]Dá a impressão (embora nunca tenha mexido com a Jenie) que no seu caso você não precisa fazer isto:

new Pointer(new AnsiString(buffer)),

(você estaria passando um ponteiro para a referência a uma string - algo como char ** buffer)

mas sim isto:

new AnsiString(buffer),

(algo como char *buffer). Mas é só um chute.
[/quote]

Thingol, obrigado por tentar ajudar, mas a classe AnsiString não implementa a interface NativeParameter. As classes que a implementam são: BOOL, BYTE, CHAR, DOUBLE, FLOAT, HANDLE, INT, INT16, INT32, INT64, INT8, LONG, Pointer, SHORT, UCHAR, UINT, UINT16, UINT32, UINT64, UINT8, ULONG, USHORT, WCHAR.

Vou dar uma olhada nesses CHAR, UCHAR e WCHAR, apesar de achar que deve ser ponteiro mesmo.

Também vou tentar com JNI mesmo, com uma DLL chamando outra DLL. Depois posto o resultado. []s

Consegui!!! :smiley:

Apenas para constar no fórum, a solução com JNI, chamando uma DLL através de outra DLL funcionou perfeitamente e finalmente consegui conversar com a catraca.

Obrigado a todos que ajudaram respondendo. []s

Como vc conseguiu as assinaturas com os parâmetros?

não consegui ver isso no Anywhere PE

Os parâmetros não podem ser obtidos com esse programa, porque não são gravados na DLL. É necessário obter o arquivo .H (header de compilação para a linguagem C ou C++).

E ai galera…
To com esse problema tmb…

Estive lendo várias vezes, porém ainda não intendi como funciona. Tem como me explicarem passo a passo de como isso será feito.
Estou até com dor de cabeça já…
A parte de ligar uma dll com JNI eu não intendi nada!!!

Vlw galera!!!

Tente usar o JNA criando um interface que extende de StdCallLibrary. Baixe o pacote JNA e adicione ao seu projeto.

public interface NomeInterface extends StdCallLibrary
{
NomeInterface INSTANCE = (NomeInterface) Native.loadLibrary(“SUA_DLL”, NomeInterface.class);

            //Funções nativas da DLL
public int Opendll();
public int Escreverl();

}

Provavelmente irá funcionar !