返回

构建原生共享库

要构建一个原生共享库,请将命令行选项 --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 APIJNI Invocation API

本指南描述了如何使用 Native Image C API。它包含以下步骤:

  1. 创建并编译一个包含至少一个入口方法的 Java 类库。
  2. 使用 native-image 工具从 Java 类库创建共享库。
  3. 创建并编译一个调用共享库中该入口方法的 C 应用程序。

提示和技巧

共享库必须至少有一个入口方法。默认情况下,只有名为 main() 的方法(源自 public static void main() 方法)被识别为入口点并可从 C 应用程序调用。

要导出任何其他 Java 方法:

  • 将方法声明为 static
  • 使用 @CEntryPoint (org.graalvm.nativeimage.c.function.CEntryPoint) 注解该方法。
  • 使方法的参数之一为 IsolateThreadIsolate 类型,例如,下面方法中的第一个参数 (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!。有关其他安装选项,请访问 下载部分

  1. 将以下 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 注解被识别为入口点,并且该方法作为注解的参数被赋予一个名称。

  2. 编译 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,请使用头文件中的函数声明来设置您的外部调用绑定。

  3. 在同一目录中创建一个 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" 加载原生共享库。

  4. 使用系统上可用的 clang 编译器编译 main.c
     clang -I ./ -L ./ -l envmap -Wl,-rpath ./ -o main main.c
    

    它会创建一个可执行文件 main

  5. 通过传递一个字符串作为参数来运行 C 应用程序。例如:
     ./main USER
    

    它正确打印出匹配的环境变量的名称和值。

使用 Native Image C API 的优点是您可以决定您的 API 将是什么样子。限制是您的参数和返回类型必须是非对象类型。如果您想从 C 管理 Java 对象,您应该考虑 JNI Invocation API

联系我们