什么是从Java调用Windows内核函数的最简单方法?

7
在搜索如何做到这一点时,我发现有关不同选项(例如JNI与JNA)的模糊讨论,但很少有具体的例子。
背景:如果Java的File.renameTo()无法完成它的工作(因为任何原因; 它有一些问题),我想直接使用此本地Windows函数,该函数定义在kernel32.dll中(来自此答案)。
BOOL WINAPI MoveFile(
  __in  LPCTSTR lpExistingFileName,
  __in  LPCTSTR lpNewFileName
);

那么,无论使用哪种方法,您要如何从Java代码中调用该函数?我正在寻找最简单的方法,最少量的非Java代码或额外步骤(例如在编译或部署中)。

3个回答

7

如果你选择使用JNA,考虑直接调用MoveFileW - 这样可以避免提供配置信息来选择Unicode和ANSI调用。

import java.io.*;
import com.sun.jna.*;

public class Ren {

  static interface Kernel32 extends Library {
    public static Kernel32 INSTANCE = (Kernel32) Native
        .loadLibrary("Kernel32", Kernel32.class);

    public static int FORMAT_MESSAGE_FROM_SYSTEM = 4096;
    public static int FORMAT_MESSAGE_IGNORE_INSERTS = 512;

    public boolean MoveFileW(WString lpExistingFileName,
        WString lpNewFileName);

    public int GetLastError();

    public int FormatMessageW(int dwFlags,
        Pointer lpSource, int dwMessageId,
        int dwLanguageId, char[] lpBuffer, int nSize,
        Pointer Arguments);
  }

  public static String getLastError() {
    int dwMessageId = Kernel32.INSTANCE.GetLastError();
    char[] lpBuffer = new char[1024];
    int lenW = Kernel32.INSTANCE.FormatMessageW(
        Kernel32.FORMAT_MESSAGE_FROM_SYSTEM
            | Kernel32.FORMAT_MESSAGE_IGNORE_INSERTS, null,
        dwMessageId, 0, lpBuffer, lpBuffer.length, null);
    return new String(lpBuffer, 0, lenW);
  }

  public static void main(String[] args) throws IOException {
    String from = ".\\from.txt";
    String to = ".\\to.txt";
    new FileOutputStream(from).close();
    if (!Kernel32.INSTANCE.MoveFileW(new WString(from),
        new WString(to))) {
      throw new IOException(getLastError());
    }
  }
}

编辑:在检查代码后,我修改了我的答案——我误解了签名中使用char[]的方式——最好使用WString


好的,但是如何直接调用MoveFileW呢?像Matthew Flaschen建议的那样吗?如果能提供一些示例代码(例如如何使用Native.loadLibrary()等),那就更好了。 - Jonik
谢谢JNA的示例!顺便问一下,MoveFileA(jitter答案中提到的)的接口会是什么样子?只是使用“MoveFileA”而不是“MoveFileW”是不行的。我真的想移动/重命名一个目录,但是用这个方法无法实现 - 虽然我不确定是因为MoveFileW还是其他原因。 - Jonik
该函数可以定义为MoveFileA(字符串,字符串),但这样做会限制您可以使用的文件名。特别是如果您正在使用NTFS,您可能无法将其用于系统上的所有文件。我在目录上测试了上述代码,所以您的问题可能是其他原因引起的。 - McDowell
我接受这个答案,因为我认为解决方案(包括示例代码)应该就在SO上。当然,如果有更好/更简单的方法被发布,我会进行更改。 :) - Jonik

1
如果这是真正必要的(renameTo不起作用,而你确定MoveFile会起作用),我建议使用JNA。看起来大部分工作已经在com.mucommander.file.util.Kernel32.java/Kernel32API.java中完成了。

不要忘记使用GetLastError()来获取有意义的解释,以了解失败移动的原因,与File.renameTo()不同。 - akarnokd
我不确定MoveFile会起作用,但我可以尝试一下,因为renameTo肯定不行。顺便说一句,你链接的这两个文件并不完全匹配;Kernel32API.DEFAULT_OPTIONS缺失了。你应该将什么作为第三个参数传递给Native.loadLibrary?我正在尝试使用空映射,但无法使其工作;出现“ClassCastException:$Proxy0无法转换为com.sun.jna.Library”的错误。 - Jonik
@jitter,我只是在检查它。 - Jonik
1
Jonik,我已经修复了到最新版本的链接,所以它们应该同步。请注意,DEFAULT_OPTIONS在com.sun.jna.examples.win32.W32API中定义(https://jna.dev.java.net/javadoc/com/sun/jna/examples/win32/W32API.html#DEFAULT_OPTIONS),而Kernel32API则继承自此类。 - Matthew Flaschen

1

基于NativeCall库,我完成了以下POC应用程序

它使用kernel32.dll中的MoveFileA函数。

它作为完整的工作示例提供,包括run.bat和所有jar和dll文件。

它将包含的test.txt移动到test2.txt。


如果您不喜欢我使用的 NativeCall 库版本,我还创建了另一个POC 应用程序,基于/重复使用了Java Native Access (JNA)库。这次演示了 MoveFileAMoveFileW 的实现。


谢谢!使用NativeCall,我能够快速移动Java中的大目录(虽然我还没有在renameTo经常失败的情况下尝试过),进行调用的代码确实很简单。但现在我对NativeCall.dll文件有些疑问-如果您需要始终将其放在工作目录中,则似乎很笨重(如果它位于其中一个jar中,对API的用户透明地使用,那么嵌入lib会更容易些)。 - Jonik
由于您不喜欢NativeCall版本,我也做了一个JNA POC应用程序。同时实现MoveFileA和MoveFileW。 - jitter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接