杰瑞科技汇

Windows API如何在Java中调用?

Of course! Using the Windows API from Java is a common requirement for tasks that are specific to the Windows operating system, such as interacting with system processes, modifying the registry, or accessing hardware.

Windows API如何在Java中调用?-图1
(图片来源网络,侵删)

Here’s a comprehensive guide covering the main methods, from simple to advanced, with code examples.

The Core Challenge: The Java Platform Independence Model

Java's primary design goal is "Write Once, Run Anywhere" (WORA). The Windows API is, by definition, Windows-only. Therefore, to call Windows functions from Java, you need to bridge this gap. There are three main ways to do this:

  1. JNI (Java Native Interface): The most powerful and complex method. It allows Java code to call and be called by native applications (written in C, C++, etc.). This is the "gold standard" for performance and full API access but is difficult to use.
  2. JNA (Java Native Access): A high-level library that simplifies JNI. You just define Java interfaces that mirror the C/C++ functions, and JNA handles the rest. This is the recommended approach for most use cases. It's much easier and safer than JNI.
  3. JNR (Java Native Runtime): Another alternative to JNI, similar in spirit to JNA but with a different implementation. It can sometimes offer better performance for certain patterns but has a steeper learning curve.

Method 1: JNA (Java Native Access) - Recommended

JNA is the most popular and user-friendly way to call Windows APIs from Java. It dynamically loads native libraries and provides a clean, type-safe mapping between Java and C/C++.

Step-by-Step Guide with JNA

Goal: Let's write a simple Java program to get the Windows version.

Windows API如何在Java中调用?-图2
(图片来源网络,侵删)

Add the JNA Dependency

If you're using a build tool like Maven or Gradle, add the JNA dependency.

Maven (pom.xml):

<dependency>
    <groupId>net.java.dev.jna</groupId>
    <artifactId>jna-platform</artifactId>
    <version>5.13.0</version> <!-- Use the latest version -->
</dependency>

Gradle (build.gradle):

Windows API如何在Java中调用?-图3
(图片来源网络,侵删)
implementation 'net.java.dev.jna:jna-platform:5.13.0' // Use the latest version

If you're not using a build tool, download the JNA JARs from the official site and add them to your project's classpath.

Find the Windows API Documentation

You need to know the function names, parameters, and return types as they are defined in C/C++. The best resource is the official Microsoft documentation.

For our example, we need the OSVERSIONINFOEX structure and the GetVersionEx function.

Create Java Mappings

We need to create three Java components:

  • A Structure class to map the C OSVERSIONINFOEX struct.
  • A Library interface to define the GetVersionEx function.
  • The Main Application class to call it all.

a. The Structure Class (OSVERSIONINFOEX.java)

import com.sun.jna.Structure;
import com.sun.jna.WString;
import com.sun.jna.win32.StdCallLibrary;
// The WString mapping is for the 'szCSDVersion' field in the struct.
// JNA automatically converts WString to the Windows LPWSTR type.
public class OSVERSIONINFOEX extends Structure {
    public int dwOSVersionInfoSize;
    public int dwMajorVersion;
    public int dwMinorVersion;
    public int dwBuildNumber;
    public int dwPlatformId;
    public WString szCSDVersion; // e.g., "Service Pack 1"
    // Default constructor
    public OSVERSIONINFOEX() {
        super();
        // The size of the structure in bytes is crucial.
        this.dwOSVersionInfoSize = size();
    }
    @Override
    protected List<String> getFieldOrder() {
        // This defines the memory layout of the C struct.
        return Arrays.asList(
            "dwOSVersionInfoSize", "dwMajorVersion", "dwMinorVersion",
            "dwBuildNumber", "dwPlatformId", "szCSDVersion"
        );
    }
}

b. The Library Interface (Kernel32.java) This interface tells JNA which functions to expose from the native library (kernel32.dll).

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.win32.StdCallLibrary;
import com.sun.jna.WString;
// We extend StdCallLibrary because kernel32.dll uses the stdcall calling convention.
public interface Kernel32 extends StdCallLibrary {
    // A single instance of the library is loaded.
    Kernel32 INSTANCE = (Kernel32) Native.load("kernel32", Kernel32.class);
    // The function signature. JNA automatically handles the boolean return type
    // and the Structure parameter.
    boolean GetVersionEx(OSVERSIONINFOEX lpVersionInformation);
}

c. The Main Application (GetWindowsVersion.java)

