You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用JNA读取Windows USN日志时出现错误1784的问题

解决JNA调用FSCTL_READ_USN_JOURNAL时的错误1784(缓冲区无效)

结合你的场景——32位Java跑在64位Windows上,C++逻辑正常但JNA报错,这个1784错误几乎可以肯定是结构体内存布局不匹配或者缓冲区对齐/大小问题导致的。我之前处理过类似的跨位JNA调用问题,给你几个针对性的排查和解决方向:

1. 强制结构体对齐方式匹配Windows VC++规则

Windows的USN相关结构体(比如READ_USN_JOURNAL_DATA、USN_JOURNAL_DATA)在32位和64位环境下的内存对齐规则是MSVC标准,而JNA默认的对齐可能和这个不兼容,导致结构体大小错位,传递给API的缓冲区自然就无效了。

解决方法:在自定义的Structure子类上显式指定对齐方式为ALIGN_MSVC,同时用@Structure.FieldOrder严格按照Windows SDK的字段顺序定义:

@Structure.FieldOrder({"StartUsn", "ReasonMask", "ReturnOnlyOnClose", "Timeout", "BytesToWaitFor", "UsnJournalID"})
public static class READ_USN_JOURNAL_DATA extends Structure {
    public long StartUsn; // USN是64位整数,必须用long
    public long ReasonMask;
    public byte ReturnOnlyOnClose; // Windows的BOOLEAN是1字节,不能用boolean(JNA里boolean是4字节)
    public int Timeout;
    public int BytesToWaitFor;
    public long UsnJournalID;

    public READ_USN_JOURNAL_DATA() {
        super(ALIGN_MSVC); // 强制MSVC对齐
    }
}

同样的,USN_JOURNAL_DATA也要做相同的处理,确保每个字段的类型、顺序、对齐都和C++里的定义完全一致。

2. 确保缓冲区按磁盘扇区大小对齐

FSCTL_READ_USN_JOURNAL要求输出缓冲区必须按磁盘扇区大小(通常是512或4096字节)对齐,32位Java直接分配的Memory可能不满足这个要求,导致API返回无效缓冲区错误。

解决方法:分配内存时调用align()方法确保对齐:

// 分配4096字节的缓冲区,对齐到512字节扇区
int bufferSize = 4096;
Pointer outputBuffer = new Memory(bufferSize).align(512);

另外,缓冲区大小不能小于USN_JOURNAL_DATA里返回的MinimumRecordLength,最好参考你C++代码里使用的缓冲区大小,保持一致。

3. 检查DeviceIoControl的参数传递

调用DeviceIoControl时,几个关键参数不能错:

  • nInBufferSize必须是READ_USN_JOURNAL_DATA结构体的实际大小(调用readData.size()获取)
  • nOutBufferSize是你分配的缓冲区的总大小
  • lpBytesReturned必须是一个初始化的IntByReference,不能传null

示例调用代码:

IntByReference outBytesReturned = new IntByReference();
boolean success = Kernel32.INSTANCE.DeviceIoControl(
        driveHandle,
        FSCTL_READ_USN_JOURNAL,
        readData.getPointer(),
        readData.size(),
        outputBuffer,
        bufferSize,
        outBytesReturned,
        null // 同步调用,不需要重叠结构
);

4. 验证结构体大小是否匹配C++版本

你可以在C++里打印结构体的大小(比如sizeof(READ_USN_JOURNAL_DATA)),然后在Java里打印readData.size(),如果两个值不一样,说明结构体映射肯定有问题,大概率是字段类型错误或者对齐没设置对。比如32位环境下READ_USN_JOURNAL_DATA的大小应该是32字节,64位是40字节,你可以对比一下。

修正后的完整示例片段

上面的要点整合起来,你可以参考这个简化的代码框架(确保和你的C++逻辑一致):

// 省略import部分
public class UsnReader {
    public static final int FSCTL_READ_USN_JOURNAL = 0x000900F7;
    public static final int FSCTL_QUERY_USN_JOURNAL = 0x000900F4;

    @Structure.FieldOrder({"UsnJournalID", "FirstUsn", "NextUsn", "LowestValidUsn", "MaxUsn", "MaximumSize", "AllocationDelta", "MinimumRecordLength"})
    public static class USN_JOURNAL_DATA extends Structure {
        public long UsnJournalID;
        public long FirstUsn;
        public long NextUsn;
        public long LowestValidUsn;
        public long MaxUsn;
        public long MaximumSize;
        public long AllocationDelta;
        public int MinimumRecordLength;

        public USN_JOURNAL_DATA() { super(ALIGN_MSVC); }
    }

    @Structure.FieldOrder({"StartUsn", "ReasonMask", "ReturnOnlyOnClose", "Timeout", "BytesToWaitFor", "UsnJournalID"})
    public static class READ_USN_JOURNAL_DATA extends Structure {
        public long StartUsn;
        public long ReasonMask;
        public byte ReturnOnlyOnClose;
        public int Timeout;
        public int BytesToWaitFor;
        public long UsnJournalID;

        public READ_USN_JOURNAL_DATA() { super(ALIGN_MSVC); }
    }

    public static void main(String[] args) {
        // 打开驱动器句柄(省略你的已有代码)
        Pointer driveHandle = ...;

        try {
            // 查询日志信息
            USN_JOURNAL_DATA journalData = new USN_JOURNAL_DATA();
            IntByReference bytesReturned = new IntByReference();
            Kernel32.INSTANCE.DeviceIoControl(
                    driveHandle, FSCTL_QUERY_USN_JOURNAL,
                    null, 0,
                    journalData.getPointer(), journalData.size(),
                    bytesReturned, null
            );
            journalData.read();

            // 准备读取参数
            READ_USN_JOURNAL_DATA readParams = new READ_USN_JOURNAL_DATA();
            readParams.StartUsn = journalData.FirstUsn;
            readParams.ReasonMask = 0xFFFFFFFFL; // 捕获所有事件
            readParams.ReturnOnlyOnClose = 0;
            readParams.BytesToWaitFor = 4096;
            readParams.UsnJournalID = journalData.UsnJournalID;
            readParams.write();

            // 分配对齐缓冲区
            int bufferSize = 4096;
            Pointer outputBuf = new Memory(bufferSize).align(512);
            IntByReference outBytes = new IntByReference();

            boolean readSuccess = Kernel32.INSTANCE.DeviceIoControl(
                    driveHandle, FSCTL_READ_USN_JOURNAL,
                    readParams.getPointer(), readParams.size(),
                    outputBuf, bufferSize,
                    outBytes, null
            );

            if (!readSuccess) {
                int err = Kernel32.INSTANCE.GetLastError();
                System.err.println("Error: " + err);
            } else {
                System.out.println("Read " + outBytes.getValue() + " bytes of USN data");
                // 后续解析USN_RECORD的逻辑
            }
        } finally {
            Kernel32.INSTANCE.CloseHandle(driveHandle);
        }
    }
}

按照这些步骤调整后,应该就能解决1784的错误了——核心就是让JNA的结构体和内存布局完全贴合Windows API的要求,毕竟C++能正常运行,说明你的逻辑是对的,问题就出在JNA的跨位映射细节上。

内容的提问来源于stack exchange,提问作者Matthias

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 10:45:44