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