Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Укрощение строптивого… 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, либо создать две отдельные библиотеки и подгружать их в зависимости от операционной системы, поскольку поддержка двух интерфейсов в одном приложении может отрицательно сказаться на его размере и объеме используемой оперативной памяти.
Список литературы
Для подготовки данной работы были использованы материалы с сайта http://www.rsdn.ru/