返回

构建原生共享库

要构建原生共享库,请将命令行参数 --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 APIJNI 调用 API

本指南介绍如何使用 **原生镜像 C API**。它包括以下步骤

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

技巧

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

要导出任何其他 Java 方法

  • 将方法声明为静态。
  • @CEntryPointorg.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 头文件。头文件包含对 原生镜像 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
    

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

使用原生镜像 C API 的优势在于,您可以确定 API 的外观。限制是您的参数和返回类型必须是非对象类型。如果您想从 C 管理 Java 对象,您应该考虑 JNI 调用 API

联系我们