SWIG+JAVA(%extends)

JNAは楽だが、どうも遅いらしいのでSWIG+JAVA(JNI)を利用。

情報が不足気味。公式の英語ドキュメントをじっくり読んで試すしかない。
このドキュメントの問題点は、どうJNIのC/C++コードに反映されるのか、Javaにどう反映されるのかわからない点にある。

特に%extendsの動作がよくわからなかったため、試してみた。
試してみた結果、以下の結果であることがわかった。

  • swigの設定ファイルは、C++のクラスで記述
  • 変換後には、%extendsのC++のメソッドの宣言がJavaコードに変換されクラスへ反映されるが、C/C++側は、C++でもクラス構成がないフラットなCの関数呼び出しとなり、クラス構成が反映されない。よって、C++ではクラス変数の設定ができない。(%typemap(javacode)を併用するとJava側ではクラス変数、メソッドの定義が可能)

下手に継承したりすると、メモリサイズとかが変わり不都合が生じる可能性があるため、このような実装になっていると思われる。

サンプル例

たとえば、epoll_eventのようにstructで配列が必要なものがある。

struct epoll_event {
    uint32_t     events;    /* epoll イベント */
    epoll_data_t data;      /* ユーザデータ変数 */
};

単純に変換しただけだと、epoll_eventはポインタにしかならない。

このため、carrays.iを使って、epoll_eventをラップして配列用のクラスを作る。(なお、arrays_java.iは、プリミティブ型のみ使えて、そもそもコピーが生じる)

%include "carrays.i"
%array_class(struct epoll_event, epoll_event_array);

なお、carrays.iの%array_classは、

%extend NAME {

#if __cplusplus
NAME(int nelements) {
  return new TYPE[nelements];
}
~NAME() {
  delete [] self;
}
#else
NAME(int nelements) {
  return (TYPE *) calloc(nelements,sizeof(TYPE));
}
~NAME() {
  free(self);
}
#endif

TYPE getitem(int index) {
  return self[index];
}
void setitem(int index, TYPE value) {
  self[index] = value;
}
TYPE * cast() {
  return self;
}
static NAME *frompointer(TYPE *t) {
  return (NAME *) t;
}

};

SWIGでの変換後

この場合、Cのコードとして、

typedef struct epoll_event epoll_event_array;

SWIGINTERN epoll_event_array *new_epoll_event_array(int nelements){
  return (struct epoll_event *) calloc(nelements,sizeof(struct epoll_event));
}
SWIGINTERN void delete_epoll_event_array(epoll_event_array *self){
  free(self);
}
SWIGINTERN struct epoll_event epoll_event_array_getitem(epoll_event_array *self,int index){
  return self[index];
}
SWIGINTERN void epoll_event_array_setitem(epoll_event_array *self,int index,struct epoll_event value){
  self[index] = value;
}
SWIGINTERN struct epoll_event *epoll_event_array_cast(epoll_event_array *self){
  return self;
}
SWIGINTERN epoll_event_array *epoll_event_array_frompointer(struct epoll_event *t){
  return (epoll_event_array *) t;
}

が生成される。C++オプションを使ってもほぼ同じ。

対応するJavaのコードは、

...
//swigが自動生成するコンストラクタ
//protectedであることに注意。すなわちextendしないと、Java側からはオブジェクトを生成できない
  protected epoll_event(long cPtr, boolean cMemoryOwn) { 
    swigCMemOwn = cMemoryOwn;
    swigCPtr = cPtr;
  }
...
  //JavaのGCのタイミングにあわせて、解放されていなければ解放する。
  //よって、Ownerでない領域は、OwnerがGCで回収されると、存在しなくなる。
  protected void finalize() {
    delete();
  }

  //publicなのでユーザが明示的に解放してもよい。
  public synchronized void delete() {
    if(swigCPtr != 0 && swigCMemOwn) {
      swigCMemOwn = false;
      epollJNI.delete_epoll_event_array(swigCPtr);
    }
    swigCPtr = 0;
  }

  //carrays.iによって生成されるコンストラクタ
  //publicなのでJavaから生成可能
  public epoll_event_array(int nelements) {
    this(epollJNI.new_epoll_event_array(nelements), true);
  }

  //配列からepoll_eventを取得
  public epoll_event getitem(int index) {
    return new epoll_event(epollJNI.epoll_event_array_getitem(swigCPtr, this, index), true);
  }

  //配列へepoll_eventを設定
  public void setitem(int index, epoll_event value) {
    epollJNI.epoll_event_array_setitem(swigCPtr, this, index, epoll_event.getCPtr(value), value);
  }

  //epoll_eventへのキャスト
  public epoll_event cast() {
    long cPtr = epollJNI.epoll_event_array_cast(swigCPtr, this);
    return (cPtr == 0) ? null : new epoll_event(cPtr, false);
  }

  //epoll_eventからのキャスト
  public static epoll_event_array frompointer(epoll_event t) {
    long cPtr = epollJNI.epoll_event_array_frompointer(epoll_event.getCPtr(t), t);
    return (cPtr == 0) ? null : new epoll_event_array(cPtr, false);
  }

となり、きちんとラップされていることがわかる。

epoll_eventとのJavaコードでの変換は、

epoll_event ee = array.cast();
epoll_event_array eea = epoll_event_array.frompointer(event);

で相互変換できる。