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

20

Я использую 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?

    
задан 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 ответ

49

Вы можете добиться того, что ищете, с помощью 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!";
  }
}

Затем мы можем запустить это и увидеть, что он работает как положено:

[email protected]:~/code/scratch/swig/javaintf > java Run
Hello from Java!

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

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

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

  1. SWIG предоставляет механизм для вызова delete , манипулирования cPtr и т. д. на стороне Java. Это не может быть сделано в interface вообще.
  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. Нам нужны карты типов, чтобы отобразить этот NativeInterface в параметрах функции на Java-интерфейс Interface , который мы добавили вручную.
    3. Пометьте NativeInterface как реализацию Interface , чтобы сделать поведение на стороне Java естественным и правдоподобным для пользователя Java.
    4. Нам нужно предоставить немного дополнительного кода, который может выступать в качестве прокси-сервера для вещей, которые реализуют Java Interface , но при этом не являются NativeInterface .
    5. То, что мы передаем в C ++, всегда должно быть NativeInterface , хотя не все Interface s будут единицами (хотя все 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!";
  }
}

Теперь это работает так, как вы надеетесь.

[email protected]:~/code/scratch/swig/javaintf > java Run
Hello from Java!
Hello from 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
Показать остальные комментарии