◀返回
构建原生共享库
要构建原生共享库,请将命令行参数 --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 方法
- 将方法声明为静态。
- 用
@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; } }
请注意,方法
filterEnv()
如何使用@CEntryPoint
注解标识为入口点,并且该方法在注解中作为参数提供了一个名称。 - 编译 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