Generando una interfaz Java con SWIG

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

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

Ahora, bash 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 es (obviamente, ya que 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?

Puede lograr lo que está buscando con SWIG + Java utilizando ” Directores “, sin embargo, no es tan sencillo el mapeo de las clases abstractas de C ++ en Java como es de esperar. Mi respuesta, por lo tanto, se divide en tres partes: en primer lugar, el ejemplo simple de implementación de una función virtual pura de C ++ en Java; en segundo lugar, una explicación de por qué el resultado es así y, en tercer lugar, un “trabajo alternativo”.

Implementando una interfaz C ++ en Java

Dado un archivo de encabezado ( module.hh ):

 #include  #include  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  #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 solicitamos que se utilicen específicamente para 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!"; } } 

Entonces podemos ejecutar esto y ver que está funcionando como se esperaba:

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

Si está contento 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 concreta. Eso significa que en el lado de Java podríamos legalmente escribir una new Interface(); que no tiene sentido ¿Por qué SWIG hace esto? La class no es ni siquiera abstract , y mucho menos una interface (Ver el punto 4 aquí ), que se sentiría más natural en el lado de Java. La respuesta es doble:

  1. SWIG proporciona mecanismos para llamar a delete , manipular el cPtr etc. en el lado de Java. Eso no se pudo hacer en una interface en absoluto.
  2. Considere el caso donde envolvemos 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 por la firma de la función no hay forma de que lo descubra. Esto significa que en el Java generado en alguna parte 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 esperaba proporcionar esto como una interfaz para express una jerarquía de tipos con herencia múltiple en Java, esto sería bastante limitante. Sin embargo, hay una solución:

  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. (Debemos hacerlo visible solo para el paquete en cuestión también, con nuestro código empaquetado viviendo en un paquete propio para evitar que la gente haga cosas "locas".
    2. En todas partes tenemos una Interface en código C ++ SWIG ahora usará NativeInterface como el tipo en el lado de Java. Necesitamos mapas de tipos para mapear esta NativeInterface en parámetros de funciones en la Interface la Interface Java que agregamos manualmente.
    3. Mark NativeInterface como Interface implementación para hacer que el comportamiento de Java sea natural y creíble para un usuario de Java.
    4. Necesitamos suministrar un poco de código adicional que pueda actuar como un proxy para las cosas que implementan la Interface Java sin ser también una NativeInterface .
    5. Lo que pasamos a C ++ debe ser siempre una NativeInterface , aunque no todas las Interface serán una (aunque todas las NativeInterfaces harán), así que proporcionamos algo de pegamento para hacer que Interface comporte como NativeInterfaces , y un mapa de tipos para aplicar ese pegamento. (Consulte este documento para una discusión sobre el nombre de pgcppname )

    Esto da como resultado un archivo de módulo que ahora se ve así:

     %module(directors="1") test %{ #include  #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 ajustar 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 úsalo como:

 import java.util.ArrayList; public class Run implements Interface { public static void main(String[] argv) { ArrayList things = new ArrayList(); // 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 se ejecuta como esperarías:

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

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