0Pricing
Java Academy · レッスン

ダウンコールハンドル

ネイティブ関数を呼び出します。

「ダウンコールハンドル」はCoddyKit上の無料Java Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはJava Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Java Academyコースには全4レッスンが含まれています。

ネイティブ関数の呼び出し

ダウンコールとは、Javaからネイティブコードへ下って呼び出すことです。FFMでは、各ネイティブ関数をLinkerから構築したMethodHandleとして表します。

このレッスンでは、C関数の検索、記述、呼び出しの手順を説明します。

ネイティブリンカー

Linker.nativeLinker()は、実行中のプラットフォーム用のリンカーを返します。Cの呼び出し規約を把握しており、ネイティブ関数へのハンドルを生成できます。

import java.lang.foreign.Linker;

public class Main {
    public static void main(String[] args) {
        Linker linker = Linker.nativeLinker();
        System.out.println("Got native linker: " + linker);
    }
}

シンボルの検索

関数のアドレスが必要です。linker.defaultLookup()は標準Cライブラリを検索し、SymbolLookup.libraryLookup(name, arena)は指定した共有ライブラリを読み込みます。

import java.lang.foreign.*;

public class Main {
    public static void main(String[] args) {
        Linker linker = Linker.nativeLinker();
        SymbolLookup stdlib = linker.defaultLookup();
        MemorySegment addr = stdlib.find("strlen").orElseThrow();
        System.out.println("strlen address found: " + addr);
    }
}

シグネチャの記述

FunctionDescriptorは、レイアウトを使ってCのシグネチャを記述します。size_t strlen(const char*)の場合、戻り値はlongで、引数はポインター(ADDRESS)です。

import java.lang.foreign.*;

public class Main {
    public static void main(String[] args) {
        FunctionDescriptor desc = FunctionDescriptor.of(
            ValueLayout.JAVA_LONG,   // return: size_t
            ValueLayout.ADDRESS);    // arg: const char*
        System.out.println("Descriptor: " + desc);
    }
}

ハンドルの構築

シンボルとディスクリプターをdowncallHandleで組み合わせると、Javaメソッドと同じように呼び出せるMethodHandleを取得できます。

import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;

public class Main {
    public static void main(String[] args) {
        Linker linker = Linker.nativeLinker();
        MethodHandle strlen = linker.downcallHandle(
            linker.defaultLookup().find("strlen").orElseThrow(),
            FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS));
        System.out.println("Handle ready: " + strlen.type());
    }
}

関数の呼び出し

strlenを呼び出すには、ネイティブ文字列へのポインターを渡します。Arenaに文字列を割り当ててから、ハンドルをinvokeします。結果はJavaのlongとして返されます。

import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;

public class Main {
    public static void main(String[] args) throws Throwable {
        Linker linker = Linker.nativeLinker();
        MethodHandle strlen = linker.downcallHandle(
            linker.defaultLookup().find("strlen").orElseThrow(),
            FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS));
        try (Arena arena = Arena.ofConfined()) {
            MemorySegment str = arena.allocateUtf8String("Hello, FFM");
            long len = (long) strlen.invoke(str);
            System.out.println("Length = " + len);
        }
    }
}

型のマッピング

各C型は次のようにレイアウトへ対応付けられます。

  • int:ValueLayout.JAVA_INT
  • long/size_t:JAVA_LONG
  • double:JAVA_DOUBLE
  • すべてのポインター:ADDRESS

対応付けを正しく行うことが重要です。間違えると、呼び出しによってデータが破損します。

ポインターを返す関数

C関数がポインターを返す場合、ハンドルは長さ0のMemorySegmentを返します。先頭より後ろを読み取るには、reinterpret(byteSize)で正しいサイズに再解釈する必要があります。

多数の引数の受け渡し

FunctionDescriptor.of(returnLayout, arg1, arg2, ...)には、引数を順番に列挙します。void関数には戻り値のレイアウトを指定せず、FunctionDescriptor.ofVoid(args...)を使用します。

import java.lang.foreign.*;

public class Main {
    public static void main(String[] args) {
        // int (*)(const char*, int)
        FunctionDescriptor two = FunctionDescriptor.of(
            ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_INT);
        // void (*)(int)
        FunctionDescriptor noReturn = FunctionDescriptor.ofVoid(ValueLayout.JAVA_INT);
        System.out.println(two + " / " + noReturn);
    }
}

throwsでエラーを処理する

MethodHandle.invokeはthrows Throwableを宣言しているため、呼び出し元のメソッドではこれを処理するか、宣言する必要があります。例でthrows Throwableを宣言しているのはこのためです。

ハンドルのキャッシュ

ダウンコールハンドルの構築には比較的コストがかかるため、一度だけ構築して再利用します。一般的なパターンは、クラスの初期化時にstatic finalフィールドへハンドルを設定し、その後何度も呼び出す方法です。

ハンドル自体は、複数スレッドから同時に呼び出しても安全です。

確認問題

Cのポインター引数をどのように記述するか思い出してください。

まとめ

ネイティブ関数の呼び出し方を学びました。

  • Linker.nativeLinker()とSymbolLookupを使ってシンボルを検索します
  • FunctionDescriptorがレイアウトを使ってシグネチャを記述します
  • downcallHandleによってinvokeできるMethodHandleを取得します
  • ポインターはADDRESSに対応し、invokeはThrowableをスローします

次は、レイアウトと構造体を使って複雑なネイティブデータを記述します。

よくある質問

「ダウンコールハンドル」レッスンは無料ですか?

はい。「ダウンコールハンドル」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Java Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Java Academyコースには全4レッスンが含まれています。

「ダウンコールハンドル」で何を学びますか?

ネイティブ関数を呼び出します。 ブラウザで直接実行するハンズオンコードでJava Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Java Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのJava Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。

「ダウンコールハンドル」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このJava Academyレッスンでコードを書いて実行できますか?

はい。すべてのJava Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. JNI より FFM を使う理由
  2. MemorySegment と Arena
  3. ダウンコールハンドル
  4. レイアウトと構造体
← Java Academyに戻る