ポインタを返す外部関数

外部関数はメモリー領域を割り当て、その領域へのポインタを返すことがあります。たとえば、C標準ライブラリ関数void *malloc(size_t)は、要求されたメモリー量をバイト単位で割り当て、それへのポインタを返します。ただし、mallocなどのポインタを返すネイティブ関数を呼び出した場合、Javaランタイムは、ポインタが指すメモリー・セグメントのサイズまたは存続期間についてのインサイトを持ちません。そのため、FFM APIは、この種類のポインタを表すために長さゼロのメモリー・セグメントを使用します。

次の例では、C標準ライブラリ関数mallocを呼び出します。その後すぐに診断メッセージが出力され、mallocによって返されるポインタが長さゼロのメモリー・セグメントであることが示されます。

public class MallocExample {
    
    // Obtain an instance of the native linker
    static final Linker linker = Linker.nativeLinker();
        
    // Create a downcall handle for malloc()
    static final MethodHandle malloc = linker.downcallHandle(
        linker.defaultLookup().findOrThrow("malloc"),
        FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.JAVA_LONG)
    );
    
    // Create a downcall handle for free()
    static final MethodHandle free = linker.downcallHandle(
        linker.defaultLookup().findOrThrow("free"),
        FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)
    );        
        
    static MemorySegment allocateMemory(long byteSize, Arena arena) throws Throwable {
        
        // Invoke malloc(), which returns a pointer
        MemorySegment segment = (MemorySegment) malloc.invokeExact(byteSize); 
        
        // The size of the memory segment created by malloc() is zero bytes!
        System.out.println(
            "Size, in bytes, of memory segment created by calling malloc.invokeExact(" +
            byteSize + "): " + segment.byteSize());
        
        Consumer<MemorySegment> cleanup = s -> {
            try {
                free.invokeExact(s);
            } catch (Throwable e) {
                throw new RuntimeException(e);
            }
        };
        
        // This reintepret method:
        // 1. Resizes the memory segment so that it's equal to byteSize
        // 2. Associates it with an existing arena
        // 3. Invokes free() to deallocate the memory allocated by malloc()
        //    when its arena is closed
        
        return segment.reinterpret(byteSize, arena, cleanup);
    }
    
    public static void main(String[] args) {
        try (Arena arena = Arena.ofConfined()) {
            allocateMemory(100L, arena);
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }
}

ヒント:

パフォーマンス上の理由から、メソッド・ハンドルをstatic finalとして宣言することをお薦めします。

例では次のようなメッセージが出力されます。

Size, in bytes, of memory segment created by calling malloc.invokeExact(100): 0

FFM APIは、長さゼロのメモリー・セグメントを使用して次を表します。

  • 外部関数から返されるポインタ
  • 外部関数によってアップコールに渡されるポインタ
  • メモリー・セグメントから読み取られるポインタ

長さゼロのメモリー・セグメントにアクセスしようとすると、JavaランタイムはIndexOutOfBoundsExceptionをスローします。これは、サイズが不明なメモリー領域にJavaランタイムが安全にアクセスしたり、アクセス操作を検証したりできないためです。また、長さゼロのメモリー・セグメントは、常に有効な新しいスコープに関連付けられます。したがって、長さゼロのメモリー・セグメントに直接アクセスできない場合でも、外部関数を受け入れる他のポインタに渡すことができます。

ただし、MemorySegment::reinterpretメソッドを使用すると、長さゼロのメモリー・セグメントを操作できるためそれらに安全にアクセスでき、既存のアリーナに接続できるためセグメントを支えるメモリー領域の存続期間を自動的に管理できます。このメソッドは次の3つの引数を使用します。

  • メモリー・セグメントのサイズを変更するバイト数: この例では、パラメータbyteSizeの値にサイズ変更します。
  • メモリー・セグメントを関連付けるアリーナ: この例では、パラメータarenaで指定されたアリーナに関連付けます。
  • アリーナが閉じたときに実行するアクション: この例では、C標準ライブラリ関数void free(void *ptr)を呼び出して、mallocによって割り当てられたメモリーの割当てを解除します。これにより、mallocから返されるポインタによって参照されるメモリーの割当てが解除されます。これは、長さゼロのメモリー・セグメントを指すポインタを外部関数に渡す例です。

ノート:

MemorySegment::reinterpretは制限されたメソッドであり、不正に使用するとJVMがクラッシュしたり、メモリーが破損したりする可能性があります。詳細は、「制限されたメソッド」を参照してください。

次の例では、allocateMemory(long, Arena)を呼び出して、mallocを含むJava文字列を割り当てます。

String s = "My string!";
try (Arena arena = Arena.ofConfined()) {
            
    // Allocate off-heap memory with malloc()
    var nativeText = allocateMemory(
        ValueLayout.JAVA_CHAR.byteSize() * (s.length() + 1), arena);
            
    // Access off-heap memory
    for (int i = 0; i < s.length(); i++ ) {
        nativeText.setAtIndex(ValueLayout.JAVA_CHAR, i, s.charAt(i)); 
    }
            
    // Add the string terminator at the end
    nativeText.setAtIndex(
         ValueLayout.JAVA_CHAR, s.length(), Character.MIN_VALUE);
                 
    // Print the string
    for (int i = 0; i < s.length(); i++ ) {
        System.out.print((char)nativeText.getAtIndex(ValueLayout.JAVA_CHAR, i)); 
    }       
    System.out.println();            
} catch (Throwable t) {
    t.printStackTrace();
}

詳細は、java.lang.foreign.MemorySegment API仕様の長さゼロのメモリー・セグメントに関する項およびjava.lang.foreign.Linker API仕様のポインタを返す関数に関する項を参照してください。