杰瑞科技汇

Linux C如何通过JNI调用Java方法?

Of course! This is a very common and powerful technique in the Java world. Let's break down how to use JNI (Java Native Interface) to call C code from a Linux C environment.

Linux C如何通过JNI调用Java方法?-图1
(图片来源网络,侵删)

We'll cover:

  1. What is JNI? - A quick conceptual overview.
  2. The Workflow - The step-by-step process of creating a JNI project.
  3. A Complete "Hello, World!" Example - A simple, runnable example to get you started.
  4. Passing Data - How to pass strings and integers between Java and C.
  5. Building and Running - The essential gcc and java commands on Linux.
  6. Troubleshooting - Common pitfalls and solutions.

What is JNI?

The Java Native Interface (JNI) is a framework that allows Java code running in the Java Virtual Machine (JVM) to call and be called by native applications and libraries written in other languages like C, C++, and assembly.

Why use it?

  • Performance: To leverage highly optimized C/C++ libraries for computationally intensive tasks (e.g., math libraries, physics engines).
  • Hardware/OS Interaction: To interact with hardware or operating system features that are not accessible through the standard Java API (e.g., specific I/O ports, kernel-level features).
  • Legacy Code: To reuse existing, legacy C/C++ codebases without rewriting them in Java.

The Big Picture: Java App -> JVM -> JNI Bridge -> Native C Library -> Linux Kernel/Hardware

Linux C如何通过JNI调用Java方法?-图2
(图片来源网络,侵删)

The Workflow

Here is the standard process for creating a JNI application:

  1. Write the Java Code: Create a Java class that declares the native methods you want to implement in C. You also need a special static block to load the native library.
  2. Compile the Java Code: Compile your .java file into a .class file.
  3. Generate the Header File: Use the javah tool (or javac -h in modern Java) on the compiled .class file to create a C header file (.h). This header file contains the C function signatures that you must implement.
  4. Write the C Code: Implement the native functions in a C source file (.c), including the necessary JNI headers.
  5. Compile the C Code: Compile your C source file into a shared object library (.so file) that the JVM can load.
  6. Run the Java Application: Execute your Java code, making sure the JVM can find your newly created .so library.

A Complete "Hello, World!" Example

Let's create a simple example where a Java application calls a C function that prints "Hello from C!" to the console.

Step 1: Write the Java Code

Create a file named HelloJNI.java.

// HelloJNI.java
public class HelloJNI {
    // Declare the native method. The implementation will be in C.
    private native void sayHello();
    // A static block to load the native library.
    // The name "libhellojni" will correspond to "libhellojni.so".
    static {
        System.loadLibrary("hellojni");
    }
    public static void main(String[] args) {
        new HelloJNI().sayHello();
    }
}

Key Points:

Linux C如何通过JNI调用Java方法?-图3
(图片来源网络,侵删)
  • private native void sayHello();: The native keyword tells the compiler that this method's implementation is not in Java.
  • static { System.loadLibrary("hellojni"); }: This block runs when the class is loaded. It tells the JVM to load the shared library named libhellojni.so. The lib prefix and .so extension are standard on Linux, so you just provide the middle part.

Step 2: Compile the Java Code

Open your terminal and run the Java compiler:

javac HelloJNI.java

This will produce a HelloJNI.class file.

Step 3: Generate the C Header File

Now, use javah to generate the C header. (For Java 9+, use javac -h).

# The -jni flag is the default, but it's good to be explicit
javac -h . HelloJNI.java
  • -h .: Specifies that the header file should be generated in the current directory.

This command will create a file named HelloJNI.h. Do not edit this file! It's your contract.

Step 4: Examine and Write the C Code

First, let's look at the generated HelloJNI.h:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloJNI */
#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloJNI
 * Method:    sayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloJNI_sayHello
  (JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif

Key Points in the Header:

  • JNIEXPORT void JNICALL: These are macros that define the function's calling convention and export status. They are essential.
  • Java_HelloJNI_sayHello: The C function name is generated by concatenating Java_, the package name (if any), the class name, and the method name. Since we have no package, it's just Java_HelloJNI_sayHello.
  • (JNIEnv *, jobject): These are the first two arguments to every JNI method.
    • JNIEnv *: A pointer to the JNI "environment". You use this pointer to call other JNI functions (e.g., to create objects, throw exceptions, access fields).
    • jobject: A reference to the HelloJNI Java object instance that called the native method. For static methods, this would be a jobject (a reference to the java.lang.Class object).

Now, create the C implementation file, HelloJNI.c:

#include <stdio.h>
#include "HelloJNI.h" // Include the generated header
// Implement the native method
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
    printf("Hello from C!\n");
}

This is straightforward: we include the header and implement the function with the exact signature from the header.

Step 5: Compile the C Code into a Shared Library

This is the most critical step on Linux. You need to compile your C code into a shared object (.so) file. Use gcc for this.

gcc -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux -fPIC -shared -o libhellojni.so HelloJNI.c

