Friday, 3 July 2009

Hacer una llamada a un método de una DLL utilizando Jawin

Java no dispone de una librería propia para el acceso a las DLL (Dinamyc Linking Library). Para ello, utilizaremos Jawin, una librería que nos permite poder trabajar con los métodos de una librería. Java dispone del JNI (Java Native Interface), que es un framework que permite a java interactuar con otros programas escritos en otros lenguajes. Podemos utilizar JNI para interaccionar con los métodos de la DLL, pero es bastante complicado de utilizar. En éste artículo, veremos lo sencillo que es utilizar la librería de Jawin. Para mi ejemplo, he creado una pequeña DLL con delphi, donde hay un método que suma dos enteros y devuelve el resultado. Primero, necesitamos descargar la última versión de la librería Jawin. En éste enlace, podemos descargar la última versión: jawin 2.0-alpha1. Una vez descargada, la descomprimimos y la dejamos en la C:\, luego veremos el porqué.

Primero, crearé la DLL de delphi. Aquí os dejo el código fuente:




library Project1;

uses
SysUtils,
Classes;

{$R *.res}

function AddIntegers(_a, _b: integer): integer; stdcall;
begin
Result := _a + _b;
end;

exports
AddIntegers;

begin
end
.




Como podéis comprobar, es realmente sencillo generar una DLL, simplemente hay que marcar que métodos queremos exportar y listo!.

Para poder visualizar los métodos de las DLL, yo utilizo 2 herramientas muy buenas: DLL Export Viewer y Dependency Walker. Las 2 son gratuitas, y permiten visualizar los métodos que tenemos dentro de la DLL.

En el caso de mi dll, aquí os dejo el pantallazo de la visualización con las 2 aplicaciones:

Luego, para la implementación en Java, crearé un proyecto con Eclipse y añadiré allí las librerías necesarias para la aplicación.

El proyecto tiene el siguiente aspecto:

He añadido las librerías jawin.jar y jawin-stubs.jar a mi proyecto. Si vamos a la C:\jawin-2.0-alpha1 encontraremos los ficheros y las librerías necesárias. Además tenemos que tener en cuenta la librería jawin.dll, que luego os diré como hay que utilizarla.

Ahora, el código fuente del ejemplo:




package test;

import org.jawin.COMException;
import org.jawin.FuncPtr;
import org.jawin.ReturnFlags;

public class AddNumbers {
public static void main(String[] args) throws Exception {
FuncPtr addNum = null;

try {
addNum = new FuncPtr("Project1.dll", "AddIntegers");

Integer g = addNum.invoke_I(3, 5, ReturnFlags.CHECK_HRESULT);
System.out.println("Suma = " + g);
} catch (COMException e) {
e.printStackTrace();
throw e;
} finally {
if (addNum != null) {
try {
addNum.close();
} catch (COMException e) {
e.printStackTrace();
throw e;
}
}
}
}
}




Como podéis ver, hago la llamada a mi dll, y espero el resultado de la suma de los valores. Como resultado tendriamos que ver que la aplicación devuelve lo siguiente:

Loading jawin from hardcoded path C:\jawin-2.0-alpha1\bin\jawin.dll
Suma = 8

Antes de ejecutar el programa, tenemos que vincular la dll de jawin con nuestra aplicación, y ésto hay que hacerlo de la siguiente manera:

Cuando hacemos Run Configurations, tenemos que introducir la siguiente línea en los argumentos de la maquina virtual:

-Dorg.jawin.hardlib=C:\jawin-2.0-alpha1\bin\jawin.dll

De esta manera le estamos diciendo a la aplicación dónde tiene la dll del jawin. Como podéis ver, es bastante sencillo de utilizar, y podemos encontrar un montón de ejemplos dentro de la librería. La otra batalla importante que hay con esta librería es con el tema del los Strings, ya que los métodos que tiene, devuelven matrices de bytes, y tenemos que parametrizar la salida mediante la longitud del String. Buscaré un ejemplo un poco interesante y lo publicaré.
Espero que os sirva de ayuda!.
  • Enlaces de interés:
