web-dev-qa-db-ja.com

SWIGとのJavaインターフェースの生成

SWIGを使用してC++ライブラリのJavaラッパー(Json(de)serializationについて)を作成し、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にインターフェイスであることを伝える方法がわからなかったため)、2つのメソッドとデフォルトのコンストラクタ/デストラクタを備えた単純なクラスです。

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で有効なインターフェイスを生成するにはどうすればよいですか?

22

" Directors "を使用してSWIG + Javaで探しているものを実現できますが、C++抽象クラスからJava)へのマッピングはそれほど簡単ではありません。したがって、私の答えは3つの部分に分かれています。1つはJavaでC++の純粋な仮想関数を実装する簡単な例、2つ目は出力がそのようなものである理由の説明、3つ目は「回避策」です。

JavaでのC++インターフェースの実装

与えられたヘッダーファイル(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/spark/swig/javaintf> Java実行
Javaからこんにちは!

abstractでもinterfaceでもないことに満足している場合は、ここで読むのをやめることができます。ディレクターは必要なことをすべて行います。

SWIGがclassではなくinterfaceを生成するのはなぜですか?

ただし、SWIGは、抽象クラスのように見えるものを具体的なクラスにしました。つまり、Java側では、合法的にnew Interface();を記述できますが、これは意味がありません。なぜSWIGがこれを行うのですか?classabstractでさえなく、interfaceは言うまでもありません。 (ポイント4 ここ を参照)、これはJava側でより自然に感じるでしょう。答えは2つあります:

  1. SWIGは、deleteを呼び出したり、Java側でcPtrなどを操作したりするためのメカニズムを提供します。これは、interfaceではまったく実行できませんでした。
  2. 次の関数をラップした場合を考えてみましょう。

    Interface *find_interface();
    

    ここで、SWIGは、戻り値の型について、タイプInterfaceよりもnothingを知っています。理想的な世界では、派生型が何であるかを知っていますが、関数のシグネチャだけからこれを理解する方法はありません。これは、生成されたJavasomewhere)でnew Interfaceを呼び出す必要があることを意味しますが、そうではありません。 InterfaceがJava側で抽象的である場合、可能/合法である可能性があります。

考えられる回避策

Javaで多重継承を持つ型階層を表現するために、これをインターフェースとして提供したい場合、これはかなり制限されます。ただし、回避策があります。

  1. インターフェイスを適切なJavaインターフェイス:

    public interface Interface {
        public String foo();
    }
    
  2. SWIGインターフェイスファイルを変更します。

    1. C++クラスの名前をInterfaceの名前をJava側でNativeInterfaceに変更します(問題のパッケージにものみ表示されるようにする必要があります。ラップされたコードは、回避するために独自のパッケージに存在します。 「クレイジー」なことをしている人々。
    2. C++コードにInterfaceがある場合、SWIGはJava側の型としてNativeInterfaceを使用します。関数パラメーターのこのNativeInterfaceInterface Java手動で追加したインターフェース。
    3. NativeInterfaceInterfaceの実装としてマークして、Javaのサイド動作を自然にし、Javaユーザーに信頼できるようにします。
    4. Interfaceでなくても、Java NativeInterfaceを実装するもののプロキシとして機能できる、少し余分なコードを提供する必要があります。
    5. C++に渡すものは常にNativeInterfaceである必要がありますが、すべてのInterfaceが1つになるわけではないため(すべてのNativeInterfacesはそうなります)、InterfaceNativeInterfacesとして動作させるための接着剤と、その接着剤を適用するためのタイプマップを提供します。 (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/spark/swig/javaintf> Java実行
Javaからこんにちは!
C++からこんにちは

そして、C++の抽象クラスをインターフェイスとしてJava Javaプログラマーが期待するとおりにラップしました!

50
Flexo