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で有効なインターフェイスを生成するにはどうすればよいですか?
" Directors "を使用してSWIG + Javaで探しているものを実現できますが、C++抽象クラスからJava)へのマッピングはそれほど簡単ではありません。したがって、私の答えは3つの部分に分かれています。1つはJavaでC++の純粋な仮想関数を実装する簡単な例、2つ目は出力がそのようなものである理由の説明、3つ目は「回避策」です。
与えられたヘッダーファイル(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
でもないことに満足している場合は、ここで読むのをやめることができます。ディレクターは必要なことをすべて行います。
class
ではなくinterface
を生成するのはなぜですか?ただし、SWIGは、抽象クラスのように見えるものを具体的なクラスにしました。つまり、Java側では、合法的にnew Interface();
を記述できますが、これは意味がありません。なぜSWIGがこれを行うのですか?class
はabstract
でさえなく、interface
は言うまでもありません。 (ポイント4 ここ を参照)、これはJava側でより自然に感じるでしょう。答えは2つあります:
delete
を呼び出したり、Java側でcPtr
などを操作したりするためのメカニズムを提供します。これは、interface
ではまったく実行できませんでした。次の関数をラップした場合を考えてみましょう。
Interface *find_interface();
ここで、SWIGは、戻り値の型について、タイプInterface
よりもnothingを知っています。理想的な世界では、派生型が何であるかを知っていますが、関数のシグネチャだけからこれを理解する方法はありません。これは、生成されたJavasomewhere)でnew Interface
を呼び出す必要があることを意味しますが、そうではありません。 Interface
がJava側で抽象的である場合、可能/合法である可能性があります。
Javaで多重継承を持つ型階層を表現するために、これをインターフェースとして提供したい場合、これはかなり制限されます。ただし、回避策があります。
インターフェイスを適切なJavaインターフェイス:
public interface Interface {
public String foo();
}
SWIGインターフェイスファイルを変更します。
Interface
の名前をJava側でNativeInterface
に変更します(問題のパッケージにものみ表示されるようにする必要があります。ラップされたコードは、回避するために独自のパッケージに存在します。 「クレイジー」なことをしている人々。Interface
がある場合、SWIGはJava側の型としてNativeInterface
を使用します。関数パラメーターのこのNativeInterface
をInterface
Java手動で追加したインターフェース。NativeInterface
をInterface
の実装としてマークして、Javaのサイド動作を自然にし、Javaユーザーに信頼できるようにします。Interface
でなくても、Java NativeInterface
を実装するもののプロキシとして機能できる、少し余分なコードを提供する必要があります。NativeInterface
である必要がありますが、すべてのInterface
が1つになるわけではないため(すべてのNativeInterfaces
はそうなります)、Interface
を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/spark/swig/javaintf> Java実行
Javaからこんにちは!
C++からこんにちは
そして、C++の抽象クラスをインターフェイスとしてJava Javaプログラマーが期待するとおりにラップしました!