Let's break down this command:

  • gcc: The GNU C Compiler.
  • -I${JAVA_HOME}/include: Tells the compiler where to find the main JNI headers (jni.h). You must set the JAVA_HOME environment variable to your JDK installation path (e.g., /usr/lib/jvm/java-11-openjdk-amd64/).
  • -I${JAVA_HOME}/include/linux: Specifies the platform-specific JNI headers (e.g., jni_md.h).
  • -fPIC: Very important! It generates Position-Independent Code, which is required for shared libraries.
  • -shared: Tells the compiler to produce a shared library (.so file).
  • -o libhellojni.so: Specifies the name of the output file. The lib prefix and .so extension are standard.
  • HelloJNI.c: Your C source file.

Step 6: Run the Java Application

Now you're ready to run it. You need to tell the Java Virtual Machine where to find your .so library using the -Djava.library.path option.

java -Djava.library path=. HelloJNI

Expected Output:

Hello from C!

Passing Data Between Java and C

Let's extend our example to pass a string from Java to C and an integer back.

Updated Java Code (`HelloJNI.java``)

public class HelloJNI {
    // ... sayHello method from before ...
    // New native method to pass data
    private native int add(int a, int b);
    private native String sayHelloTo(String name);
    static {
        System.loadLibrary("hellojni");
    }
    public static void main(String[] args) {
        HelloJNI myApp = new HelloJNI();
        myApp.sayHello();
        int sum = myApp.add(10, 25);
        System.out.println("10 + 25 = " + sum);
        String greeting = myApp.sayHelloTo("JNI User");
        System.out.println("Message from C: " + greeting);
    }
}

Updated C Code (HelloJNI.c)

#include <stdio.h>
#include "HelloJNI.h"
// ... Java_HelloJNI_sayHello from before ...
// JNI method to add two integers
JNIEXPORT jint JNICALL Java_HelloJNI_add(JNIEnv *env, jobject thisObj, jint a, jint b) {
    printf("C: Received %d and %d\n", a, b);
    return a + b;
}
// JNI method to take a string and return a new one
JNIEXPORT jstring JNICALL Java_HelloJNI_sayHelloTo(JNIEnv *env, jobject thisObj, jstring name) {
    // 1. Get the C-style string from the Java String
    const char *c_name = (*env)->GetStringUTFChars(env, name, NULL);
    if (c_name == NULL) {
        return NULL; // OutOfMemoryError already thrown
    }
    printf("C: Received name '%s'\n", c_name);
    // 2. Create a new C string to return
    char *result_string = "Hello, ";
    char *full_greeting = malloc(strlen(result_string) + strlen(c_name) + 1);
    strcpy(full_greeting, result_string);
    strcat(full_greeting, c_name);
    // 3. Create a new Java String from the C string and return it
    jstring result = (*env)->NewStringUTF(env, full_greeting);
    // 4. Release the original Java string to avoid memory leak
    (*env)->ReleaseStringUTFChars(env, name, c_name);
    free(full_greeting);
    return result;
}

Key C Concepts:

  • Basic Types: int in Java maps to jint in C. The mapping is direct.
  • Strings: Java strings (String) are objects. In JNI, they are jstring.
    • GetStringUTFChars(): Converts a jstring to a UTF-8 encoded C-style char*. You must release it.
    • NewStringUTF(): Creates a new jstring from a C-style char*.
  • Memory Management: JNI functions that allocate memory or create objects can throw Java exceptions. You must check for errors and release resources you've acquired (like GetStringUTFChars).

Compile and Run

Compile the updated C code:

gcc -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux -fPIC -shared -o libhellojni.so HelloJNI.c

Run the updated Java code:

java -Djava.library path=. HelloJNI

Expected Output:

Hello from C!
C: Received 10 and 25
10 + 25 = 35
C: Received name 'JNI User'
Message from C: Hello, JNI User

Troubleshooting

  • java.lang.UnsatisfiedLinkError: no hellojni in java.library.path

    • Cause: The JVM cannot find libhellojni.so.
    • Solution:
      1. Make sure the file name is exactly libhellojni.so.
      2. Make sure you are using the correct -Djava.library.path= argument, pointing to the directory containing the .so file.
      3. Verify that the library was compiled correctly with ldd libhellojni.so. It should show dependencies on libjvm.so and other standard libraries.
  • ldd: libhellojni.so: not a dynamic executable

    • Cause: You forgot the -shared flag when compiling with gcc.
    • Solution: Re-run the gcc command with the -shared flag.
  • Segmentation fault (core dumped)

    • Cause: This is a classic C error. You are likely accessing a null pointer, dereferencing an invalid object, or corrupting memory. A common JNI mistake is not checking if a JNI call (like GetStringUTFChars) returned NULL (indicating an OutOfMemoryError was thrown in Java).
    • Solution: Use a debugger like gdb to inspect the core dump. Carefully review your C code for null pointer checks and proper memory management.
  • error: jni.h: No such file or directory

    • Cause: The -I flag for javac or gcc is incorrect, or JAVA_HOME is not set properly.
    • Solution: Double-check that echo $JAVA_HOME outputs the correct path to your JDK. Verify the path to include and include/linux is correct.
分享:
扫描分享到社交APP
上一篇
下一篇