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 greeterlibc クレートを依存関係に追加します。
また、ダイナミックリンクライブラリを生成するように 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