特集:組み込みにも役立つJavaとネイティブコードの橋渡し

JNIより簡単にJavaとC/C++をつなぐ「JNA」とは


富士ソフト株式会社
高見 誠
2009/12/14


【4】構造体型のマッピング

 JNAのクラスStructureはCの構造体structに相当します。Java側のC構造体に対応するクラスは、Structureクラスを継承して、宣言しなければなりません。マッピングする際、メンバー名ではなく、メンバーの順序でマッピングされるため、メンバーの順序をそろえる必要があります。

図3 構造体マッピングイメージ
図3 構造体マッピングイメージ

C/C++側

 JavaとC/C++間のやりとりについて、社員情報構造体のサンプルで説明します。まず、リスト2のC/C++のネイティブライブラリソースコードを見てみましょう。

  リスト2 構造体型のマッピングのC/C++側サンプルソースコード
 1 /**
 2  * 社員情報構造体
 3  */
 4 typedef struct {
 5     char* id;           /*社員番号*/
 6     char* name;     /*氏名*/
 7     int age;            /*年齢*/
 8     char* sectionId     /*部署番号*/
 9 } EMPLOYEE;
10
11 /**
12  * 指定番号の社員情報を取得。
13  * @param  pId     [in]社員番号
14  * @param  rc    [out]実行結果コード
15  * @return  社員情報を返す。
16  */
17 EMPLOYEE* getEmployee(const char* pId, int* rc);
 
18 /**
19  * 社員情報構造体領域を解放。
20  * @param  pEmployeeList [in]社員情報リスト
21  * @param  listSize    [out]返すリストの件数
22  */
23 void freeEmployeeList(EMPLOYEE * pEmployeeList, const int listSize);

 4〜9行目では、社員情報構造体の宣言です。17行目では、1人の社員情報を取得するAPIです。

 23行目は社員情報構造体領域を解放するAPIです。C/C++側で確保したメモリは、Java側に渡した後、参照利用が終わったら、C/C++側で解放する必要があるからです。これは構造体だけではなく、文字列などもすべて同じで、C/C++で確保されたメモリは、APIを通じてC/C++側で解放する必要があります。その後、実装を完成して、ネイティブライブラリStructureSampleを作成とします。

Java側

 次に、Java側のネイティブライブラリStructureSampleをアクセスするために、Java側のサンプルソース(リスト3)を見てみましょう。

  リスト3 構造体型のマッピングのJava側サンプルソースコード
 1 /**
 2  * 社員情報
 3  */
 4 public interface StructureSample extends Library {
 5     public static class _Employee extends Structure {
 6         String id;        /*社員番号*/
 7         String name;    /*氏名*/
 8         int age;          /*年齢*/
 9         String sectionId; /*部署番号*/
10     }
11
12     /**
13      * 指定番号の社員情報を取得。
14      */
15     _Employee getEmployee(String pId, IntReference rc);
16
17     /**
18      * 社員情報構造体領域を解放。
19      */
20      void freeEmployeeList(Pointer pEmployeeList, int listSize);
21 }

 4行目では、アクセスのネイティブライブラリをマッピングするために、インターフェイスの StructureSampleを宣言します。ネイティブライブラリのデータメンバーやAPIとも、このインターフェイス内に宣言しなければなりません。

 5〜10行目では、社員情報構造体にマッピングするための宣言です。id、name、sectionIdの各プロパティメンバーは表2に従って、String型にしています。繰り返しになりますが、JavaとC/C++のプロパティ名は異なっていても構いませんが、プロパティメンバーの順序は変更できません。

 15行目では、アクセスのAPI getEmployeeを宣言します。第1引数は、表2に従って、String型にし、第2引数は、Cのint型の戻り値なので、これも表2に従って、IntReferenceにしています。IntReferenceはCのint型のin/outを対応できます。

 20行目では、メモリ解放用のAPI「freeEmployeeList」を宣言しています。getEmployeeで、データのインスタンスを取得して、Java側のDTOにデータをコピー後、freeEmployeeListで、C側に要求して、そのインスタンスを解放します。

