java c++ jni swig

Generando interfaz Java con SWIG



c++ jni (1)

Puede lograr lo que está buscando con SWIG + Java usando " Directors ", sin embargo, no es tan sencillo el mapeo de las clases abstractas de C ++ a Java como podría esperar. Por lo tanto, mi respuesta se divide en tres partes: en primer lugar, el ejemplo simple de implementar una función virtual pura de C ++ en Java, en segundo lugar, una explicación de por qué la salida es así y, en tercer lugar, una "solución alternativa".

Implementando una interfaz de C ++ en Java

Dado un archivo de cabecera ( module.hh ):

#include <string> #include <iosfwd> class Interface { public: virtual std::string foo() const = 0; virtual ~Interface() {} }; inline void bar(const Interface& intf) { std::cout << intf.foo() << std::endl; }

Nos gustaría envolver esto y hacerlo funcionar intuitivamente desde el lado de Java. Podemos hacer esto definiendo la siguiente interfaz SWIG:

%module(directors="1") test %{ #include <iostream> #include "module.hh" %} %feature("director") Interface; %include "std_string.i" %include "module.hh" %pragma(java) jniclasscode=%{ static { try { System.loadLibrary("module"); } catch (UnsatisfiedLinkError e) { System.err.println("Native code library failed to load. /n" + e); System.exit(1); } } %}

Aquí hemos habilitado directores para todo el módulo, y luego pedimos que se usen específicamente para la class Interface . Aparte de eso, y mi código favorito de "cargar el objeto compartido automáticamente" no hay nada particularmente notable. Podemos probar esto con la siguiente clase de Java:

public class Run extends Interface { public static void main(String[] argv) { test.bar(new Run()); } public String foo() { return "Hello from Java!"; } }

Luego podemos ejecutar esto y ver que funciona como se espera:

ajw @ rapunzel: ~ / code / scratch / swig / javaintf> java Run
Hola desde Java!

Si está satisfecho con que no sea abstract ni una interface que pueda dejar de leer aquí, los directores hacen todo lo que necesita.

¿Por qué SWIG genera una class lugar de una interface ?

Sin embargo, SWIG ha convertido lo que parecía una clase abstracta en una clase concreta. Eso significa que en el lado de Java podríamos escribir legalmente una new Interface(); , lo que no tiene sentido. ¿Por qué SWIG hace esto? La class ni siquiera es abstract , por no hablar de una interface (vea el punto 4 here ), que se sentiría más natural en el lado de Java. La respuesta es doble:

  1. SWIG suministra mecánicos para llamar a delete , manipular el cPtr etc. en el lado de Java. Eso no se puede hacer en una interface en absoluto.
  2. Considere el caso donde envolvimos la siguiente función:

    Interface *find_interface();

    Aquí SWIG no sabe nada más sobre el tipo de retorno que el tipo de Interface . En un mundo ideal, sabría cuál es el tipo derivado, pero solo a partir de la firma de la función no hay forma de resolverlo. Esto significa que en el Java generado en algún lugar tendrá que haber una llamada a la new Interface , lo que no sería posible / legal si la Interface fuera abstracta en el lado de Java.

Posible solución

Si esperara proporcionar esto como una interfaz para expresar una jerarquía de tipos con herencia múltiple en Java, esto sería bastante limitante. Hay una solución sin embargo:

  1. Escriba manualmente la interfaz como una interfaz Java adecuada:

    public interface Interface { public String foo(); }

  2. Modificar el archivo de interfaz SWIG:

    1. Cambie el nombre de la Interface clase C ++ para que sea NativeInterface en el lado de Java. (Deberíamos hacerlo visible solo para el paquete en cuestión, con nuestro código envuelto viviendo en un paquete propio para evitar que las personas hagan cosas "locas".
    2. En todas partes tenemos una Interface en código C ++. SWIG ahora utilizará NativeInterface como el tipo en el lado de Java. Necesitamos mapas de tipos para asignar este NativeInterface en los parámetros de función a la Interface interfaz de Java que agregamos manualmente.
    3. Marque NativeInterface como Interface implementación para hacer que el comportamiento de Java sea natural y creíble para un usuario de Java.
    4. Necesitamos proporcionar un poco de código adicional que pueda actuar como un proxy para cosas que implementan la Interface Java sin ser también una NativeInterface .
    5. Lo que pasamos a C ++ siempre debe ser un NativeInterface , aunque no todas las Interface s serán una (aunque todas las NativeInterfaces serán), así que proporcionamos algo de pegamento para hacer que Interface s se comporte como NativeInterfaces , y un mapa de tipos para aplicar ese pegamento. (Vea este documento para una discusión del pgcppname )

    Esto da como resultado un archivo de módulo que ahora parece:

    %module(directors="1") test %{ #include <iostream> #include "module.hh" %} %feature("director") Interface; %include "std_string.i" // (2.1) %rename(NativeInterface) Interface; // (2.2) %typemap(jstype) const Interface& "Interface"; // (2.3) %typemap(javainterfaces) Interface "Interface" // (2.5) %typemap(javain,pgcppname="n", pre=" NativeInterface n = makeNative($javainput);") const Interface& "NativeInterface.getCPtr(n)" %include "module.hh" %pragma(java) modulecode=%{ // (2.4) private static class NativeInterfaceProxy extends NativeInterface { private Interface delegate; public NativeInterfaceProxy(Interface i) { delegate = i; } public String foo() { return delegate.foo(); } } // (2.5) private static NativeInterface makeNative(Interface i) { if (i instanceof NativeInterface) { // If it already *is* a NativeInterface don''t bother wrapping it again return (NativeInterface)i; } return new NativeInterfaceProxy(i); } %}

Ahora podemos envolver una función como:

// %inline = wrap and define at the same time %inline %{ const Interface& find_interface(const std::string& key) { static class TestImpl : public Interface { virtual std::string foo() const { return "Hello from C++"; } } inst; return inst; } %}

y utilízalo como:

import java.util.ArrayList; public class Run implements Interface { public static void main(String[] argv) { ArrayList<Interface> things = new ArrayList<Interface>(); // Implements the interface directly things.add(new Run()); // NativeInterface implements interface also things.add(test.find_interface("My lookup key")); // Will get wrapped in the proxy test.bar(things.get(0)); // Won''t get wrapped because of the instanceOf test test.bar(things.get(1)); } public String foo() { return "Hello from Java!"; } }

Esto ahora funciona como esperas:

ajw @ rapunzel: ~ / code / scratch / swig / javaintf> java Run
Hola desde Java!
Hola de C ++

¡Y hemos envuelto una clase abstracta de C ++ como una interfaz en Java exactamente como lo esperaría un programador de Java!

Estoy usando SWIG para hacer un contenedor Java de una biblioteca de C ++ (sobre la serialización Json (de)) para usarlo en Android. Definí una clase abstracta en C ++, que representa un objeto que puede ser (des) serializado:

class IJsonSerializable { public: virtual void serialize(Value &root) = 0; virtual void deserialize(Value &root) = 0; };

Ahora, estoy tratando de generar desde esta clase una interfaz Java. Aquí está mi interfaz SWIG:

%module JsonSerializable %{ #include "JsonSerializable.hpp" %} %import "JsonValue.i" class IJsonSerializable { public: virtual void serialize(Value &root) = 0; virtual void deserialize(Value &root) = 0; };

Pero el código Java generado (obviamente, como no pude averiguar cómo decirle a SWIG que es una interfaz) una clase simple, con los dos métodos y un constructor / destructor predeterminado:

public class IJsonSerializable { private long swigCPtr; protected boolean swigCMemOwn; public IJsonSerializable(long cPtr, boolean cMemoryOwn) { swigCMemOwn = cMemoryOwn; swigCPtr = cPtr; } public static long getCPtr(IJsonSerializable obj) { return (obj == null) ? 0 : obj.swigCPtr; } protected void finalize() { delete(); } public synchronized void delete() { if (swigCPtr != 0) { if (swigCMemOwn) { swigCMemOwn = false; JsonSerializableJNI.delete_IJsonSerializable(swigCPtr); } swigCPtr = 0; } } public void serialize(Value root) { JsonSerializableJNI.IJsonSerializable_serialize(swigCPtr, this, Value.getCPtr(root), root); } public void deserialize(Value root) { JsonSerializableJNI.IJsonSerializable_deserialize(swigCPtr, this, Value.getCPtr(root), root); } }

¿Cómo puedo generar una interfaz válida con SWIG?