Создание интерфейса 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-->

  1. SWIG поставляет механику для вызова delete, управляя cPtr etc. на стороне Java. Этого нельзя было сделать за ... --11--> at все.
  2. рассмотрим случай, когда мы обернули следующую функцию:

    Interface *find_interface();
    

    здесь SWIG знает ничего больше о типе возврата, чем о том, что он имеет тип 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. Нам нужны typemaps, чтобы отобразить это NativeInterface в параметрах функции на Interface интерфейс Java мы добавили вручную.
    3. Марк NativeInterface внедрение Interface чтобы сделать поведение Java естественным и правдоподобным для пользователя Java.
    4. нам нужно предоставить немного дополнительного кода, который может выступать в качестве прокси для вещей, которые реализуют Java Interface без NativeInterface тоже.
    5. то, что мы передаем на C++, всегда должно быть NativeInterface все-таки не все Interfaces будет один, хотя (хотя все NativeInterfaces будет), поэтому мы обеспечиваем некоторый клей для того чтобы сделать Interfaces вести себя как 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);
      }
    %}
    

теперь мы можем обернуть функции как:

// %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!