2011-11-17 14 views
18

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

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

Ahora, estoy intentando generar a partir de 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 era capaz de averiguar cómo decirle TRAGO que es una interfaz) una clase simple, con los dos métodos y un constructor por defecto/destructor:

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?

+0

¿Por qué? Java ya tiene API JSON, solo use una de las muchas disponibles. –

+0

@ChrisDennett: ya estoy usando esta biblioteca para otros usos en C++. Tengo otras bibliotecas para portar en un futuro próximo, así que tendré el mismo problema con ellas. –

+0

No entiendo, ¿qué quieres que genere aquí? SWIG genera proxies que coinciden con las declaraciones y definiciones que usted lo muestra, que es lo que se hace aquí. ¿Es un caso de [este problema] (http://www.swig.org/Doc1.3/Java.html#adding_downcasts)? Puedo darte un ejemplo concreto si haces un poco más claro lo que estás buscando. – Flexo

Respuesta

44

Puede lograr lo que está buscando con SWIG + Java utilizando "Directors", 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".

La implementación de una interfaz de C++ en Java

Dado un fichero 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 terminar con esto y hacer que funcione de manera intuitiva desde el lado de Java. Podemos hacer esto mediante la definición de 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); 
    } 
    } 
%} 

directores Aquí hemos habilitado para que todo el módulo, y luego pidió que se utilizan para class Interface específicamente. 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 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: ~/código/cero/trago/javaintf> java Ejecute
¡Hola de Java!

Si usted es feliz con él que ni abstract ni un interface se puede dejar de leer aquí, los directores hacen todo lo que necesita.

¿Por qué SWIG genera un class en lugar de un interface?

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

  1. TRAGO proporciona el sistema mecánico para llamar delete, la manipulación de la cPtr etc. en el lado de Java. Eso no se pudo hacer en un interface en absoluto.
  2. Considérese el caso en que nos envuelve la siguiente función:

    Interface *find_interface(); 
    

    Aquí TRAGO sabe nada más sobre el tipo de retorno de eso, es de tipo 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 algún lugar tendrá que haber una llamada al new Interface, que no sería posible/legal si Interface fuera abstracto en el lado de Java.

Posible solución

Si usted estaba esperando para dar esto como una interfaz con el fin de expresar una jerarquía de tipos con herencia múltiple en Java esto sería bastante limitante. Hay una solución alternativa, sin embargo:

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

    public interface Interface { 
        public String foo(); 
    } 
    
  2. Modificar el archivo de interfaz SWIG:

    1. Cambiar el nombre de la clase de C++ Interface 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 Interface en código C++ SWIG será ahora utilizando NativeInterface como el tipo en el lado de Java. necesitamos typemaps para mapear este NativeInterface en los parámetros de función en la interfaz Interface Java hemos añadido manualmente.
    3. Marcos NativeInterface como la implementación de Interface para hacer el comportamiento lado de Java natural y creíble para un usuario de Java
    4. Tenemos que proporcionar un poco de código adicional que puede actuar como un proxy para las cosas que implementan el Java Interface sin ser un NativeInterface también.
    5. Lo pasamos a C++ debe ser siempre un NativeInterface aún así, no todos los Interface s serán uno, aunque (aunque todos NativeInterfaces habrá), por lo que ofrecen un poco de pegamento para hacer Interface s se comportan como NativeInterfaces, y una typemap de aplicar ese pegamento .(Ver this document para una discusión de la pgcppname)

    Esto se traduce en un archivo de módulo que ahora se ve así:

    %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 úsala 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 se ejecuta como se esperaría:

AJW @ Rapunzel: ~/código/cero/trago/javaintf> Java se ejecutan
Hola desde Java!
Hola de C++

Y hemos envuelto una clase abstracta de C++ como una interfaz en Java exactamente como un programador de Java se puede esperar!

+0

Probablemente puedas automatizar la escritura de la clase 'NativeIntefaceProxy' usando algo como [this] (http://stackoverflow.com/questions/3291637/alternatives-to-java-lang-reflect-proxy-for-creating-proxies- of-abstract-classes) – Flexo

+3

¡Exactamente lo que necesitaba, y tan bien explicado! Muchas gracias. –

+2

Creo que es probable que SWIG 3.1 cambie sustancialmente esta respuesta cuando se lance: https://github.com/swig/swig/blob/master/Lib/java/swiginterface.i – Flexo

Cuestiones relacionadas