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

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;
}

关键修正点说明

  1. Union字段激活:JNA的Union必须通过setType()指定当前使用的字段,否则内存布局会混乱
  2. 文件ID类型匹配:NTFS使用128位ID,因此Type需设为1(扩展类型),而非原代码的0
  3. 卷句柄权限:原FILE_READ_ATTRIBUTES权限不足,需提升为FILE_READ_DATA才能支持OpenFileById
  4. 结构体字段顺序:严格遵循Windows原生定义的字段顺序,确保内存对齐正确

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 14:47:02