Создание интерфейса Java с помощью SWIG - PullRequest
20 голосов
/ 17 ноября 2011

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

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 Ответ

50 голосов
/ 23 ноября 2011

Вы можете достичь того, что ищете, с помощью 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.Ответ двоякий:

  1. SWIG предоставляет механизмы для вызова delete, манипулирования cPtr и т. Д. На стороне Java.Это невозможно сделать за interface.
  2. Рассмотрим случай, когда мы обернули следующую функцию:

    Interface *find_interface();
    

    Здесь SWIG не знает ничего *На 1052 * больше о типе возвращаемого значения, чем о типе Interface.В идеальном мире он знал бы, что является производным типом, но только из сигнатуры функции у него нет возможности выяснить это.Это означает, что в сгенерированной Java где-то должен быть вызов new Interface, который был бы невозможен / недопустим, если бы Interface были абстрактными на стороне Java.

Возможный обходной путь

Если вы надеялись предоставить это как интерфейс для выражения иерархии типов с множественным наследованием в Java, это было бы весьма ограничивающим.Однако есть обходной путь:

  1. Вручную запишите интерфейс как правильный интерфейс Java:

    public interface Interface {
        public String foo();
    }
    
  2. Измените файл интерфейса SWIG:

    1. Переименуйте класс C ++ Interface, чтобы он был NativeInterface на стороне Java.(Мы должны сделать его видимым только для рассматриваемого пакета, так как наш упакованный код живет в отдельном пакете, чтобы люди не делали «сумасшедшие» вещи.
    2. Везде, где у нас есть Interface в C ++код SWIG теперь будет использовать NativeInterface в качестве типа на стороне Java. Нам нужны карты типов, чтобы сопоставить эти NativeInterface в параметрах функции с Interface интерфейсом Java, который мы добавили вручную.
    3. Mark NativeInterfaceкак реализация Interface, чтобы сделать поведение на стороне Java естественным и правдоподобным для пользователя Java.
    4. Нам нужно предоставить немного дополнительного кода, который может выступать в качестве прокси для вещей, которые реализуют Java Interfaceне являясь NativeInterface тоже.
    5. То, что мы передаем в 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!

...