Подтвердить что ты не робот

Создание интерфейса Java с помощью SWIG

Я использую SWIG для создания Java-оболочки библиотеки С++ (о сериализации Json (de)), чтобы использовать ее на Android. Я определил абстрактный класс в С++, представляющий объект, который может быть (де) сериализован:

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?

4b9b3361

Ответ 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-программист!