это быстро и бесплатно
Оформите заказ сейчас и получите скидку 100 руб.!
Ознакомительный фрагмент работы:
Укрощение строптивого… CD-ROM
Алексей Фоминов
Кто не мечтает о быстром CD-ROM? Быстрый CD-ROM – это хорошо… с одной стороны. А если на компакт-диске появилась трещина? Быстрый CD-ROM – это уже нехорошо. На скорости 52х такой компакт-диск читать просто опасно. А если на этом диске жизненно важные данные? Выход есть. Просто снизить скорость привода. Если вы знакомы с языком программирования Object Pascal, тогда читайте далее.
Использование интерфейса SCSI
SCSI (Small Computer System Interface - интерфейс малой компьютерной системы) – шина ввода/вывода, которая разрабатывалась как метод соединения нескольких классов периферийных устройств в главную систему, не требующий внесения модификации в общие аппаратные средства и программное обеспечение.
Поскольку цель данной статьи рассказать читателю о том, как программно управлять устройствами, которые подключаются к SCSI-шине, а не о том, как написать драйвер SCSI-устройства, описывать технические особенности шины SCSI и её отличие от IDE я не буду.
Каким же образом операционная система Windows общается со SCSI-устройствами?
Это зависит от версии операционной системы. В системах семейства Windows 9х (95, 98, 98SE, Me) применяется ASPI (Advanced SCSI Programmer Interface – улучшенный интерфейс программирования SCSI). В стандартную поставку этих операционных систем входят ASPI-драйвер и библиотека для работы с ним, разработанные фирмой Adaptec. В системах семейства Windows NT (NT 4.0, 2000, XP, Server) используется SPTI (SCSI Pass Through Interface – интерфейс передачи через SCSI). То есть, в NT-системах компания Майкрософт полностью отказалась от продукта фирмы Adaptec и создала свой интерфейс общения со SCSI-устройствами. Принесло ли это пользу пользователям? Вряд ли. На мой субъективный взгляд, рядовому пользователю всё равно, как происходит доступ к SCSI, ему важно, чтобы всё работало правильно. Принесло ли это пользу разработчикам программного обеспечения? Однозначно нет. Теперь, разрабатывая приложения для управления SCSI-устройствами, разработчик должен либо создавать две версии своего продукта (одну для Win9x, другую для WinNT), либо включать поддержку двух интерфейсов в свой продукт, что вряд ли является целесообразным с точки зрения размера программы.
Какой из двух интерфейсов лучше, мне сказать трудно. Отмечу лишь то, что программа Nero использует ASPI-драйвер, специально разработанный для неё фирмой Adaptec.
Рассмотрим сначала программирование с помощью интерфейса ASPI, на примере управления приводом CD-ROM/R/RW.
Предполагается, что читатель умеет работать с динамически компонуемыми библиотеками (dll). Как вы будете подключать библиотеку для работы с ASPI-драйвером wnaspi32.dll (статически или динамически) – дело ваше, главное, чтобы ваше приложение правильно импортировало из этой библиотеки необходимые нам функции.
Я подключал эту библиотеку статически и импорт нужных нам функций у меня выглядел так:
function GetASPI32SupportInfo:DWORD; external 'wnaspi32.dll' name 'GetASPI32SupportInfo'; function SendASPI32Command(LPSRB:Pointer):DWORD; external 'wnaspi32.dll' name 'SendASPI32Command'. |
Функция GetASPI32SupportInfo инициализирует ASPI и возвращает информацию об основной конфигурации. При успешном выполнении она возвращает двойное слово (DWORD), в котором старший байт младшего слова содержит статус ASPI, а младший байт – количество устройств (адаптеров), поддерживающих ASPI. Байт статуса может содержать следующие значения:
$01 – выполнено без ошибок;
$E8 – нет адаптеров;
$E2 – не может быть выполнено под управлением Windows 3.1;
$E3 – неправильная установка ASPI, или имеются конфликты ресурсов;
$E7 – установка ASPI нарушена (требуется повторная установка);
$E9 – недостаточно системных ресурсов для инициализации ASPI;
$E4 – общий внутренний сбой ASPI.
Количество возвращенных адаптеров представляет собой количество логических шин, а не физических адаптеров. Для адаптеров с единственной шиной количество адаптеров и количество логических шин идентичны.
Функция SendASPI32Command оперирует со всеми SCSI-запросами ввода/вывода. Каждый SCSI-запрос использует SCSI Request Block (SRB – Блока Запроса SCSI), определяющий операцию ASPI, которую нужно выполнить.
Параметр, передаваемый функции SendASPI32Command – указатель на определённую структуру. Описание этих структур приведено ниже.
type SRB_HAInquiry = packed record SRB_Cmd: Byte; // код команды ASPI (константа SC_HA_INQUIRY = $00) SRB_Status, // байт статуса ASPI команды SRB_HaId, // номер адаптера ASPI SRB_Flags: Byte; // зарезервировано, должно быть 0 SRB_Hdr_Rsvd: Dword; // зарезервировано, должно быть 0 HA_Count: Byte; // количество адаптеров HA_SCSI_ID: Byte; // ID SCSI-адаптера HA_ManagerId: array [0..15] of Byte; // строка, описывающая менеджер HA_Identifier: array [0..15] of Byte; // строка, описывающая адаптер HA_Unique: array [0..15] of Byte; // уникальные параметры адаптера HA_Rsvd1: Word; // зарезервировано, должно быть 0 end; PSRB_HAInquiry = ^SRB_HAInquiry; TSRB_HAInquiry = SRB_HAInquiry; |
Структура TSRB_HAInquiry используется для получения информации о физических SCSI-адаптерах.
type SRB_GDEVBlock = packed record SRB_Cmd, // код команды ASPI (константа SC_GET_DEV_TYPE = $01); SRB_Status, // байт статуса ASPI команды; SRB_HaId, // номер адаптера ASPI; SRB_Flags: Byte; // зарезервировано, должно быть 0; SRB_Hdr_Rsvd: Dword; // зарезервировано, должно быть 0; SRB_Target, // ID объекта SCSI; SRB_Lun, // Logical Unit Number (LUN - логический номер устройства); SRB_DeviceType, // тип периферийного устройства; SRB_Rsvd1: Byte; // зарезервировано, должно быть 0; end; TSRB_GDEVBlock = SRB_GDEVBlock; PSRB_GDEVBlock = ^SRB_GDEVBlock; |
Структура TSRB_GDEVBlock используется для идентификации устройств на шине SCSI.
type SRB_ExecSCSICmd = packed record SRB_Cmd, // код команды ASPI (константа SC_EXEC_SCSI_CMD = $02) SRB_Status, // байт статуса ASPI команды SRB_HaId, // номер адаптера ASPI SRB_Flags: Byte; // флаги запроса ASPI SRB_Hdr_Rsvd: Dword; // зарезервировано, должно быть 0 SRB_Target, // ID объекта SCSI SRB_Lun: Byte; // Logical Unit Number (LUN - логический номер устройства) SRB_Rsvd1: Word; // зарезервировано для выравнивания SRB_BufLen: Dword; // длина буфера SRB_BufPointer: Pointer; // указатель на буфер данных SRB_SenseLen, // длина значения; SRB_CDBLen, // длина Command Descriptor Block – блока дескриптора команды SRB_HaStat, // статус адаптера SRB_TargStat: Byte; // статус объекта SRB_PostProc, // указатель на функцию постинга (см.ниже) SRB_Rsvd2: Pointer; // зарезервировано, должно быть 0; SRB_Rsvd3, // зарезервировано для выравнивания CDBByte: array [0..15] of byte; // SCSI Command Descriptor Block // буфер значения для SCSI-запроса SenseArea: array [0..SENSE_LEN + 1] of byte; end; TSRB_ExecSCSICmd = SRB_ExecSCSICmd; PSRB_ExecSCSICmd = ^SRB_ExecSCSICmd; |
Структура TSRB_ExecSCSICmd используется для выполнения команд ввода/вывода. Константа SENSE_LEN (длина буфера значения) по умолчанию равна 14.
На мой взгляд, теории пока достаточно. Перейду к практике.
Для начала инициализируем ASPI.
function GetASPI: Integer; var dwSupportInfo: DWORD; byASPIStatus,byHACount: Byte; begin Result := 0; dwSupportInfo := GetASPI32SupportInfo; byASPIStatus := HIBYTE(LOWORD(dwSupportInfo)); // статус ASPI byHACount := LOBYTE(LOWORD(dwSupportInfo)); // количество адаптеров case byASPIStatus of SS_COMP: Result := Integer(byHACount); SS_NO_ADAPTERS: ShowMessage('ASPI-контроллеры не обнаружены!'); SS_ILLEGAL_MODE: ShowMessage( 'ASPI не может быть выполнен под управлением Windows 3.1!'); SS_NO_ASPI: ShowMessage( 'Неправильная установка ASPI, или имеются конфликты ресурсов!'); SS_MISMATCHED_COMPONENTS: ShowMessage( 'Установка ASPI нарушена! Установите повторно, пожалуйста!'); SS_INSUFFICIENT_RESOURCES: ShowMessage( 'Недостаточно системных ресурсов для инициализации ASPI!'); SS_FAILED_INIT: ShowMessage('Общий внутренний сбой ASPI!'); end; end; |
Итак, мы получили информацию об имеющихся SCSI-адаптерах. Теперь выделим из их числа (если их несколько) устройства CD-ROM/R/RW. Для этого создадим вспомогательные структуры: TCDROM и TCDROMs.
type TCDROM=record HaID, // номер адаптера ASPI Target, // ID объекта SCSI Lun: Byte; // логический номер устройства DriveLetter: string; // буквенное обозначение диска VendorID, // идентификатор производителя ProductID, // идентификатор продукта Revision, // изменение VendorSpec, // спецификация производителя Description: string; // описание end; |
Тип TCDROM будет хранить необходимые нам данные об устройствах CD-ROM.
type TCDROMs=record CdromCount: Byte; Cdroms: array [Byte] of TCDROM; end; |
Поскольку у некоторых пользователей может быть подключено несколько CD-ROM, мы объявили тип TCDROMs, содержащий в себе информацию о количестве CD-ROM и массив элементов TCDROM. А теперь давайте напишем функцию для определения всех имеющихся в системе устройств CD-ROM, объявив перед этим глобальную переменную Cdroms: TCDROMs.
// в качестве параметра передаётся количество всех SCSI-адаптеров, // имеющихся в системе. Результат работы функции – количество CD-ROM. function GetCDROMs(var Adapters:Byte): Integer; var sh: TSRB_HAInquiry; sd: TSRB_GDEVBlock; maxTgt: Byte; H, T, L: byte; Begin Result := 0; if Adapters = 0 then exit; // если количество адаптеров 0 – выходим // начинаем перебирать все адаптеры for H := 0 to Adapters - 1 do begin FillChar(sh,sizeof(sh),0); // инициализируем структуру TSRB_HAInquiry // (константа SC_HA_INQUIRY = $00) запрос ASPI для получения информации // об адаптерах. sh.SRB_Cmd := SC_HA_INQUIRY; sh.SRB_HaID := H; SendASPI32Command(@sh); // посылаем ASPI команду if sh.SRB_Status=SS_COMP then // если выполнено без ошибок, тогда: begin // четвёртый байт уникальных параметров определяет максимальное // количество объектов SCSI maxTgt := sh.HA_Unique[3]; // если этот байт равен 0, тогда присваиваем переменной максимально // возможное значение (константа MAXTARG=7) if maxTgt=0 then maxTgt := MAXTARG; for T := 0 to maxTgt-1 do // начинаем перебирать все объекты SCSI begin for L := 0 to MAXLUN-1 do // и все логические номера устройств begin // инициализируем структуру TSRB_GDEVBlock FillChar(sd,sizeof(sd),0); // команда запрашивает тип устройства для объекта SCSI (константа // SC_GET_DEV_TYPE = $01) sd.SRB_Cmd := SC_GET_DEV_TYPE; sd.SRB_HaID := H; sd.SRB_Target := T; sd.SRB_Lun := L; SendASPI32Command(@sd); // посылаем ASPI-команду // если выполнено без ошибок, и устройство является CD-ROM, // заполняем переменную Cdroms. if (sd.SRB_Status=SS_COMP) and (sd.SRB_DeviceType=DTYPE_CDROM) then begin Cdroms.Cdroms[Cdroms.CdromCount].HaID := H; Cdroms.Cdroms[Cdroms.CdromCount].Target := T; Cdroms.Cdroms[Cdroms.CdromCount].Lun := L; // получаем информацию об этом CD-ROM CdromInfo(Cdroms.CdromCount); // увеличиваем счётчик количества устройств CD-ROM inc(Cdroms.CdromCount); end; end; end; end; end; Result := Cdroms.CdromCount; // присваиваем результату функции количество CD-ROM end; |
Вы, наверное, обратили внимание на то, что в коде используется процедура CdromInfo. Это процедура, с помощью которой, мы получаем информацию о нашем CD-ROM. Перед тем, как привести её описание, я хочу рассказать вам о том, как происходит управление SCSI-устройствами посредством специальных команд, и как при этом используется структура TSRB_ExecSCSICmd.
Вот поля структуры TSRB_ExecSCSICmd, на которые нужно, прежде всего, обратить внимание: SRB_Cmd, SRB_Flags, SRB_CDBLen, CDBByte. Поле SRB_Cmd всегда должно содержать значение SC_EXEC_SCSI_CMD. Поле SRB_Flags должно определять направление передачи данных. Если данные передаются из SCSI-устройства в приложение, используется шестнадцатиричное значение $08 (определим это значение как константу SRB_DIR_IN). Если происходит обратная передача данных (от приложения к SCSI-устройству), используется шестнадцатиричное значение $10 (определим это значение как константу SRB_DIR_OUT). В зависимости от посылаемой команды, поле SRB_CDBLen может содержать значения: 6, 10 или 12. Массив байт CDBByte подробно описывает параметры выполняемой команды. Значение массива различно для всех команд. Замечу лишь, то, что нулевой байт этого массива всегда определяет код команды. Какие команды я имею в виду? Например: команда установки скорости CD-привода, команда записи CD-R или CD-RW-диска, команды управления аудио-CD (Play, Pause, Stop и так далее).
Существуют SCSI-команды, которые поддерживают все устройства, и есть команды, которые специфичны для определённого типа устройств. Первая команда, которую мы рассмотрим, команда INQUIRY, является обязательной для всех устройств. Она запрашивает информацию о SCSI-устройстве. А теперь собственно перейдём к коду процедуры:
// параметр, передаваемый процедуре – номер CD-ROM. procedure CdromInfo(const Number: Byte); var // буфер будет содержать информацию о приводе buffer: array [1..100] of Char; begin // инициализируем буфер (просто обнуляем его) Fillchar(buffer, sizeof(buffer), 0); // инициализируем структуру TSRB_ExecSCSICmd (глобальная переменная Srb) Fillchar(Srb, sizeof(TSRB_ExecSCSICmd), 0); hEvent := CreateEvent(nil, true, false, nil); // создаём событие ResetEvent(hEvent); // переключаем на наше событие with Srb do begin SRB_Cmd := SC_EXEC_SCSI_CMD; SRB_HaId := Cdroms.Cdroms[Number].HaID; SRB_Target := Cdroms.Cdroms[Number].Target; SRB_Lun := Cdroms.Cdroms[Number].Lun; // здесь добавляется ещё один флаг SRB_EVENT_NOTIFY ($40), уведомляющий // систему о событии SRB_Flags := SRB_DIR_IN or SRB_EVENT_NOTIFY; SRB_BufLen := sizeof(buffer); // указываем размер буфера SRB_BufPointer := @buffer; // определяем указатель на наш буфер SRB_SenseLen := SENSE_LEN; // определяем длину буфера значения SRB_CDBLen := 6; // эта команда – шестибайтная SRB_PostProc := Pointer(hEvent); // процедура постинга – созданное событие CDBByte[0] := $12; // код команды INQUIRY // сюда помещаем старший байт длины буфера CDBByte[3] := HIBYTE(sizeof(buffer)); // а сюда помещаем младший байт длины буфера CDBByte[4] := LOBYTE(sizeof(buffer)); end; // после того как заполнили структуру TSRB_ExecSCSICmd, посылаем // ASPI-команду dwASPIStatus := SendASPI32Command(@Srb); if dwASPIStatus=SS_PENDING then WaitForSingleObject(hEvent, INFINITE); // ждём окончания обработки команды CloseHandle(hEvent); // закрываем хэндл события // если команда выполнена без ошибок, заполняем данные об устройстве: if Srb.SRB_Status=SS_COMP then begin with Cdroms.Cdroms[Number] do begin // восемь байт буфера, начиная с девятого, содержат // идентификатор производителя VendorID := PChar(Copy(buffer, 9, 8)); // шестнадцать байт, начиная с семнадцатого, содержат // идентификатор продукта ProductID := PChar(Copy(buffer, 17, 16)); // четыре байта, начиная с тридцать третьего, содержат номер // изменения продукта Revision := PChar(Copy(buffer, 33, 4)); // двадцать байт, начиная с тридцать седьмого, содержат // спецификацию производителя VendorSpec := PChar(Copy(buffer, 37, 20)); end; end; end; |
Я понимаю, что многим эта процедура покажется неинтересной – я её привёл лишь для того, чтобы показать основы работы со SCSI-устройствами.
Следующие две процедуры, на мой взгляд, заинтересуют большее число пользователей. Уверен, многие из вас постоянно пользуются, или пользовались ранее, программами, управляющими скоростью привода CD-ROM (например, программой CDSlow). Хотите написать подобную программу сами? Позвольте помочь вам кодом, состоящим из двух процедур, одна из которых определяет текущую и максимально поддерживаемую скорость привода, а другая устанавливает необходимую пользователю скорость.
Для этого я воспользовался SCSI-командой MODE SENSE(10). Цифра десять означает, что команда десятибайтная. Это важно, потому что существует такая же шестибайтная команда. В принципе, можно было бы воспользоваться и шестибайтной командой, но поскольку команда MODE SENSE(10) более совершенна, я остановил свой выбор на ней. Итак, для чего же нужна данная команда? Всё просто, она читает значения режимов (Mode Sense), установленных для SCSI-устройства. Существуют так называемые страницы режима (Mode Page), в которых хранится некоторая информация (например, параметры скорости привода, параметры для записи CD-R/RW-дисков и многое другое). Доступ к этим страницам осуществляется по их коду с использованием команды MODE SENSE.
Опишем вспомогательный тип TCDSpeeds.
type TCDSpeeds=record MaxSpeed, // максимальная скорость чтения CurrentSpeed, // текущая скорость чтения MaxWriteSpeed, // максимальная скорость записи CurrentWriteSpeed:integer; // текущая скорость записи end; |
Теперь, я думаю, понятно для чего эта структура нужна.
// какие параметры передавать функции, объяснять, по моему, не надо function GetCDSpeeds(Host,Target,Lun:Byte):TCDSpeeds; var buffer: array [0..29] of Byte; // буфер для принимаемых данных |
Здесь я сделаю небольшое пояснение относительно размера буфера. Данные, возвращаемые при использовании страницы режима CD Capabilities and Mechanical Status Page, имеют размер 20 байт. Но, как вы заметили, я использовал буфер размером 30 байт, и вот почему. Перед самой страницей режима, идут заголовок режима параметров, код страницы и её размер. Размер заголовка при использовании шестибайтной команды MODE SENSE составляет 4 байта, а при использовании команды MODE SENSE(10) – 8 байт.
Продолжим. Код, который уже встречался ранее, приведен без комментариев:
begin hEvent := CreateEvent(nil, true, false, nil); FillChar(buffer,sizeof(buffer),0); FillChar(Srb,sizeof(TSRB_ExecSCSICmd),0); Srb.SRB_Cmd := SC_EXEC_SCSI_CMD; Srb.SRB_Flags := SRB_DIR_IN or SRB_EVENT_NOTIFY; Srb.SRB_Target := Target; Srb.SRB_HaId := Host; Srb.SRB_Lun := Lun; Srb.SRB_BufLen := sizeof(buffer); Srb.SRB_BufPointer := @buffer; Srb.SRB_SenseLen := SENSE_LEN; Srb.SRB_CDBLen := $0A; // это десятибайтная команда Srb.SRB_PostProc := Pointer(hEvent); Srb.CDBByte[0] := $5A; // код команды MODE SENSE(10) // код страницы CD Capabilities and Mechanical Status Page Srb.CDBByte[2] := $2A; Srb.CDBByte[7] := HIBYTE(sizeof(buffer)); Srb.CDBByte[8] := LOBYTE(sizeof(buffer)); ResetEvent(hEvent); dwASPIStatus := SendASPI32Command(@Srb); if dwASPIStatus=SS_PENDING then WaitForSingleObject(hEvent,INFINITE); if Srb.SRB_Status<>SS_COMP then // если ошибка, обнуляем структуру TCDSpeeds FillChar(Result,sizeof(TCDSpeeds),0); else begin // почему сумма байт делится на 176? 176 – это скорость передачи // данных, равная одному килобайту в секунду. Result.MaxSpeed := ((buffer[16] shl 8) + buffer[17]) div 176; Result.CurrentSpeed := ((buffer[22] shl 8) + buffer[23]) div 176; Result.MaxWriteSpeed := ((buffer[26] shl 8) + buffer[27]) div 176; Result.CurrentWriteSpeed := ((buffer[28] shl 8) + buffer[29]) div 176; end; CloseHandle(hEvent); end; |
Итак, скорости мы определили, теперь нужно научиться ими управлять.
Для этого воспользуемся SCSI-командой SetCDSpeed.
// параметры ReadSpeed и WriteSpeed – скорость чтения и записи соответственно function SetSpeed( Host, Target, Lun : Byte; ReadSpeed, WriteSpeed : integer) : boolean; begin if ReadSpeed=0 then result := false else begin hEvent := CreateEvent(nil, true, false, nil); FillChar(Srb,sizeof(TSRB_ExecSCSICmd), 0); Srb.SRB_Cmd := SC_EXEC_SCSI_CMD; // обратите внимание здесь данные передаются из приложения в // устройство (флаг SRB_DIR_OUT) Srb.SRB_Flags := SRB_DIR_OUT or SRB_EVENT_NOTIFY; Srb.SRB_Target := Target; Srb.SRB_HaId := Host; Srb.SRB_Lun := Lun; Srb.SRB_SenseLen := SENSE_LEN; Srb.SRB_CDBLen := $0C; // эта команда двенадцатибайтная Srb.SRB_PostProc := Pointer(hEvent); Srb.CDBByte[0] := $BB; // код команды Set CD Speed // устанавливаем скорость чтения Srb.CDBByte[2] := Byte((ReadSpeed * 176) shr 8); Srb.CDBByte[3] := Byte(ReadSpeed * 176); if WriteSpeed<>0 then // если привод пишущий begin // ...устанавливаем скорость записи Srb.CDBByte[4] := Byte((WriteSpeed * 176) shr 8); Srb.CDBByte[5] := Byte(WriteSpeed * 176); end; ResetEvent(hEvent); dwASPIStatus := SendASPI32Command(@Srb); if dwASPIStatus=SS_PENDING then WaitForSingleObject(hEvent,INFINITE); if Srb.SRB_Status<>SS_COMP then result := false else result := true; end; end; |
Напоследок хочу рассказать о том, как узнать все скорости, которые поддерживает привод. Разместите на форме компоненты TComboBox и TButton. В обработчике события OnClick компонента TButton поместите следующий код:
var i : integer; begin ComboBox1.Items.Clear; // очищаем элементы выпадающего списка with Cdroms.Cdroms[0] do // используем первый CD-ROM begin // открываем цикл от 1 до максимальной скорости привода for i := 1 to GetCDSpeeds(HaID, Target, Lun).MaxSpeed do begin SetSpeed(HaID, Target, Lun, i, 0); // устанавливаем скорость, равную i if i = GetCDSpeeds(HaID, Target, Lun).CurrentSpeed then // сравниваем, если текущая скорость равна i, заносим это // значение в выпадающий список ComboBox1.Items.Add(IntToStr(i)); end; end; end; |
Вот и всё. Следующая часть статьи посвящена работе с SPTI-интерфейсом.
Использование интерфейса SPTI
Итак, в предыдущей статье было рассказано, как управлять приводом CD-ROM, используя интерфейс ASPI.
Однако интерфейс ASPI поддерживается в операционных системах семейства Win9x, которые сейчас используются крайне редко. Здесь я расскажу о том, как осуществлять управление CD-ROM посредством SPTI-интерфейса, который поддерживается в операционных системах WinNT, 2000, XP, 2003 Server. Начну с описания основных структур, которые при этом понадобятся:
type TScsiPassThrough = record Length : Word; // Размер структуры TScsiPassThrough ScsiStatus : Byte; // Статус SCSI-запроса PathId : Byte; // Идентификатор SCSI-адаптера TargetId : Byte; // Идентификатор объекта SCSI Lun : Byte; // Logical Unit Number (LUN - логический номер устройства) // Длина CDB (Command Descriptor Block – блока дескриптора команды) CDBLength : Byte; SenseInfoLength : Byte; // Длина буфера значения DataIn : Byte; // Байт, определяющий тип запроса (ввод или вывод) DataTransferLength : DWORD; // Размер передаваемых данных TimeOutValue : DWORD; // Время ожидания запроса в секундах DataBufferOffset : DWORD; // Смещение буфера данных SenseInfoOffset : DWORD; // Смещение буфера значения // SCSI Command Descriptor Block (Блок дескриптора команды) CDB: array [0..15] of Byte; end; |
Следующая структура:
TScsiPassThroughWithBuffers = record spt : TScsiPassThrough; bSenseBuf : array [0..31] of Byte; // Буфер значения bDataBuf : array [0..191] of Byte; // Буфер данных end; ScsiPassThroughWithBuffers=TScsiPassThroughWithBuffers; PScsiPassThroughWithBuffers=^TScsiPassThroughWithBuffers; |
Как видите, эта структура содержит тип TScsiPassThrough и два буфера. Для удобства мы будем использовать структуру TScsiPassThroughWithBuffers.
Теперь постараюсь объяснить принцип использования интерфейса 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, либо создать две отдельные библиотеки и подгружать их в зависимости от операционной системы, поскольку поддержка двух интерфейсов в одном приложении может отрицательно сказаться на его размере и объеме используемой оперативной памяти.
Сделайте индивидуальный заказ на нашем сервисе. Там эксперты помогают с учебой без посредников Разместите задание – сайт бесплатно отправит его исполнителя, и они предложат цены.
Цены ниже, чем в агентствах и у конкурентов
Вы работаете с экспертами напрямую. Поэтому стоимость работ приятно вас удивит
Бесплатные доработки и консультации
Исполнитель внесет нужные правки в работу по вашему требованию без доплат. Корректировки в максимально короткие сроки
Гарантируем возврат
Если работа вас не устроит – мы вернем 100% суммы заказа
Техподдержка 7 дней в неделю
Наши менеджеры всегда на связи и оперативно решат любую проблему
Строгий отбор экспертов
К работе допускаются только проверенные специалисты с высшим образованием. Проверяем диплом на оценки «хорошо» и «отлично»
Работы выполняют эксперты в своём деле. Они ценят свою репутацию, поэтому результат выполненной работы гарантирован
Ежедневно эксперты готовы работать над 1000 заданиями. Контролируйте процесс написания работы в режиме онлайн
Надо сделать куросовой проект на тему сенсорная оценка качества пищевых продуктов. Тема пищевого продукта любой можно поставить какую удобно для исполниьеля, хоть творог, сыр, яйцо куринное.
Курсовая, Технология производства и переработки продукции животноводства
Срок сдачи к 28 янв.
Здравствуйте нужно будет решить билет в нем будет 4 задачи решение...
Решение задач, Дискретная математика
Срок сдачи к 27 янв.
Вариант 8
Контрольная, Спец курс по решению задач по бухгалтерскому учёту и составлению отчетности в 1с
Срок сдачи к 26 янв.
Расширить реферат до 20 страниц и оформить в соответствие с...
Реферат, история государства и права
Срок сдачи к 26 янв.
Пирамиду пересекает плоскость R?. Показать в ортогональной проекции...
Решение задач, начертательная геометрия и инженерная графика
Срок сдачи к 27 янв.
23 вариант. В каждом документе первые два задания 23 вариатна. Итого 14 задач.
Решение задач, Физика
Срок сдачи к 30 янв.
стандарты качества которые есть на сегодняшний день по бережливому...
Презентация, Бережливое производство
Срок сдачи к 28 янв.
Презентация к речи, по защите диплома
Презентация, Проектирование ракетных двигателей
Срок сдачи к 26 янв.
Найти информацию по трём вопросам и структурировать
Поиск информации, Основы российской государственности
Срок сдачи к 29 янв.
Заполните форму и узнайте цену на индивидуальную работу!