Реферат: Как сделать чтобы запущеный exe сам себя удалил?
Реферат: Как сделать чтобы запущеный exe сам себя удалил?
Укрощение строптивого… 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, или имеются
конфликты ресурсов!');
Итак, мы получили информацию об имеющихся
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)
// после того как заполнили структуру 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; // Смещение буфера
значения
Как видите, эта структура содержит тип
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;
// Команда будет получать данные от устройства (ввод)
Если вы заметили, использование параметров 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
В этом куске кода используется структура
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;
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, либо создать две отдельные библиотеки и подгружать
их в зависимости от операционной системы, поскольку поддержка двух интерфейсов
в одном приложении может отрицательно сказаться на его размере и объеме
используемой оперативной памяти.