Создание интерфейса Java с помощью SWIG
Я использую SWIG, чтобы сделать Java-оболочку библиотеки C++ (о сериализации Json (de)), чтобы использовать ее на Android. Я определил абстрактный класс В C++, представляющий объект, который может быть (de)сериализован :
class IJsonSerializable {
public:
virtual void serialize(Value &root) = 0;
virtual void deserialize(Value &root) = 0;
};
Теперь, я пытаюсь создать из этого класса интерфейс java. Вот мой интерфейс SWIG:
%module JsonSerializable
%{
#include "JsonSerializable.hpp"
%}
%import "JsonValue.i"
class IJsonSerializable {
public:
virtual void serialize(Value &root) = 0;
virtual void deserialize(Value &root) = 0;
};
но сгенерированный Java-код (очевидно, поскольку я не смог узнать, как сказать SWIG, что это интерфейс) - простой класс с двумя методами и конструктор/деструктор по умолчанию:
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);
}
}
как я могу создать действительный интерфейс с SWIG ?
1 ответов
вы можете достичь того, что вы ищете с SWIG+Java, используя "директора", однако это не совсем так просто отображение из абстрактных классов C++ на Java, как вы могли бы надеяться. Поэтому мой ответ разделен на три части - во-первых, простой пример реализации чистой виртуальной функции C++ в Java, во-вторых, объяснение того, почему вывод такой, и в-третьих, "обходной".
реализация интерфейса C++ в Java
данный файл заголовка (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;
}
мы хотели бы обернуть это и заставить его работать интуитивно со стороны Java. Мы можем сделать это, определив следующий интерфейс 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);
}
}
%}
здесь мы включили директоров для всего модуля, а затем попросили их использовать для class Interface
специально. Кроме этого и моего любимого кода "загрузить общий объект автоматически" нет ничего особенно примечательного. Мы можем проверить это с следующие Java-класс:
public class Run extends Interface {
public static void main(String[] argv) {
test.bar(new Run());
}
public String foo() {
return "Hello from Java!";
}
}
затем мы можем запустить это и увидеть, что он работает так, как ожидалось:
ajw@rapunzel:~ / code / scratch/swig / javaintf > java Run
Привет с Java!
если вы довольны тем, что это ни abstract
и interface
вы можете прекратить читать здесь, режиссеры делают все, что вам нужно.
почему SWIG генерирует class
вместо interface
?
SWIG, однако, сделал то, что как абстрактный класс в конкретный. Это означает, что на стороне Java мы могли бы юридически написать new Interface();
, что не имеет никакого смысла. Почему SWIG делает это? The class
не abstract
, не говоря уже об interface
(см. пункт 4 здесь), что было бы более естественно на стороне Java. Ответ двоякий:--43-->
- SWIG поставляет механику для вызова
delete
, управляяcPtr
etc. на стороне Java. Этого нельзя было сделать за ... --11--> at все. -
рассмотрим случай, когда мы обернули следующую функцию:
Interface *find_interface();
здесь SWIG знает ничего больше о типе возврата, чем о том, что он имеет тип
Interface
. В идеальном мире он знал бы, что такое производный тип, но только из сигнатуры функции нет никакого способа понять это. Это означает, что в сгенерированном Java где-то должен быть звонок вnew Interface
, что не было бы возможно/законно, еслиInterface
были абстрактными на стороне Java.
возможный обходной путь
если бы вы надеялись предоставить это в качестве интерфейса, чтобы выразить иерархию типов с множественным наследованием в Java, это было бы довольно ограниченным. Однако есть обходной путь:
-
вручную напишите интерфейс как правильный интерфейс Java:
public interface Interface { public String foo(); }
-
измените интерфейс SWIG файл:
- переименовать класс c++
Interface
наNativeInterface
на стороне Java. (Мы должны сделать его видимым только для пакета, о котором идет речь, с нашим завернутым кодом, живущим в собственном пакете, чтобы избежать людей, делающих "сумасшедшие" вещи. - везде у нас есть
Interface
в коде C++ SWIG теперь будет использоватьNativeInterface
как тип на стороне Java. Нам нужны typemaps, чтобы отобразить этоNativeInterface
в параметрах функции наInterface
интерфейс Java мы добавили вручную. - Марк
NativeInterface
внедрениеInterface
чтобы сделать поведение Java естественным и правдоподобным для пользователя Java. - нам нужно предоставить немного дополнительного кода, который может выступать в качестве прокси для вещей, которые реализуют Java
Interface
безNativeInterface
тоже. - то, что мы передаем на C++, всегда должно быть
NativeInterface
все-таки не всеInterface
s будет один, хотя (хотя всеNativeInterfaces
будет), поэтому мы обеспечиваем некоторый клей для того чтобы сделатьInterface
s вести себя какNativeInterfaces
, и typemap для применения этого клея. (См.документ обсуждениеpgcppname
)
это приводит к файлу модуля, который теперь выглядит так:
%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); } %}
- переименовать класс c++
теперь мы можем обернуть функции как:
// %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;
}
%}
и использовать его как:
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!";
}
}
теперь это работает, как вы надеетесь:
ajw@rapunzel:~ / code / scratch/swig / javaintf > java Run
Привет от Ява!
Привет из C++
и мы завернули абстрактный класс из C++ в качестве интерфейса на Java точно так, как ожидал бы программист Java!