Ответ 1
Вы можете добиться того, что вы ищете, с помощью SWIG + Java, используя " Directors", однако это не совсем простое отображение из абстрактные классы С++ на Java, как вы могли бы надеяться. Поэтому мой ответ разбит на три части - во-первых, простой пример реализации чистой виртуальной функции С++ в Java, во-вторых, объяснение того, почему вывод такой, и, в-третьих, "обход".
Реализация интерфейса С++ в 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 это делает? class
не является даже abstract
, не говоря уже о interface
(см. Пункт 4 здесь), что было бы более естественным на стороне Java. Ответ двоякий:
- SWIG поставляет механики для вызова
delete
, манипулируяcPtr
и т.д. на стороне Java. Это невозможно сделать вinterface
вообще. -
Рассмотрим случай, когда мы завернули следующую функцию:
Interface *find_interface();
Здесь SWIG ничего не знает о возвращаемом типе, чем тип
interface
. В идеальном мире он знал бы, что такое производный тип, но из одной только сигнатуры функции нет способа понять это. Это означает, что в сгенерированной Java где-то должен появиться вызовnew Interface
, что было бы невозможно/законно, еслиinterface
были абстрактными на стороне Java.
Возможное обходное решение
Если бы вы надеялись предоставить это как интерфейс, чтобы выразить иерархию типов с множественным наследованием в Java, это было бы весьма ограничительным. Однако обходной путь:
-
Вручную написать интерфейс в качестве надлежащего интерфейса Java:
public interface Interface { public String foo(); }
-
Измените файл интерфейса SWIG:
- Переименуйте класс С++
interface
какNativeInterface
на стороне Java. (Мы должны сделать это видимым только для пакета, о котором идет речь, с нашим завернутым кодом, живущим в собственном пакете, чтобы люди не делали "сумасшедшие" вещи. - Всюду мы имеем
interface
в С++ код SWIG теперь будет использоватьNativeInterface
как тип на стороне Java. Нам нужны typemaps для сопоставления этих параметровNativeInterface
в функциональном интерфейсеinterface
, который мы добавили вручную. - Отметить
NativeInterface
как реализациюinterface
, чтобы поведение Java-стороны было естественным и правдоподобным для пользователя Java. - Нам нужно предоставить немного дополнительного кода, который может выступать в качестве прокси для вещей, которые реализуют Java
interface
, не будучи такжеNativeInterface
. - То, что мы переходим на С++, должно быть всегда
NativeInterface
, но не всеinterface
будут одним, хотя (хотя всеNativeInterfaces
будут), поэтому мы предоставляем некоторый клей, чтобыinterface
вел себя какNativeInterfaces
, и типовую карту для нанесения этого клея. (См. этот документ для обсуждения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); } %}
- Переименуйте класс С++
Теперь мы можем обернуть функцию, например:
// %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 Запустить
Привет из Java!
Привет из С++
И мы завернули абстрактный класс из С++ в качестве интерфейса в Java, как ожидал Java-программист!