public class GetWindowsVersion {
    public static void main(String[] args) {
        // 1. Create an instance of our structure
        OSVERSIONINFOEX osVersionInfo = new OSVERSIONINFOEX();
        // 2. Call the Windows API function
        boolean success = Kernel32.INSTANCE.GetVersionEx(osVersionInfo);
        if (success) {
            System.out.println("Successfully retrieved OS version info.");
            System.out.println("Major Version: " + osVersionInfo.dwMajorVersion);
            System.out.println("Minor Version: " + osVersionInfo.dwMinorVersion);
            System.out.println("Build Number: " + osVersionInfo.dwBuildNumber);
            System.out.println("Platform ID: " + osVersionInfo.dwPlatformId);
            System.out.println("CSD Version: " + osVersionInfo.szCSDVersion);
        } else {
            // Get the last error code from the Windows API
            int errorCode = com.sun.jna.Native.getLastError();
            System.err.println("Failed to get version info. Error code: " + errorCode);
        }
    }
}

When you run this, it will print the version information of the Windows machine it's running on.


Method 2: JNI (Java Native Interface)

JNI is the "hard way" but offers the most control and performance. It's generally recommended only if JNA cannot meet your needs (e.g., extreme performance requirements or complex callback scenarios).

Concept:

  1. Write Java code that declares a native method.
  2. Use the javac compiler to compile the Java code.
  3. Use the javah tool (or javac -h) to generate a C/C++ header file (.h).
  4. Write the C/C++ implementation for that native method.
  5. Compile the C/C++ code into a shared library (.dll on Windows).
  6. Place the .dll in a location where the Java Virtual Machine (JVM) can find it (e.g., in the java.library.path).
  7. Run the Java code.

Example:

Java Code (JniExample.java)

public class JniExample {
    // Declare the native method
    public native void displayMessage(String message);
    static {
        // Load the native library
        System.loadLibrary("JniExample");
    }
    public static void main(String[] args) {
        new JniExample().displayMessage("Hello from Java via JNI!");
    }
}

Generate C Header

# Compile the Java file first
javac JniExample.java
# Generate the header file
javah -jni JniExample

This creates a JniExample.h file.

C/C++ Implementation (JniExample.c)

#include <stdio.h>
#include "JniExample.h" // The generated header
// The JNI_OnLoad function is called when the library is loaded.
// It's good practice for setting up the JNI environment.
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    printf("Native library loaded.\n");
    return JNI_VERSION_1_8;
}
// The implementation of our native method
JNIEXPORT void JNICALL Java_JniExample_displayMessage(JNIEnv *env, jobject obj, jstring message) {
    // Convert the Java String (jstring) to a C-style string (const char*)
    const char *c_message = (*env)->GetStringUTFChars(env, message, NULL);
    // Print the message
    printf("Message from native code: %s\n", c_message);
    // Release the C-style string to avoid memory leaks
    (*env)->ReleaseStringUTFChars(env, message, c_message);
}

Compile to DLL (using MinGW on Windows)

gcc -shared -IC:\path\to\jdk\include -IC:\path\to\jdk\include\win32 JniExample.c -o JniExample.dll

(You need to replace the path with your actual JDK's include directory).

Run Place JniExample.dll in your project directory or a location specified by -Djava.library.path. Then run:

java JniExample

Comparison: JNA vs. JNI

Feature JNA (Java Native Access) JNI (Java Native Interface)
Ease of Use Very High. Just define interfaces and structures. Low. Requires C/C++ knowledge, manual memory management, and build steps.
Performance Good. Slight overhead due to the library layer, but often negligible. Highest. Direct call to native code, minimal overhead.
Memory Management Automatic. JNA handles marshalling and unmarshalling. Manual. You are responsible for allocating and freeing memory in C/C++.
Error Handling Integrated with Java exceptions. Relies on C-style error codes and manual checking.
Dependencies JNA JARs. JDK headers, C/C++ compiler (GCC, MSVC), etc.
Best For 95% of use cases. Calling Win32 APIs, accessing DLLs. Performance-critical code, complex callbacks, or when JNA is not an option.

Summary and Recommendation

For any new project that requires Windows API integration in Java, start with JNA. It is significantly easier, safer, and faster to develop with. The performance is more than adequate for almost all applications.

Use JNI only if you have a very specific reason, such as:

  • You've profiled your application and found that JNA's overhead is a bottleneck.
  • You need to implement a complex callback mechanism that is difficult or impossible to express with JNA.
  • You are integrating with an existing C/C++ codebase that cannot be easily wrapped.
分享:
扫描分享到社交APP
上一篇
下一篇