入社時研修以来のC言語系の開発案件があり、しかもJavaとの接続という研修でも習わないような処理方式を実現する必要がありました。
JNAという便利な機能もあり、Web上の情報でC++の呼び出しまではできたのですが、C++のライブラリ内で動的ロードが実装されていたので、そこで若干ハマりました。
JNAでかつ動的ロードのパターンはWeb上でも情報があまりなかったので、誰かのためになればと思いメモを書いておきます。
ソースは実際のソースから抜粋。(稼働確認してないので誤りがある可能性大)
状況の整理
- C++で提供された機能をSpringから呼び出して利用。
- SpringからC++呼び出しにはJNAを利用。
- C++としての提供機能内で動的ロード(dlopen)がある。
手順
Springから呼ばれるクラス
#include <stdio.h>
extern "C"{
int sampleMethod(char* inputSample)
{
/* 動的ロードするライブラリのメソッドは適当、インプット渡してStringを返す想定で書いてます。*/
handle = dlopen("/Sample/C++/libDlOpenSample.so", RTLD_LAZY);
/* べき論で言えばここでhandleのステータス判定を入れる。今回は割愛 */
return dlTest(inputSample);
}
}
Spring側の開発
pomの設定。
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>4.5.0</version>
</dependency>
ネイティブライブラリの設定
package hoge.fuga;
import org.springframework.stereotype.Service;
import com.sun.jna.Library;
import com.sun.jna.Native;
@Service
public interface JnaSampleLib extends Library {
JnaSampleLib INSTANCE = (JnaSampleLib) Native.loadLibrary("sample", JnaSampleLib.class);
int sampleMethod(String inputSample);
}
ネイティブライブラリの呼び出し
package hoge.fuga;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class SampleController {
@RequestMapping(value = "/test", method = RequestMethod.GET)
public String test() {
JnaSampleLib sample = JnaSampleLib.INSTANCE;
return sample.sampleMethod("JNA");
}
}
Spring/C++の両方を用意した環境
フォルダ構成
Sample
├C++
│ ├libsample.so
│ └libDlOpenSample.so
└Spring
└sample.war
Springの起動コマンド
java -Djna.library.path=/Sample/C++ -jar sample.war
私がハマったポイント
基本的にパスでハマりました。
1.JNAが認識するパス
JNAで呼び出す共有ライブラリのパスがSpringで認識されなかった。
対応:Sprintの起動時に-Djna.library.pathの環境変数でpathを渡す。
(注)-Djava.library.pathではなかった。これはJNIの場合のみ。
2.C++の動的ロードが認識するパス
C++の処理内で別の共通ライブラリを動的ロード(dlopen)しているが、共有ライブラリが認識されなかった。
原因:javaのカレントフォルダの環境変数がC++にも連携されているため、C++のモジュールから見たpathではなくjavaから見たpathを指定する必要があった。
-Djna.library.pathで設定している同じフォルダ内のモジュールを呼び出ししているようだったので、相対パスでカレントが指定すれば読み込めると思っていたのですが、これがダメでした。
対応:dlopenに渡すpathを絶対パスに変更。