◀返回
建置原生共享函式庫
要建置原生共享函式庫,請將命令列引數 --shared
傳遞給 native-image
工具,如下所示
native-image <class name> --shared
要從 JAR 檔案建置原生共享函式庫,請使用以下語法
native-image -jar <jarfile> --shared
產生的原生共享函式庫會將給定 Java 類別的 main()
方法作為其進入點方法。
如果您的函式庫不包含 main()
方法,請使用 -o
命令列選項來指定函式庫名稱,如下所示
native-image --shared -o <libraryname> <class name>
native-image --shared -jar <jarfile> -o <libraryname>
GraalVM 讓使用 C 呼叫原生共享函式庫變得容易。呼叫嵌入原生共享函式庫中的方法(函式)主要有兩種機制:原生映像 C API 和 JNI 呼叫 API。
本指南描述如何使用原生映像 C API。它包含以下步驟
- 建立並編譯至少包含一個進入點方法的 Java 類別函式庫。
- 使用
native-image
工具從 Java 類別函式庫建立共享函式庫。 - 建立並編譯一個 C 應用程式,該應用程式呼叫共享函式庫中的進入點方法。
提示與技巧
共享函式庫必須至少有一個進入點方法。預設情況下,只有名稱為 main()
的方法,源自 public static void main()
方法,才會被識別為進入點,並且可以從 C 應用程式呼叫。
要匯出任何其他 Java 方法
- 將該方法宣告為 static。
- 使用
@CEntryPoint
(org.graalvm.nativeimage.c.function.CEntryPoint
) 註釋該方法。 - 使該方法的一個參數類型為
IsolateThread
或Isolate
,例如,下面方法中的第一個參數 (org.graalvm.nativeimage.IsolateThread
)。此參數為呼叫提供目前執行緒的執行環境。 - 將您的參數和回傳類型限制為非物件類型。這些是 Java 基本類型,包括來自
org.graalvm.nativeimage.c.type
套件的指標。 - 為該方法提供唯一的名稱。如果為兩個公開的方法指定相同的名稱,則
native-image
建置器將會失敗,並顯示duplicate symbol
訊息。如果未在註釋中指定名稱,則必須在建置時提供-o <libraryName>
選項。
以下是進入點方法的範例
@CEntryPoint(name = "function_name")
static int add(IsolateThread thread, int a, int b) {
return a + b;
}
當 native-image
工具建置原生共享函式庫時,也會產生 C 標頭檔。標頭檔包含 原生映像 C API 的宣告(可讓您從 C 程式碼建立隔離區並附加執行緒),以及共享函式庫中每個進入點的宣告。這是上述範例的 C 標頭宣告
int add(graal_isolatethread_t* thread, int a, int b);
原生共享函式庫可以有無限數量的進入點,例如用於實作回呼或 API。
執行示範
在以下範例中,您會建立一個小的 Java 類別函式庫(包含一個類別),使用 native-image
從類別函式庫建立共享函式庫,然後建立一個小的 C 應用程式來使用該共享函式庫。C 應用程式會將字串作為引數,將其傳遞給共享函式庫,並列印包含引數的環境變數。
先決條件
請確保您已安裝 GraalVM JDK。入門最簡單的方法是使用 SDKMAN!。如需其他安裝選項,請造訪下載區。
-
將以下 Java 程式碼儲存到名為 LibEnvMap.java 的檔案中
import java.util.Map; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.c.function.CEntryPoint; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CTypeConversion; public class LibEnvMap { //NOTE: this class has no main() method @CEntryPoint(name = "filter_env") private static int filterEnv(IsolateThread thread, CCharPointer cFilter) { String filter = CTypeConversion.toJavaString(cFilter); Map<String, String> env = System.getenv(); int count = 0; for (String envName : env.keySet()) { if(!envName.contains(filter)) continue; System.out.format("%s=%s%n", envName, env.get(envName)); count++; } return count; } }
請注意如何使用
@CEntryPoint
註釋將方法filterEnv()
識別為進入點,並在註釋中將方法命名為引數。 - 編譯 Java 程式碼並建置原生共享函式庫,如下所示
javac LibEnvMap.java
native-image -o libenvmap --shared
它會產生以下成品
-------------------------------------------------- Produced artifacts: /demo/graal_isolate.h (header) /demo/graal_isolate_dynamic.h (header) /demo/libenvmap.dylib (shared_lib) /demo/libenvmap.h (header) /demo/libenvmap_dynamic.h (header) ==================================================
如果您使用 C 或 C++,請直接使用這些標頭檔。對於其他語言(例如 Java),請使用標頭中的函式宣告來設定您的外部呼叫繫結。
- 在包含以下程式碼的同一目錄中建立 C 應用程式 main.c
#include <stdio.h> #include <stdlib.h> #include "libenvmap.h" int main(int argc, char **argv) { if (argc != 2) { fprintf(stderr, "Usage: %s <filter>\n", argv[0]); exit(1); } graal_isolate_t *isolate = NULL; graal_isolatethread_t *thread = NULL; if (graal_create_isolate(NULL, &isolate, &thread) != 0) { fprintf(stderr, "initialization error\n"); return 1; } printf("Number of entries: %d\n", filter_env(thread, argv[1])); graal_tear_down_isolate(thread); }
陳述式
#include "libenvmap.h"
會載入原生共享函式庫。 - 使用系統上可用的
clang
編譯器編譯 main.cclang -I ./ -L ./ -l envmap -Wl,-rpath ./ -o main main.c
它會建立一個可執行檔 main。
- 透過傳遞字串作為引數來執行 C 應用程式。例如
./main USER
它會正確列印出相符環境變數的名稱和值。
使用原生映像 C API 的優點是您可以決定 API 的外觀。限制是參數和回傳類型必須為非物件類型。如果您想要從 C 管理 Java 物件,則應該考慮使用 JNI 呼叫 API。
相關文件
- 嵌入 Truffle 語言 – Kevin Menard 的一篇部落格文章,他在其中比較了公開 Java 方法的兩種機制。
- 與原生程式碼的互通性
- 原生映像中的 Java 原生介面 (JNI)
- 原生映像 C API