如何在Windows中向CD-ROM驱动器发出读取CD命令?

如何在Windows中向CD-ROM驱动器发出读取CD命令?,c,winapi,windows-xp,ioctl,cd-rom,C,Winapi,Windows Xp,Ioctl,Cd Rom,我正在开发一个需要向CD-ROM驱动器发出原始SCSI命令的应用程序。目前,我正在努力向驱动器发送读取CD(0xBE)命令,并从CD的给定扇区获取数据 考虑以下代码: #include <windows.h> #include <winioctl.h> #include <ntddcdrm.h> #include <ntddscsi.h> #include <stddef.h> int main(void) { HANDLE fh

我正在开发一个需要向CD-ROM驱动器发出原始SCSI命令的应用程序。目前,我正在努力向驱动器发送读取CD(
0xBE
)命令,并从CD的给定扇区获取数据

考虑以下代码:

#include <windows.h>
#include <winioctl.h>
#include <ntddcdrm.h>
#include <ntddscsi.h>
#include <stddef.h>

int main(void)
{
  HANDLE fh;
  DWORD ioctl_bytes;
  BOOL ioctl_rv;
  const UCHAR cdb[] = { 0xBE, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0 };
  UCHAR buf[2352];
  struct sptd_with_sense
  {
    SCSI_PASS_THROUGH_DIRECT s;
    UCHAR sense[128];
  } sptd;

  fh = CreateFile("\\\\.\\E:", GENERIC_READ | GENERIC_WRITE,
    FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL, NULL);

  memset(&sptd, 0, sizeof(sptd));
  sptd.s.Length = sizeof(sptd.s);
  sptd.s.CdbLength = sizeof(cdb);
  sptd.s.DataIn = SCSI_IOCTL_DATA_IN;
  sptd.s.TimeOutValue = 30;
  sptd.s.DataBuffer = buf;
  sptd.s.DataTransferLength = sizeof(buf);
  sptd.s.SenseInfoLength = sizeof(sptd.sense);
  sptd.s.SenseInfoOffset = offsetof(struct sptd_with_sense, sense);
  memcpy(sptd.s.Cdb, cdb, sizeof(cdb));

  ioctl_rv = DeviceIoControl(fh, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd,
    sizeof(sptd), &sptd, sizeof(sptd), &ioctl_bytes, NULL);

  CloseHandle(fh);

  return 0;
}
这表明驱动器已成功执行该命令,因为
ScsiStatus
为0(
SCSI\u STATUS\u GOOD
),并且未返回任何检测数据。但是,不会写入数据的缓冲区,因为在调试模式下编译应用程序时,调试器显示缓冲区中填充了
0xcc

但是,当我将CDB更改为标准查询命令时,如下所示:

const UCHAR cdb[] = { 0x12, 0, 0, 0, 36, 0 };
const unsigned char cdb[] = { 0xBE, 0, 0, 0, 0, 1, 0, 0, 1, 0x10, 0, 0 };
缓冲区中正确填充了查询数据,我能够读取驱动器、供应商和其他所有内容的名称

根据,我已经尝试对齐目标缓冲区,这表示SCSI_PASS_THROUGH_DIRECT的DataBuffer成员是指向此适配器设备对齐缓冲区的指针。实验性地将缓冲区对齐到64字节不起作用,并发出一个
IOCTL\u SCSI\u GET\u CAPABILITIES
,它应该返回所需的对齐方式,给了我以下信息:

Length                      0x00000018  unsigned long
MaximumTransferLength       0x00020000  unsigned long
MaximumPhysicalPages        0x00000020  unsigned long
SupportedAsynchronousEvents 0x00000000  unsigned long
AlignmentMask               0x00000001  unsigned long
TaggedQueuing               0x00        unsigned char
AdapterScansDown            0x00        unsigned char
AdapterUsesPio              0x01        unsigned char
这使我相信对齐不是必需的,因为
AlignmentMask
是1,因此似乎这不是问题的原因。有趣的是,
adapterUserPio
是1,尽管设备管理器不这么说

作为记录,下面的代码在Linux上正常工作,并且目标缓冲区中充满了来自CD的数据。与Windows上相同,返回的SCSI状态为0,不返回任何检测数据

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <scsi/sg.h>
#include <scsi/scsi.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>

