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:
- SWIG suministra mecánicos para llamar a
delete
, manipular elcPtr
etc. en el lado de Java. Eso no se puede hacer en unainterface
en absoluto. 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 lanew Interface
, lo que no sería posible / legal si laInterface
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:
Escriba manualmente la interfaz como una interfaz Java adecuada:
public interface Interface { public String foo(); }
Modificar el archivo de interfaz SWIG:
- Cambie el nombre de la
Interface
clase C ++ para que seaNativeInterface
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". - 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 esteNativeInterface
en los parámetros de función a laInterface
interfaz de Java que agregamos manualmente. - Marque
NativeInterface
comoInterface
implementación para hacer que el comportamiento de Java sea natural y creíble para un usuario de Java. - 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 unaNativeInterface
. - Lo que pasamos a C ++ siempre debe ser un
NativeInterface
, aunque no todas lasInterface
s serán una (aunque todas lasNativeInterfaces
serán), así que proporcionamos algo de pegamento para hacer queInterface
s se comporte comoNativeInterfaces
, y un mapa de tipos para aplicar ese pegamento. (Vea este documento para una discusión delpgcppname
)
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); } %}
- Cambie el nombre de la
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?