Теперь постараюсь объяснить принцип использования интерфейса SPTI.
Сначала, с помощью функции CreateFile, создаём хэндл для доступа к устройству. Затем заполняем данными структуру TScsiPassThroughWithBuffers. И, наконец, с помощью функции DeviceIoControl, посылаем устройству управляющий код.
Выглядит это примерно так:
procedure GetSPTIDrives; // Процедура получает информацию о CD-ROM var j : integer; s : string; len, returned : DWORD; sptwb : TScsiPassThroughWithBuffers; Cdroms : TCdroms; // Структура Tcdroms описана в предыдущей статье const SCSI_IOCTL_DATA_IN = 1; IOCTL_SCSI_PASS_THROUGH = ($00000004 shl 16) or (($0001 or $0002) shl 14) or ($0401 shl 2) or (0); begin // Кроме строки '\.\E : ', можно использовать, 'cdrom0', 'cdrom1' и т.д. // в зависимости от количества устройств hDevice := CreateFile('\.\E : ', GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0); if hDevice=INVALID_HANDLE_VALUE then ShowMessage('INVALID_HANDLE_VALUE'); sptwb.Spt.Length := sizeof(TSCSIPASSTHROUGH); sptwb.Spt.CdbLength := 6; // Шестибайтная команда sptwb.Spt.SenseInfoLength := 24; // Команда будет получать данные от устройства (ввод) sptwb.Spt.DataIn := SCSI_IOCTL_DATA_IN; // Устанавливаем размер передаваемых данных sptwb.Spt.DataTransferLength := sizeof(sptwb.bDataBuf); sptwb.Spt.TimeOutValue := 10; // Время ожидания – 10 секунд sptwb.Spt.DataBufferOffset := DWORD(@sptwb.bDataBuf)-DWORD(@sptwb); sptwb.Spt.SenseInfoOffset := DWORD(@sptwb.bSenseBuf)-DWORD(@sptwb); len := sptwb.Spt.DataBufferOffset+sptwb.spt.DataTransferLength; // Команда INQUIRY вам уже известна по предыдущей статье sptwb.Spt.CDB[0] := SCSI_INQUIRY; sptwb.Spt.CDB[3] := HiByte(sizeof(sptwb.bDataBuf)); sptwb.Spt.CDB[4] := LoByte(sizeof(sptwb.bDataBuf)); if DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH, @sptwb, len, @sptwb, len, Returned, nil) and (sptwb.Spt.ScsiStatus = $00) then begin // Нижеследующие циклы предназначены для разделения информации о // производителе, спецификации и т.д. Если вашей программе это не нужно, // можно сделать так : ShowMessage(PChar(@sptwb.bDataBuf[8])); s := ''; for j := 8 to 15 do s := s + Chr(sptwb.bDataBuf[j]); // Идентификатор производителя Cdroms.Cdroms[Cdroms.ActiveCdrom].VendorID := s; s := ''; for j := 16 to 31 do s := s + Chr(sptwb.bDataBuf[j]); Cdroms.Cdroms[Cdroms.ActiveCdrom].ProductID := s; // Идентификатор продукта s := ''; for j := 32 to 35 do s := s+chr(sptwb.bDataBuf[j]); Cdroms.Cdroms[Cdroms.ActiveCdrom].Revision := s; // Номер изменения s := ''; for j := 36 to 55 do s := s+chr(sptwb.bDataBuf[j]); // Спецификация производителя Cdroms.Cdroms[Cdroms.ActiveCdrom].VendorSpec := s; end; end; |
Если вы заметили, использование параметров PathId, TargetId и Lun для интерфейса SPTI не является обязательным (в отличие от ASPI). Поэтому, если вы всё же хотите, чтобы ваша программа определяла идентификатор SCSI-адаптера, идентификатор объекта SCSI и логический номер устройства, могу посоветовать воспользоваться таким кодом:
procedure Get_PathId_TargetId_Lun; var buf : array [0..1023] of Byte; pscsiAddr:PSCSI_ADDRESS; const IOCTL_SCSI_GET_ADDRESS = $41018; begin ZeroMemory(@buf, sizeof(buf)); pscsiAddr := PSCSI_ADDRESS(@buf); pscsiAddr^.Length := sizeof(TSCSI_ADDRESS); if (DeviceIoControl(hDevice, IOCTL_SCSI_GET_ADDRESS, nil, 0, pscsiAddr, sizeof(TSCSI_ADDRESS), returned, nil)) then begin Cdroms.Cdroms[Cdroms.ActiveCdrom].HaID := pscsiAddr^.PortNumber; Cdroms.Cdroms[Cdroms.ActiveCdrom].Target := pscsiAddr^.TargetId; Cdroms.Cdroms[Cdroms.ActiveCdrom].Lun := pscsiAddr^.Lun; end else ShowMessage(SysErrorMessage(GetLastError)); end; |
В этом куске кода используется структура PSCSI_ADDRESS, которая выглядит следующим образом:
type TSCSI_ADDRESS = record Length : LongInt; // Размер структуры TSCSI_ADDRESS PortNumber : Byte; // Номер адаптера SCSI PathId : Byte; // Идентификатор адаптера SCSI TargetId : Byte; // Идентификатор объекта SCSI Lun : Byte; // Логический номер устройства end; SCSI_ADDRESS = TSCSI_ADDRESS; PSCSI_ADDRESS = ^TSCSI_ADDRESS; |
Как вы уже успели заметить, SCSI-команды для интерфейсов ASPI и SPTI одинаковы, поэтому необходимо знать лишь сами команды и заполнять соответствующим образом CDB (Command Descriptor Block). Для наглядности приведу пример использования интерфейса SPTI для установки скорости CD-ROM. Сравните этот код с таким же, но использующим интерфейс ASPI, и вы сами увидите все отличия.
function SPTISetSpeed(ReadSpeed, WriteSpeed:integer):Boolean; var spti:TScsiPassThroughWithBuffers; const SCSI_IOCTL_DATA_OUT = 0; Rate = 176; begin spti.Spt.Length := sizeof(TSCSIPASSTHROUGH); spti.Spt.CdbLength := 10; spti.Spt.SenseInfoLength := 24; spti.Spt.DataIn := SCSI_IOCTL_DATA_OUT; spti.Spt.TimeOutValue := 10; spti.spt.DataBufferOffset := DWORD(@spti.bDataBuf)-DWORD(@spti); spti.spt.SenseInfoOffset := DWORD(@spti.bSenseBuf)-DWORD(@spti); spti.Spt.DataTransferLength := sizeof(spti.bDataBuf); spti.spt.CDB[0] := $BB; spti.spt.CDB[2] := BYTE(ReadSpeed*Rate shr 8); spti.spt.CDB[3] := BYTE(ReadSpeed*Rate); if WriteSpeed<>0 then begin spti.spt.CDB[4] := BYTE(WriteSpeed*Rate shr 8); spti.spt.CDB[5] := BYTE(WriteSpeed*Rate); end else spti.spt.CDB[4] := $FF; spti.spt.CDB[5] := $FF; if DeviceIoControl(hDevice, IOCTL_SCSI_PASS_THROUGH, @spti, len, @spti, len, returned, nil) and (spti.spt.ScsiStatus=$00) then result := true else result := false; end; |
Думаю, данный код не нуждается в пояснениях.
Кстати, всё вышесказанное (в том числе и в предыдущей статье) относится не только к устройствам CD-ROM, но и к другим SCSI-устройствам. Отличия лишь в командах. Есть команды, которые обязательны для всех устройств (MODE SELECT, MODE SENSE, INQUIRY и т.д.), и есть команды, которые специфичны для разных типов устройств (BLANK – для устройств CD-RW, PRINT – для принтеров, SCAN – для сканеров, и т.д.).
Теперь вы знаете, как осуществляется управление устройствами, подключёнными к шине SCSI. Какой использовать интерфейс, ASPI или SPTI, или оба вместе – дело ваше. Могу сказать лишь, что для использования двух интерфейсов рациональнее будет либо создать два приложения для двух семейств операционных систем Windows, либо создать две отдельные библиотеки и подгружать их в зависимости от операционной системы, поскольку поддержка двух интерфейсов в одном приложении может отрицательно сказаться на его размере и объеме используемой оперативной памяти.