Как сделать чтобы запущеный exe сам себя удалил 2

Укрощение строптивого… 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, либо создать две отдельные библиотеки и подгружать их в зависимости от операционной системы, поскольку поддержка двух интерфейсов в одном приложении может отрицательно сказаться на его размере и объеме используемой оперативной памяти.