◀返回
构建原生共享库
要构建一个原生共享库,请将命令行选项 --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 调用原生共享库变得容易。调用嵌入在原生共享库中的方法(函数)有两种主要机制:Native Image C API 和 JNI Invocation API。
本指南描述了如何使用 Native Image 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 头文件。该头文件包含 Native Image 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.c:clang -I ./ -L ./ -l envmap -Wl,-rpath ./ -o main main.c
它会创建一个可执行文件
main
。 - 通过传递一个字符串作为参数来运行 C 应用程序。例如:
./main USER
它正确打印出匹配的环境变量的名称和值。
使用 Native Image C API 的优点是您可以决定您的 API 将是什么样子。限制是您的参数和返回类型必须是非对象类型。如果您想从 C 管理 Java 对象,您应该考虑 JNI Invocation API。
相关文档
- 嵌入 Truffle 语言 – Kevin Menard 的一篇博客文章,他比较了公开 Java 方法的两种机制。
- 与原生代码的互操作性
- Native Image 中的 Java Native Interface (JNI)
- 原生镜像 C API