ポインタのマッピング

 注目してほしいのは、freeEmployeeListの第1引数が、Pointerになっていることです。StructureインスタンスからgetPointerでPointerを取得して、渡します。Pointerのインスタンスは、C側の任意型のポインタをマッピングします。Pointerの一部メソッドは直接、ネイティブポインタのメモリにアクセスできます。第2引数は、社員情報リストのサイズです。このサンプルでは、1人の社員情報しかないため不要ですが、後述の拡張用のために、指定しておきます。

 デフォルトでは、構造体型のデータのやりとりはデータの参照で行いますが、値で渡す方法もあります。詳細は、JNAのドキュメントを参照してください。

 以上、簡単な構造体をマッピングする方法をサンプルを通じて説明しました。この後も、引き続いて、さらに拡張した、一層複雑なデータ型のマッピング方法を説明します。

【5】配列型のマッピング

 データの配列を引数としてJavaからC/C++に渡す場合、プリミティブの1次元配列ならJNAが自動的にマッピングしてくれるので、そのまま渡せます。問題は、2次元配列です。JavaとC/C++では配列の実装方法が違いますので、そのままでは渡せません。

 もう1つの問題は、Cから戻り値を受け取るとき、配列のサイズが分からないことです。そのため、自動的にJavaの配列を作成できません。その代わりに、JNAのPointerクラス、または、PointerByRefenerceを通じ、配列の戻り値を受け取ります。

 以降は、文字列の配列と構造体の配列のやりとりをサンプルソースコード通じて、説明します。

文字列配列を引数として渡す方法

 Javaの文字列配列String[]はメソッドの引数として渡すのは、JNAは自動的にCのchar**にマッピングしてくれますので、そのまま渡すことができます。

 しかし、String[]を構造体Structureのメンバーとして、そのまま渡せません。渡すためには、String[]をC/C++の実装に合わせて、連続メモリの上にデータを作成する必要です。そのため、構造体Structureのメンバーとする場合、Pointerで宣言します。渡す際、JNAで提供のStringArrayクラスで、String[]をPointerに変換します。リスト4は、その方法をソースコードで示しています。

  リスト4 String[]からPointerに変換するサンプルソースコード
 1 Pointer StringList2Pointer(List source) {
 2     String[] sourceStrings = (String[])source.toArray(new String[0]);
 3     StringArray stringArray = new StringArray(sourceStrings);
 4     return stringArray.share(0);
 5 }

 3行目は、String[]からクラスStringArrayのインスタンスを生成します。StringArrayインスタンスは、連続メモリ上に文字列配列を作成してくれます。4行目は、連続メモリの先頭ポインターを返します。

文字列配列を受け取る方法

 文字列配列の戻り値はPointerで受け取ります。Cのchar**は、ポインタのポインタなので、Java側の変換も、PointerからPointerの配列を取得し、その配列から1つ1つの文字列を取り出します。リスト5は、その方法をソースコードで示しています。

 また、PointerByRefenerceでも、文字列配列を引き渡せます。

  リスト5 Pointerから文字列配列に変換するサンプルソースコード
 1 List<String> Pointer2StringList(Pointer source, final int size) {
 2     List<String> result = new ArrayList<String>;
 3     Pointer[] pList = source.getPointerArray(0, size);
 4     for( Pointer p : pList) {
 5         String value = p.getString(0);
 6         result.add(value);
 7     }
 8     return result;
 9 }

 3行目は、PointerからPointerの配列を取得します。引数のsizeは、配列のサイズでCから渡されます。5行目は、配列のメンバーから文字列の値を取り出します。

Structure配列を引数として渡す方法

 文字列配列と同様の理由で、Structure配列をPointerで、渡す必要があります。リスト6のサンプルを通じ、説明します。

  リスト6 Structure[]からPointerに変換するサンプルソースコード
 1 public interface StructureSample extends Library {
 2     public static class _Employee extends Structure {
 3         String id;        /*社員番号*/
 4         String name;    /*氏名*/
 5         int age;          /*年齢*/
 6         String sectionId; /*部署番号*/
 7     }
 8     public _Employee() {
 9         super();
10     }
11     public _Employee (Pointer p) {
12         super();
13         useMemory( p );
14         read();
15     }
16 }
 
