Java JNA调用OpenFileById失败,错误码87(无效参数)求助
通过NTFS文件ID获取文件句柄(JNA):错误码87修复方案
问题根源分析
错误码87(无效参数)的核心原因是结构体定义与Windows原生布局不匹配,以及文件ID类型误用:
- NTFS的文件ID是128位(对应
FILE_ID_128),你当前代码误用了64位的FileId字段,和FILE_ID_INFO.FileId.Identifier的128位数据不兼容 - JNA中Union类型需要显式指定激活字段,否则内存布局会混乱
FILE_ID_DESCRIPTOR的Type参数未设置为NTFS对应的扩展类型
修正步骤与代码实现
1. 修正结构体定义
严格对齐Windows原生结构体的内存布局:
import com.sun.jna.*; import com.sun.jna.platform.win32.*; import com.sun.jna.platform.win32.WinBase.FILE_ID_INFO; import java.util.List; public class NtfsFileIdHandler { // 对应Windows原生FILE_ID_128结构体 public static class FILE_ID_128 extends Structure { public byte[] Identifier = new byte[16]; public FILE_ID_128() { super(); } public FILE_ID_128(Pointer p) { super(p); read(); } @Override protected List<String> getFieldOrder() { return List.of("Identifier"); } } // 修正后的FILE_ID_DESCRIPTOR及内部Union public static class FILE_ID_DESCRIPTOR extends Structure { public WinDef.UINT dwSize; public int Type; // 0=64位ID, 1=NTFS扩展128位ID public DUMMYUNIONNAME Id; public static class DUMMYUNIONNAME extends Union { public long FileId; // 旧文件系统用64位ID public Guid.GUID ObjectId; public FILE_ID_128 ExtendedFileId; // NTFS用128位ID public DUMMYUNIONNAME() { super(); } // 针对NTFS 128位ID的构造方法 public DUMMYUNIONNAME(FILE_ID_128 extendedId) { this.ExtendedFileId = extendedId; setType(FILE_ID_128.class); // 必须显式指定当前激活的Union字段 write(); } } public FILE_ID_DESCRIPTOR() { super(); this.dwSize = new WinDef.UINT(size()); } // 快速构造NTFS专用的DESCRIPTOR public FILE_ID_DESCRIPTOR(FILE_ID_128 fileId128) { this.dwSize = new WinDef.UINT(size()); this.Type = 1; // 指定为扩展128位ID类型 this.Id = new DUMMYUNIONNAME(fileId128); write(); } @Override protected List<String> getFieldOrder() { return List.of("dwSize", "Type", "Id"); } } // 扩展Kernel32接口,声明OpenFileById方法 public interface Kernel32Extended extends Kernel32 { Kernel32Extended INSTANCE = Native.load("kernel32", Kernel32Extended.class, W32APIOptions.DEFAULT_OPTIONS); WinNT.HANDLE OpenFileById( WinNT.HANDLE hVolumeHint, FILE_ID_DESCRIPTOR lpFileId, int dwDesiredAccess, int dwShareMode, WinBase.SECURITY_ATTRIBUTES lpSecurityAttributes, int dwFlagsAndAttributes ); } }
2. 修正OpenFileById调用逻辑
public static WinNT.HANDLE openFileByIdFromExistingHandle(WinNT.HANDLE hFile) { // 步骤1:获取FILE_ID_INFO Memory p = new Memory(128); boolean success = Kernel32.INSTANCE.GetFileInformationByHandleEx( hFile, WinBase.FileIdInfo, p, new WinDef.DWORD(p.size()) ); if (!success) { throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); } FILE_ID_INFO fii = new FILE_ID_INFO(p); // 步骤2:通过卷序列号匹配对应盘符(保留原逻辑) String[] drives = Kernel32Util.getLogicalDriveStrings(); String volumePath = null; WinDef.DWORDByReference serialNum = new WinDef.DWORDByReference(); for (String drive : drives) { if (Kernel32.INSTANCE.GetVolumeInformation( drive, null, 0, serialNum, null, null, null, 0)) { if (serialNum.getValue().longValue() == fii.VolumeSerialNumber.longValue()) { volumePath = drive; break; } } } if (volumePath == null) { throw new RuntimeException("未找到目标卷"); } // 步骤3:打开卷句柄(提升权限,原权限不足以支持OpenFileById) WinNT.HANDLE hVolume = Kernel32.INSTANCE.CreateFile( volumePath, WinNT.FILE_READ_DATA, WinNT.FILE_SHARE_READ | WinNT.FILE_SHARE_WRITE | WinNT.FILE_SHARE_DELETE, null, WinNT.OPEN_EXISTING, WinNT.FILE_FLAG_BACKUP_SEMANTICS, null ); if (WinNT.INVALID_HANDLE_VALUE.equals(hVolume)) { throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); } // 步骤4:构造正确的DESCRIPTOR并调用OpenFileById NtfsFileIdHandler.FILE_ID_128 fileId128 = new NtfsFileIdHandler.FILE_ID_128(); System.arraycopy(fii.FileId.Identifier, 0, fileId128.Identifier, 0, 16); NtfsFileIdHandler.FILE_ID_DESCRIPTOR fileIdDesc = new NtfsFileIdHandler.FILE_ID_DESCRIPTOR(fileId128); WinNT.HANDLE targetHandle = NtfsFileIdHandler.Kernel32Extended.INSTANCE.OpenFileById( hVolume, fileIdDesc, WinNT.FILE_READ_ATTRIBUTES, WinNT.FILE_SHARE_READ | WinNT.FILE_SHARE_WRITE | WinNT.FILE_SHARE_DELETE, null, WinNT.FILE_FLAG_BACKUP_SEMANTICS ); // 关闭卷句柄(按需保留,若后续需复用可跳过关闭) Kernel32.INSTANCE.CloseHandle(hVolume); if (WinNT.INVALID_HANDLE_VALUE.equals(targetHandle)) { throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); } return targetHandle; }
关键修正点说明
- Union字段激活:JNA的Union必须通过
setType()指定当前使用的字段,否则内存布局会混乱 - 文件ID类型匹配:NTFS使用128位ID,因此
Type需设为1(扩展类型),而非原代码的0 - 卷句柄权限:原
FILE_READ_ATTRIBUTES权限不足,需提升为FILE_READ_DATA才能支持OpenFileById - 结构体字段顺序:严格遵循Windows原生定义的字段顺序,确保内存对齐正确
内容的提问来源于stack exchange,提问作者Andriy Y
相关产品推荐
相关产品推荐

