Hello Project Panama, on Java17
はじめに
当時(Java14)は Project Panama 用にビルドされた JDK を利用する必要がありましたが、 Java17 では incubator ではあるものの JEP 412: Foreign Function & Memory API が標準 JDK に導入された
ので、標準の JDK でも冒頭にリンクしたコード相当のものをビルド、実行できるようになりました(※ 後述の通り jextract コマンド は標準 JDK に含まれていないので別途取得する必要があります)。
API, jextract コマンド引数など、 Java14 当時と結構変わっていましたので、改めて project Panama を使って Hello, world してみたいと思います。
今回の成果物は次のリンク先にあります:
環境
-
Ubuntu 20.04
-
後で Windows10 上でも試してみましたが、こちらも上手く動作しました
-
-
Java Corretto-17.0.0.35.1
-
jextractBuild 17-panama+3-167 (2021/5/18) -
rustc 1.55.0
作成手順
Rust でダイナミックリンクライブラリ作成
greeter という名前でプロジェクトを作成します。
プロジェクトルートディレクトリで次のコマンドを実行します。
cargo new --lib greeter
cd greeter
libc クレートを依存関係に追加します。
また、ダイナミックリンクライブラリを生成するように crate-type に cdylib を設定します。
[dependencies]
libc = "0.2.103"
[lib]
crate-type = ["cdylib"]
Rust 側で行う処理を実装します。
use libc::size_t;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
#[no_mangle]
pub unsafe extern "C" fn greet(name: *const c_char, message: *mut c_char, count: size_t) {
let name = CStr::from_ptr(name);
let name = name.to_str().unwrap();
let text = format!("こんにちは、{}!", name);
let text = CString::new(text).unwrap();
message.copy_from(text.as_ptr(), count);
}
ビルドします。
cargo build --release
ダイナミックリンクライブラリの C ヘッダ自動生成
上で作成したライブラリのヘッダファイルを自動生成します。
cbindgen コマンドをインストールします。
cargo install --force cbindgen
プロジェクトルートディレクトリで次のコマンドを実行します。
cbindgen -l c -o bridges/greeter.h greeter
上記コマンド実行により bridges/greeter.h ヘッダファイルが生成されます。
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
void greet(const char *name, char *message, size_t count);
C ヘッダから Java API 自動生成
jextract コマンドを利用してヘッダファイルから Java API を自動生成します。
jextract コマンドは Project Panama の Early Access Build をダウンロードし展開すると bin ディレクトリ下にあります。
プロジェクトルートディレクトリで次のコマンドを実行します。
/path/to/jextract \ -l greeter \ -d classes \ -t com.example \ ./bridges/greeter.h
上記コマンドを実行すると classes ディレクトリ下にクラスファイルが生成されます。
補足/参考
-
上で自動生成した
bridges/greeter.hは、実際には不必要なインクルードを含んでいます。 これを削減し、#include <stddef.h>(size_tを定義しているヘッダファイル) だけにすると、ここで自動生成されるファイルも減ります。 -
jextractコマンドに--sourceオプションを付与すると、.classファイルでなく.javaファイルが生成されます。 -
jextractコマンドの詳細は次のリンク先を参照:-
Using the jextract tool - openjdk/panama-foreign
-
同階層
docディレクトリには他にも参考になるドキュメントあり
-
-
呼び出し側を Java で実装
jdk.incubator.foreign 機能を用いてメモリ領域を確保し、自動生成した API com.example.greeter_h.greeter() を呼ぶコードを実装します。
import static com.example.greeter_h.*;
import static jdk.incubator.foreign.CLinker.*;
import jdk.incubator.foreign.*;
import java.awt.BorderLayout;
import java.io.Serial;
import java.nio.charset.StandardCharsets;
import javax.swing.*;
public class Main extends JFrame {
@Serial
private static final long serialVersionUID = 4648172894076113183L;
public Main() {
super("Rust GUI Frontend by Java Swing");
setLayout(new BorderLayout());
final JTextField nameField = new JTextField(20);
final JTextField outputField = new JTextField(30);
outputField.setEditable(false);
final JButton greetButton = new JButton("greet");
greetButton.addActionListener((e) -> {
try (ResourceScope scope = ResourceScope.newConfinedScope()) {
final SegmentAllocator allocator = SegmentAllocator.ofScope(scope);
final MemorySegment name = toCString(nameField.getText(), scope);
final long size = 256;
final MemorySegment message = allocator.allocateArray(C_CHAR, size);
greet(name, message, size);
// Project PanamaのJDKには存在するが、通常のJDK17には無い
// final String retval = toJavaString(message, StandardCharsets.UTF_8);
final String retval = toJavaString(message);
outputField.setText(retval);
}
});
add(nameField, BorderLayout.WEST);
add(greetButton, BorderLayout.EAST);
add(outputField, BorderLayout.SOUTH);
pack();
}
public static void main(final String[] args) {
SwingUtilities.invokeLater(() -> {
final Main app = new Main();
app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
app.setVisible(true);
});
}
}
補足
-
IDE を用いる場合、次のリンク先に IntelliJ の設定方法が説明されています。
-
https://github.com/carldea/panama4newbies/blob/main/README.md
-
こちらのリンク、IDE の設定方法だけでなく、 Project Panama 全体の説明もわかりやすいと思います
-
-
Java コードビルド
上記のコードを JDK17 でビルドするには --add-modules jdk.incubator.foreign オプションを付与する必要があります。
javac \
-encoding utf-8 \
-d ./classes \
-cp ./classes \
--add-modules jdk.incubator.foreign \
./src/Main.java
実行
--enable-native-access=ALL-UNNAMED, --add-modules jdk.incubator.foreign オプションが必要です。
LD_LIBRARY_PATH=./greeter/target/release \
java \
-Dfile.encoding=utf-8 \
--enable-native-access=ALL-UNNAMED \
--add-modules jdk.incubator.foreign \
-cp ./classes Main
参考/補足
-
Windows で実行する場合、
LD_LIBRARY_PATHは機能しません。 代わりに、./greeter/target/release/greeter.dllをカレントディレクトリにコピーしてから上のコマンドを実行します。 -
Using the jextract tool > Running the Java code that invokes helloworld