Error con la JNI.

13 comments:

  1. ing_vmgc@hotmail.com14 June 2012 at 20:32

    hola..... necesito implementar este JNI con una dll que me pide 2integer y 1cadena como parametros pero no .invoke debo usar.... sera con el que tiene byte[] arg0..... pero no recuerdo como incluir 3 valores a un byte :S
    gracias

    ReplyDelete
    Replies
    1. Hola,

      Mira el ejemplo de Jawin, a lo mejor te da una pista de como hacerlo: Jawin Userguide

      Delete
  2. hola jordi lo que quiero es llamar a una rchivo dll pero sin darle la ruta si no cargarlo desde el mismo pakete de java ejemplo estoy cargando archivo .dll asi System.load("C:/Windows/avas.dll"); lo que quiero es cargarlo desde la misma aplicacion java osea como agregar un .jar una libreria comos e hace normalmente sera que se puede? yo lo estoy haciendo de esta manera System.load(package.avas.Avast.class.getResource("avas.dll").getFile());

    pero me sale un error alguna ayuda gracias

    ReplyDelete
    Replies
    1. Hola edith,

      La libreria debe estar disponible en java.library.path. Mira como tienes tu path montado y copia tu libreria alli. Una vez hecho, podras utilizar el System.load("avas.dll").

      Mas informacion aqui:
      Load library linux

      Delete
    2. Hola Jordi, esta muy bueno el post, lo probé y funcionó perfecto. Ahora estoy tratando de llamar a una DLL que tiene un metodo que recibe 3 String,aqui esta la definicion en Visual Basic : Private Declare Function Decrypt Lib "crypt32" (ByVal sOrigen As String, ByVal sDestino As String, ByVal sKey As String) As Integer, no existe ningun invoke en los que reciba estos parametros no obstante lo implemente usando LittleEndianOutputStream y despues de escribir los 3 String me quedaria el llamado a la funcion asi:

      FuncPtr msgBox = = new FuncPtr("crypt32.dll", "Decrypt");

      NakedByteStream nbs = new NakedByteStream();

      LittleEndianOutputStream leos = new LittleEndianOutputStream(nbs);

      leos.writeStringUnicode("c:\\metodo.txt");
      leos.writeStringUnicode("c:\\metodo-encry.txt");
      leos.writeStringUnicode("lolo");

      msgBox.invoke("GGG:I:", 12, nbs, null, ReturnFlags.CHECK_FALSE);

      sin embargo cuando se hace la llamada a la DLL solo me esta tomando el primer caracter del String que estoy intentando pasarle, tienes alguna idea de cual sea la manera en que debe estar codificado los bytes para que pueda llegar la cadena completa a la DLL?

      Gracias de antemano

      Delete
    3. Hola Iskael,

      Pues deberia funcionar. Mira la siguiente entrada a ver si ves alguna cosa que te ayude o que no mandes bien los argumentos:
      jawinuserguide.dll

      Jordi

      Delete
  3. Hola que tal, estoy tratando de desarrollar un sistema que utiliza una conexión a una Pocket PC.
    Para poder acceder a esta pocket, utilizo un archivo dll que me permite copiar archivos.
    Lo he desarrollado en visual c# y el código necesario en este lenguaje para su funcionamiento es de la siguiente manera:

    (En la parte del encabezado)
    using OpenNETCF.Desktop.Communication;

    (en la parte del desarrollo)

    OpenNETCF.Desktop.Communication.RAPI jar = new RAPI();
    RAPI myRAPI = new RAPI();
    myRAPI.Connect();

    (y por último, utilizo las siguientes lineas para copiar en el dispositivo y del dispositivo al equipo)

    jar.CopyFileOnDevice(ruta2, ruta3, true);
    jar.CopyFileFromDevice(ruta, ruta3, true);

    Ahora bien, ¿qué debo hacer para que esto pueda funcionar en un código java usando jawin?

    Ojalá pudieras ayudarme.
    Un saludo.

    ReplyDelete
    Replies
    1. Hola Eduardo,

      En teoría tendría que funcionar de la misma manera. Una vez creas tu DLL, solo tienes que crear tu proyecto Java y hacer las llamadas de jawin a tu DLL. Debes exponer tus metodos para que sean publicos para que las llamadas a estos sean posibles.

      Jordi

      Delete
  4. Hola Jordi,

    Buscando información de como hacer llamadas a librerias dll desde Java he llegado a tu blog. He estado haciendo pruebas tal y como comentas y me he encontrado con el siguiente problema, que las dll de jawin estan compiladas en 32 bits y mi sistema es de 64, ¿existe una versión de 64?. Me he dado cuenta al probar a hacer un System.load de las mismas, ya que con la llamada new FuncPtr , el error que me daba era java.lang.UnsatisfiedLinkError: C:\apli\bin: Can't find dependent libraries y me estaba volviendo loco.

    De no existir una version de 64 bits, ¿que otras opciones tengo?

    Un saludo y muchas gracias

    Manu

    ReplyDelete
    Replies
    1. Hola Manu,

      Pues como no cojas y te bajes el codigo fuente y lo compiles para 64 bits no se como se puede hacer. El proyecto se modificó por última vez hace 9 años asi que habrá que buscar alternativas.

      Jawin Source code

      Delete
    2. Hola Jordi,

      En primer lugar gracias por la pronta respuesta. Lo que estoy intentando es cambiar simplemente la maquina virtual de java y poner una de 32 bits en lugar de la de 64 y hasta he cambiado el eclipse de 64 por el de 32. Ahora al hacer el load del Jawin.dll ya no me salta el error de que no puede hacerlo por ser de 32 bits, asi que entiendo que se soluciona. Esto solo lo hacía para probar si eran compatibles. Ahora el problema que tengo es la famosa excepción:

      Loading jawin from hardcoded path C:\apli\bin
      Exception in thread "main" java.lang.UnsatisfiedLinkError: C:\apli\bin: Can't find dependent libraries
      at java.lang.ClassLoader$NativeLibrary.load(Native Method)
      at java.lang.ClassLoader.loadLibrary1(Unknown Source)
      at java.lang.ClassLoader.loadLibrary0(Unknown Source)
      at java.lang.ClassLoader.loadLibrary(Unknown Source)
      at java.lang.Runtime.load0(Unknown Source)
      at java.lang.System.load(Unknown Source)
      at org.jawin.Bootstrap.(Bootstrap.java:37)
      at org.jawin.FuncPtr.(FuncPtr.java:79)
      at Main.main(Main.java:47)

      En esa ruta tengo guardadas tanto las dll que realmente voy a usar como la de jawin.dll y esto si que ni idea de porque falla, cualquier sugerencia que se te ocurra será una maravilla, muchas gracias!

      Un saludo

      Manu

      Delete
    3. Hola de nuevo, habia cometido un error en el último post y no había completado la ruta del jawin.dll, el problema que me queda ahora es que al hacer la llamada metodo = new FuncPtr("C:\\apli/bin/glot_inv.dll", "glot_inv"); me da un error de espacio :

      Loading jawin from hardcoded path C:\apli\bin\jawin.dll
      org.jawin.COMException: 80070008: Espacio de almacenamiento insuficiente para procesar este comando.

      Si hago un load de la librería me sucede lo mismo.

      Saludos

      Manu

      Delete
  5. Tengo una aplicacion en visual basic que utiliza una .dll, en una de sus funciones le envia los siguientes parametros R= iwared(a String, b long, c string, d long). algo como esto.

    luego la respuesta que se obtiene es c string y d long. R=0; significa que se conecto pues.

    la pregunta es la siguiente, puedo desde java, utilizando jawin u otra libreria comunicarme a esa .dll llamar a la funcion tal cual como es su nombre, pasandole los mismos parametros para obtener esa misma respuesta?

    Y si puedes aclararme la duda de lo que hablas encuanto a string el parametrizar la salida mediante la longuitud del string.

    ReplyDelete