使用JNA读取Windows USN日志时出现错误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

