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

无法读取SD卡原始扇区,ReadFile调用返回错误87求助

解决ReadFile读取原始SD卡返回错误87(ERROR_INVALID_PARAMETER)的思路

首先,错误87(无效参数)在原始磁盘读取场景下,通常和Windows对磁盘访问的严格要求、权限变化或者参数对齐有关,结合你提到的Windows更新后出现问题的背景,给你几个针对性的排查方向:


1. 确认磁盘真实的物理扇区大小,确保读取长度和缓冲区对齐

你提到Linux识别扇区是512字节,但Windows下的物理扇区可能是4K(很多现代SD卡和磁盘都是4K物理扇区,512是逻辑扇区)。如果读取长度或缓冲区没有对齐到物理扇区大小,ReadFile就会返回错误87。

可以用DeviceIoControl获取磁盘的真实物理扇区信息,代码示例:

#include <winioctl.h>

DISK_GEOMETRY_EX diskInfo = {0};
DWORD bytesReturned = 0;
if (DeviceIoControl(memCard, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, 
    NULL, 0, &diskInfo, sizeof(diskInfo), &bytesReturned, NULL))
{
    Logger::Info("Physical sector size: %u bytes", diskInfo.Geometry.BytesPerSector);
    Logger::Info("Media sector size: %u bytes", diskInfo.DiskSize.BytesPerSector);
}

拿到真实扇区大小后,确保:

  • 读取的长度是物理扇区大小的整数倍
  • 缓冲区的内存地址对齐到物理扇区边界(可以用_aligned_malloc分配,比如unsigned char* sector = (unsigned char*)_aligned_malloc(4096, 4096);)

2. 启用SeManageVolumePrivilege权限

部分Windows更新后,即使是管理员权限,访问原始磁盘也需要额外的SeManageVolumePrivilege权限。你可以在程序启动时主动启用这个权限:

#include <windows.h>
#include <accctrl.h>
#include <aclapi.h>

bool EnableVolumePrivilege()
{
    HANDLE hToken;
    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
        return false;

    TOKEN_PRIVILEGES tp;
    tp.PrivilegeCount = 1;
    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    if (!LookupPrivilegeValue(NULL, SE_MANAGE_VOLUME_NAME, &tp.Privileges[0].Luid))
    {
        CloseHandle(hToken);
        return false;
    }

    bool result = AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL) != FALSE;
    CloseHandle(hToken);
    return result;
}

在调用CreateFile之前调用这个函数,确保权限被正确启用。


3. 尝试访问物理磁盘而非逻辑卷

你当前用的\\\\.\\G:是逻辑卷的路径,Windows对逻辑卷的原始读取限制更多。可以尝试找到G:对应的物理磁盘(比如\\\\.\\PhysicalDriveX),直接访问物理磁盘:

  1. 用GetLogicalDriveStrings枚举所有盘符
  2. 对每个盘符调用GetVolumeNameForVolumeMountPoint获取卷GUID路径
  3. 用CreateFile打开卷GUID路径,再调用DeviceIoControl的IOCTL_STORAGE_GET_DEVICE_NUMBER获取物理磁盘编号

代码示例片段:

char drivePath[MAX_PATH] = "G:\\";
char volumeName[MAX_PATH];
if (GetVolumeNameForVolumeMountPoint(drivePath, volumeName, MAX_PATH))
{
    HANDLE hVolume = CreateFile(volumeName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
    if (hVolume != INVALID_HANDLE_VALUE)
    {
        STORAGE_DEVICE_NUMBER devNum = {0};
        DWORD bytesReturned;
        if (DeviceIoControl(hVolume, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &devNum, sizeof(devNum), &bytesReturned, NULL))
        {
            char physicalDrivePath[MAX_PATH];
            sprintf(physicalDrivePath, "\\\\.\\PhysicalDrive%u", devNum.DeviceNumber);
            HANDLE hPhysicalDrive = CreateFile(physicalDrivePath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH, NULL);
            // 用hPhysicalDrive尝试ReadFile
        }
        CloseHandle(hVolume);
    }
}

4. 检查CreateFile的标志组合

尝试使用FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH的组合,这是原始磁盘读取的标准标志,同时确保不要使用FILE_ATTRIBUTE_NORMAL(原始磁盘不需要这个属性)。修改后的CreateFile调用:

HANDLE memCard = CreateFile("\\\\.\\G:", GENERIC_READ, 
    FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
    OPEN_EXISTING, FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH, NULL);

这些方法应该能覆盖Windows更新带来的权限或行为变化问题,建议你按顺序排查,先确认扇区对齐和权限,再尝试物理磁盘访问。

内容的提问来源于stack exchange,提问作者François

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 10:18:24