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:
- 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.
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:
escribir manualmente la interfaz como una interfaz Java adecuada:
public interface Interface {
public String foo();
}
Modificar el archivo de interfaz SWIG:
- 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".
- 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.
- Marcos
NativeInterface
como la implementación de Interface
para hacer el comportamiento lado de Java natural y creíble para un usuario de Java
- 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.
- 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!
¿Por qué? Java ya tiene API JSON, solo use una de las muchas disponibles. –
@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. –
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