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

19

Я использую SWIG для создания Java-оболочки библиотеки C ++ (о сериализации Json (de)), чтобы использовать ее на 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?

    
задан Marc Plano-Lesay 17.11.2011 в 15:13
источник
  • Почему? Java уже имеет API JSON, просто используйте одно из доступных. –  Chris Dennett 17.11.2011 в 15:56
  • @ChrisDennett: Я уже использую эту библиотеку для других применений в C ++. У меня есть другие библиотеки для порта в ближайшем будущем, поэтому у меня будет такая же проблема с ними. –  Marc Plano-Lesay 17.11.2011 в 16:08
  • Я не понимаю - что вы хотите здесь создать? SWIG генерирует прокси, которые соответствуют объявлениям и определениям, которые вы им показываете, что и делается здесь. Это случай этой проблемы? Я могу дать вам конкретный пример, если вы сделаете то, что ищете, немного яснее. –  Flexo♦ 20.11.2011 в 13:54
  • Я пытаюсь реализовать Java-класс с C ++-определенным интерфейсом. На самом деле, у меня есть некоторые (Java) методы, ожидающие IJsonSerializable (конечно, у них есть методы сериализации и десериализации). –  Marc Plano-Lesay 20.11.2011 в 19:38

1 ответ

47

Вы можете добиться того, что вы ищете с помощью 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 & gt; java Run
  Привет из Java!

Если вы довольны тем, что не являетесь ни abstract , ни interface , вы можете перестать читать здесь, режиссеры делают все, что вам нужно.

Почему SWIG генерирует class вместо interface ?

Однако SWIG сделал то, что было похоже на абстрактный класс в конкретный. Это означает, что на стороне Java мы могли бы законно написать new Interface(); , что не имеет смысла. Почему SWIG это делает? % Co_de% даже не class , не говоря уже о abstract (см. Пункт 4 здесь ), что было бы более естественным на стороне Java. Ответ двоякий:

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

    Interface *find_interface();
    

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

Возможное обходное решение

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

  1. Вручную написать интерфейс в качестве надлежащего интерфейса Java:

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

    1. Переименуйте класс C ++ Interface на Interface на стороне Java. (Мы должны сделать это видимым только для пакета, о котором идет речь, с нашим завернутым кодом, живущим в собственном пакете, чтобы люди не делали «сумасшедшие» вещи.
    2. Всюду мы имеем NativeInterface в коде C ++. SWIG теперь будет использовать Interface в качестве типа на стороне Java. Нам нужны typemaps для сопоставления этих NativeInterface в функциональных параметрах на интерфейсе NativeInterface Java, который мы добавили вручную.
    3. Пометить Interface как реализацию NativeInterface , чтобы поведение Java-стороны было естественным и правдоподобным для пользователя Java.
    4. Нам нужно предоставить немного дополнительного кода, который может выступать в качестве прокси-сервера для вещей, которые реализуют Java Interface , даже не являясь Interface .
    5. То, что мы переходим на C ++, должно всегда быть NativeInterface , но не все NativeInterface s будут одним (хотя все Interface будут), поэтому мы предоставляем некоторый клей, чтобы NativeInterfaces s вел себя как Interface и типовую карту для применения этого клея. (См. этот документ для обсуждения NativeInterfaces )

    В результате получается файл модуля, который теперь выглядит следующим образом:

    %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 & gt; java Run
  Привет от Java!
  Привет из C ++

И мы завернули абстрактный класс из C ++ в качестве интерфейса в Java, как ожидал Java-программист!

    
ответ дан Flexo 23.11.2011 в 18:17
источник
  • Вы, вероятно, можете автоматизировать запись класса NativeIntefaceProxy, используя что-то вроде этого –  Flexo♦ 23.11.2011 в 18:56
  • Именно то, что мне нужно, и так хорошо объяснено! Огромное спасибо. –  Marc Plano-Lesay 23.11.2011 в 20:01
  • Я думаю, что SWIG 3.1, скорее всего, изменит этот ответ, когда он будет выпущен: github.com/swig/swig/blob/master/Lib/java/swiginterface.i –  Flexo♦ 02.06.2016 в 08:49
  • @Flexo не могли бы вы посоветовать мне, как использовать swig для установки Java-объекта на C ++? –  Quang Huynh 31.10.2016 в 09:22
  • @QuangHuynh, это в значительной степени то, что делает этот раздел руководства SWIG - вам либо нужно быть более конкретным в своем вопросе (и задавать его как вопрос!), либо нет ничего проницательного, что я могу добавить по тому, что уже написано. –  Flexo♦ 31.10.2016 в 09:27
Показать остальные комментарии