……
 
 1 static Pointer Structures2Pointer(List<EmployeeVO> employees) {
 2     StructureSample._Employee headEmployee = new StructureSample._Employee();
 3     StructureSample._Employee[] _Employees = headEmployee.toArray(employees.size());
 4     int i = 0;
 5     for(EmployeeVO employee : employees) {
 6         StructureSample._Employee _Employee = new StructureSample._Employee(_Employees[i++].getPointer());
 7         _Employee.id = employee.getId();
 8         _Employee.name = employee.getName();
 9         _Employee.age = employee.getAge();
10         _Employee.sectionId = employee.getSectionId();
11         _Employee.write();
12     }
13     return headEmployee.getPointer();
14 }

 リスト6の前半1〜16行目は、連続メモリ上に_Employeeの配列を作成するための処理で、リスト3の_Employeeの拡張です。2〜7行目は、リスト3の_Employeeと同じで、変更がありません。8〜10行目は、デフォルトコンストラクタです。11〜15行目は、拡張した部分で、Pointerを渡して、StructureクラスのメンバーメソッドuseMemoryとreadを呼び出して、渡したPointerと同じメモリを共有する_Employeeインスタンスを生成します。

 次に、後半のStructure[]からPointerに変換する部分を見てみましょう。仮に、Webプレゼンテーション層からEmployeeデータリストが渡されてPointerに変換する、とします。ここでEmployeeは、DTOとして普通のJavaBeanで、そのプロパティメンバーはStructureSample._Employeeと基本的に同じとします。

 2行目は、配列の先頭のメンバーとして、StructureSample._Employeeのインスタンスを生成します。3行目は、配列先頭のメンバーから連続メモリ上に配列を作成します。4〜12行目は、EmployeeデータリストからStructureSample._Employee配列に、データをコピーします。6行目は、Pointerと同じメモリを共有する_Employeeインスタンスを生成します。11行目は、データをメモリに書き込みます。13行目は、配列の先頭のPointerを取得して、返します。

Structure配列を受け取る方法

 PointerからStructure配列を受け取る方法をリスト7のサンプルソースコードで、説明します。考え方は文字列配列と同じです。

  リスト7 PointerからにStructure[]変換するサンプルソースコード
 1 final int SIZEOF_EMPLOYEE = 16;
……
 2 static List<EmployeeVO> Pointer2Structures(Pointer _employees, final int size)
 3 {  
 4     List<EmployeeVO> results = new ArrayList< EmployeeVO >();
 5     for(int i = 0; i < size; i++) {
 6         Pointer pEmployee = employees.share(i * SIZEOF_EMPLOYEE);
 7         StructureSample._ Employee _employee = new StructureSample._ Employee(pEmployee);
 8         EmployeeVO employee = new EmployeeVO ();
 9         /** プロパティコピー*/
10 ……
11         results.add(employee);
12     }
13     return results;
14 }

 1行目は、Cのsizeof(EMPLOYEE)の値でEMPLOYEE構造体のサイズを宣言します。5〜12行目は、EMPLOYEE構造体配列のポインターからEmployeeデータリストにデータをコピーします。6行目は、1つのEMPLOYEE構造体のポインターを取り出します。7行目は、EMPLOYEE構造体ポインターからStructureSample._Employeeインスタンスを生成します。

1-2-3-4

 INDEX 特集「組み込みにも役立つJavaとネイティブコードの橋渡し」
  Page1
  C/Sでは、サーバサイドはC/C++がまだまだ主役
JNIを使いやすくした「JNA」とは
  Page2
  JNAを利用するための基本的な流れ
JavaとC/C++のマッピング方法、6パターン
【1】ライブラリのマッピング
【2】APIのマッピング
【3】プリミティブデータ型のマーシャリング
Page3
   【4】構造体型のマッピング
 【5】配列型のマッピング
  Page4
   【6】入れ子構造体型のマッピング
JNAアプリケーションのアーキテクチャ
JNIよりも保守性を高めるJNA

Java Solution全記事一覧



Java Agile フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Java Agile 記事ランキング

本日 月間