int main(void)
{
  int fd = open("/dev/sr0", O_RDONLY | O_NONBLOCK);
  if(fd == -1) { perror("open"); return 1; }

  {
    struct sg_io_hdr sgio;
    unsigned char cdb[] = { 0xBE, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0 };
    unsigned char buf[2352];
    unsigned char sense[128];
    int rv;

    sgio.interface_id = 'S';
    sgio.dxfer_direction = SG_DXFER_FROM_DEV;
    sgio.cmd_len = sizeof(cdb);
    sgio.cmdp = cdb;
    sgio.dxferp = buf;
    sgio.dxfer_len = sizeof(buf);
    sgio.sbp = sense;
    sgio.mx_sb_len = sizeof(sense);
    sgio.timeout = 30000;

    rv = ioctl(fd, SG_IO, &sgio);
    if(rv == -1) { perror("ioctl"); return 1; }
  }
  close(fd);
  return 0;
}
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
内部主(空)
{
int fd=open(“/dev/sr0”,O|RDONLY | O|unblock);
如果(fd=-1){perror(“open”);返回1;}
{
结构sg_io_hdr sgio;
无符号字符cdb[]={0xBE,0,0,0,0,1,0,0,1,0,0,0};
无符号字符buf[2352];
无符号字符检测[128];
int-rv;
sgio.interface_id='S';
sgio.dxfer_direction=SG_dxfer_FROM_DEV;
sgio.cmd_len=sizeof(cdb);
sgio.cmdp=cdb;
sgio.dxferp=buf;
sgio.dxfer_len=sizeof(buf);
sgio.sbp=感觉;
sgio.mx_sb_len=sizeof(感测);
sgio.timeout=30000;
rv=ioctl(fd、SG_IO和sgio);
如果(rv==-1){perror(“ioctl”);返回1;}
}
关闭(fd);
返回0;
}

< Windows >代码是用Visual Studio C++ 2010 Express和WindDK 7600 .1638 5.1编译的,在WindowsXP上编译。它也在Windows XP上运行。

问题在于格式不正确的CDB,尽管在语法方面有效。我在MMC规范中没有看到以下内容:

第9个字节应该包含用于选择驱动器应该返回的数据类型的位。在问题中的代码中,我将其设置为0,这意味着我从驱动器请求了“无字段”。将此字节更改为
0x10
(用户数据)将导致Linux和Windows版本返回给定扇区的相同数据。我仍然不知道为什么Linux返回缓冲区中的一些数据,即使是原始形式的CDB

因此,在LBA 1读取一个CD-DA扇区时,读取CD命令的正确CDB应如下所示:

const UCHAR cdb[] = { 0x12, 0, 0, 0, 36, 0 };
const unsigned char cdb[] = { 0xBE, 0, 0, 0, 0, 1, 0, 0, 1, 0x10, 0, 0 };

顺便说一句,由于读取安全限制,您的代码在Windows 7中总是会失败。您可以使用DeviceIOControl API发送大多数SCSI命令,但是当涉及到数据或原始读取时,您必须使用指定的SPTI方法来读取扇区,否则Windows 7将阻止它(无论是否具有管理员权限),因此,仅供参考,如果您想要更高的兼容性,您不能再使用SCSI方法

以下是SPTI规定的方式,谢天谢地,与使用OxBE或READ10构建SCSI命令包相比,它的代码要少得多(如果您只需要数据扇区的数据,则应该使用OxBE或READ10,因为它是SCSI-1命令,而不是兼容程度较低的0xBE命令):


简而言之,谷歌搜索IOCTL_CDROM_RAW_READ命令。上面的代码片段适用于音频扇区,返回2352字节。如果您的CreateFile()调用正确,这可以一直工作到Windows NT4.0。但是,如果您使用IOCTL\u SCSI\u PASS\u THROUGH\u DIRECT并尝试构建自己的0xBE SCSI命令包,Windows 7将阻止它!Microsoft希望您使用IOCTL_CDROM_RAW_READ进行原始读取。您可以建立其他SCSI命令包来读取TOC,获得驱动器功能,但读取命令将被阻止,DeviceIoControl将引发“无效功能”错误。显然,至少在Windows 10上,我的软件再次工作,限制也被取消了,但由于Windows 7的用户安装基数很大,您无论如何都会希望以SPTI规定的方式进行安装,再加上IOCTL_CDROM_RAW_READ知道一些比通用0xBE更不常见的读取命令,用于旧的古怪驱动器,所以无论如何还是用它好

我不知道,但您可能想了解项目中的libscg驱动程序是如何做到这一点的。开发cdrecord的人也喜欢回答与SCSI相关的问题。我想你弄错了一些术语。SPTI=SCSI直通。您建议的解决方案是只使用本机ioctl,它在某些情况下可以工作,但在其他情况下有缺点。根据我的经验,访问光盘驱动器的SPTI不需要管理员权限。