Приборкання норовливого ... CD-ROM h2>
Олексій Фомін p>
Хто не мріє про швидке CD-ROM?
Швидкий CD-ROM - це добре ... з одного боку. А якщо на компакт-диску з'явилася
тріщина? Швидкий CD-ROM - це вже недобре. На швидкості 52х такий компакт-диск
читати просто небезпечно. А якщо на цьому диску життєво важливі дані? Вихід є.
Просто знизити швидкість приводу. Якщо ви знайомі з мовою програмування
Object Pascal, тоді читайте далі. P>
Використання інтерфейсу SCSI h2>
SCSI (Small Computer System Interface - інтерфейс
малої комп'ютерної системи) - шина введення/виводу, яка розроблялася як
метод з'єднання декількох класів периферійних пристроїв у головну систему,
що не вимагає внесення модифікації в загальні апаратні засоби та програмне
забезпечення. p>
Оскільки мета даної статті розповісти читачу про
те, як програмно керувати пристроями, які підключаються до SCSI-шині, а
не про те, як написати драйвер SCSI-пристрої, описувати технічні особливості
шини SCSI і її відмінність від IDE я не буду. p>
Яким же чином операційна система Windows спілкується
з SCSI-пристроями? p>
Це залежить від версії операційної системи. У системах
сімейства 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), або включати
підтримку двох інтерфейсів в свій продукт, що навряд чи є доцільним
з точки зору розміру програми. p>
Який з двох інтерфейсів краще, мені сказати важко.
Відзначу лише те, що програма Nero використовує ASPI-драйвер, спеціально
розроблений для неї фірмою Adaptec. p>
Розглянемо спочатку програмування з допомогою
інтерфейсу ASPI, на прикладі управління приводом CD-ROM/R/RW. p>
Передбачається, що читач вміє працювати з
динамічно компонуемимі бібліотеками (dll). Як ви будете підключати
бібліотеку для роботи з ASPI-драйвером wnaspi32.dll (статично або
динамічно) - свою справу, головне, щоб ваш додаток правильно
імпортувала з цієї бібліотеки необхідні нам функції. p>
Я підключав цю бібліотеку статично та імпорт потрібних
нам функцій у мене виглядав так: p>
function
GetASPI32SupportInfo: DWORD; external 'wnaspi32.dll' p>
name 'GetASPI32SupportInfo'; p>
function
SendASPI32Command (LPSRB: Pointer): DWORD; external 'wnaspi32.dll' p>
name
'SendASPI32Command'. P>
Функція GetASPI32SupportInfo ініціалізує ASPI і
повертає інформацію про основну конфігурації. При успішному виконанні вона
повертає подвійне слово (DWORD), в якому старший байт молодшого слова
містить статус ASPI, а молодший байт - кількість пристроїв (адаптерів),
підтримують ASPI. Байт статусу може містити наступні значення: p>
$ 01 - виконано без помилок; p>
$ E8 - немає адаптерів; p>
$ E2 - не може бути виконано під управлінням Windows
3.1; p>
$ E3 - неправильна установка ASPI, або є
конфлікти ресурсів; p>
$ E7 - установка ASPI порушена (потрібна повторна
установка); p>
$ E9 - недостатньо системних ресурсів для
ініціалізації ASPI; p>
$ E4 - загальний внутрішній збій ASPI. p>
Кількість повернутих адаптерів являє собою
кількість логічних шин, а не фізичних адаптерів. Для адаптерів з
єдиною шиною кількість адаптерів і кількість логічних шин ідентичні. p>
Функція SendASPI32Command оперує з усіма SCSI-запитами
введення/виводу. Кожен SCSI-запит використовує SCSI Request Block (SRB - Блоку
Запиту SCSI), який визначає операцію ASPI, яку потрібно виконати. P>
Параметр, що передається функції SendASPI32Command --
покажчик на певну структуру. Опис цих структур наведено нижче. P>
type p>
SRB_HAInquiry = packed record p>
SRB_Cmd: Byte;// код команди ASPI
(константа SC_HA_INQUIRY = $ 00) p>
SRB_Status,// байт статусу ASPI команди p>
SRB_HaId,// номер адаптера ASPI p>
SRB_Flags: Byte;// зарезервовано, повинно
бути 0 p>
SRB_Hdr_Rsvd: Dword;// зарезервовано,
має бути 0 p>
HA_Count: Byte;// кількість адаптерів p>
HA_SCSI_ID: Byte;// ID SCSI-адаптера p>
HA_ManagerId: array [0 .. 15] of Byte;//
рядок, що описує менеджер p>
HA_Identifier: array [0 .. 15] of Byte;//
рядок, що описує адаптер p>
HA_Unique: array [0 .. 15] of Byte;//
унікальні параметри адаптера p>
HA_Rsvd1: Word;// зарезервовано, повинно
бути 0 p>
end; p>
PSRB_HAInquiry =
^ SRB_HAInquiry; p>
TSRB_HAInquiry = SRB_HAInquiry; p>
Структура TSRB_HAInquiry використовується для отримання
інформації про фізичні SCSI-адаптери. p>
type p>
SRB_GDEVBlock = packed record p>
SRB_Cmd,// код команди ASPI (константа SC_GET_DEV_TYPE = $ 01); p>
SRB_Status,// байт статусу ASPI команди; p>
SRB_HaId,// номер адаптера ASPI; p>
SRB_Flags: Byte;// зарезервовано, має бути 0; p>
SRB_Hdr_Rsvd: Dword;// зарезервовано,
має бути 0; p>
SRB_Target,// ID об'єкта SCSI; p>
SRB_Lun,// Logical Unit Number
(LUN - логічний номер пристрою); p>
SRB_DeviceType,// тип периферійного пристрою; p>
SRB_Rsvd1: Byte;// зарезервовано, має бути 0; p>
end; p>
TSRB_GDEVBlock = SRB_GDEVBlock; p>
PSRB_GDEVBlock = ^ SRB_GDEVBlock; p>
Структура TSRB_GDEVBlock використовується для
ідентифікації пристроїв на шині SCSI. p>
type p>
SRB_ExecSCSICmd = packed record p>
SRB_Cmd,// код команди ASPI (константа
SC_EXEC_SCSI_CMD = $ 02) p>
SRB_Status,// байт статусу ASPI команди p>
SRB_HaId,// номер адаптера ASPI p>
SRB_Flags: Byte;// прапори запиту ASPI p>
SRB_Hdr_Rsvd: Dword;// зарезервовано,
має бути 0 p>
SRB_Target,// ID об'єкта SCSI p>
SRB_Lun: Byte;// Logical Unit Number (LUN --
логічний номер пристрою) p>
SRB_Rsvd1: Word;// зарезервовано для
вирівнювання p>
SRB_BufLen: Dword;// довжина буфера p>
SRB_BufPointer: Pointer;// покажчик на
буфер даних p>
SRB_SenseLen,// довжина значення; p>
SRB_CDBLen,// довжина Command Descriptor
Block - блоку дескриптора команди p>
SRB_HaStat,// статус адаптера p>
SRB_TargStat: Byte;// статус об'єкта p>
SRB_PostProc,// покажчик на функцію
постинга (см.ниже) p>
SRB_Rsvd2: Pointer;// зарезервовано, повинно
бути 0; p>
SRB_Rsvd3,// зарезервовано для вирівнювання p>
CDBByte: array [0 .. 15] of byte;
//SCSI Command Descriptor Block p>
// буфер значення для SCSI-запиту p>
SenseArea: array [0 .. SENSE_LEN
+ 1] of byte; p>
end; p>
TSRB_ExecSCSICmd =
SRB_ExecSCSICmd; p>
PSRB_ExecSCSICmd = ^ SRB_ExecSCSICmd; p>
Структура TSRB_ExecSCSICmd використовується для виконання
команд введення/виводу. Константа SENSE_LEN (довжина буфера значення) за замовчуванням
дорівнює 14. p>
На мій погляд, теорії поки достатньо. Перейду до
практиці. p>
Для початку ініціалізіруем ASPI. p>
function GetASPI: Integer; p>
var p>
dwSupportInfo: DWORD; p>
byASPIStatus, byHACount: Byte; p>
begin p>
Result: = 0; p>
dwSupportInfo: =
GetASPI32SupportInfo; p>
byASPIStatus: =
HIBYTE (LOWORD (dwSupportInfo));// статус ASPI p>
byHACount: = LOBYTE (LOWORD (dwSupportInfo));
//Кількість адаптерів p>
p>
case byASPIStatus of p>
SS_COMP: Result: =
Integer (byHACount); p>
SS_NO_ADAPTERS:
ShowMessage ( 'ASPI-контролери не виявлені !'); p>
SS_ILLEGAL_MODE: ShowMessage ( p>
'ASPI не може бути виконаний під управлінням Windows 3.1 !'); p>
SS_NO_ASPI: ShowMessage ( p>
'Неправильна установка ASPI, або є
конфлікти ресурсів !'); p>
SS_MISMATCHED_COMPONENTS: ShowMessage ( p>
'Установка ASPI порушена! Встановіть повторно, будь ласка !'); p>
SS_INSUFFICIENT_RESOURCES:
ShowMessage ( p>
'Недостатньо системних ресурсів для ініціалізації ASPI !'); p>
SS_FAILED_INIT: ShowMessage ( 'Загальний внутрішній збій ASPI !'); p>
end; p>
end; p>
Отже, ми отримали інформацію про наявні
SCSI-адаптери. Тепер виділимо з їх числа (якщо їх декілька) пристрою
CD-ROM/R/RW. Для цього створимо допоміжні структури: TCDROM і TCDROMs. P>
type p>
TCDROM = record p>
HaID,// номер адаптера ASPI p>
Target,// ID об'єкта SCSI p>
Lun: Byte;// логічний номер пристрою p>
DriveLetter: string;// буквене
позначення диска p>
VendorID,// ідентифікатор виробника p>
ProductID,// ідентифікатор продукту p>
Revision,// зміна p>
VendorSpec,// специфікація виробника p>
Description: string;// опис p>
end; p>
Тип TCDROM буде зберігати необхідні нам дані про
пристроях CD-ROM. p>
type p>
TCDROMs = record p>
CdromCount: Byte; p>
Cdroms: array [Byte] of TCDROM; p>
end; p>
Оскільки у деяких користувачів може бути
підключено кілька CD-ROM, ми оголосили тип TCDROMs, що містить в собі
інформацію про кількість CD-ROM та масив елементів TCDROM. А тепер давайте напишемо
функцію для визначення всіх наявних в системі пристроїв CD-ROM, оголосивши
перед цим глобальну змінну Cdroms: TCDROMs. p>
// як параметр
передається кількість всіх SCSI-адаптерів, p>
// що є в системі.
Результат роботи функції - кількість CD-ROM. P>
function GetCDROMs (var Adapters: Byte): Integer; p>
var p>
sh: TSRB_HAInquiry; p>
sd: TSRB_GDEVBlock; p>
maxTgt: Byte; p>
H, T, L: byte; p>
Begin p>
Result: = 0; p>
if Adapters = 0 then p>
exit;// якщо кількість адаптерів 0 --
виходимо p>
// починаємо перебирати всі адаптери p>
for H: = 0 to Adapters - 1 do p>
begin p>
FillChar (sh, sizeof (sh), 0);// ініціалізіруем структуру TSRB_HAInquiry p>
// (константа SC_HA_INQUIRY = $ 00) запит ASPI для отримання
інформації p>
// про адаптери. p>
sh.SRB_Cmd: = SC_HA_INQUIRY; p>
sh.SRB_HaID: = H; p>
SendASPI32Command (@ sh);// посилаємо ASPI
команду p>
if sh.SRB_Status = SS_COMP then// якщо
виконано без помилок, тоді: p>
begin p>
// четвертий байт унікальних параметрів
визначає максимальну p>
// кількість об'єктів SCSI p>
maxTgt: = sh.HA_Unique [3]; p>
// якщо цей байт дорівнює 0, тоді присвоюємо
змінної максимально p>
// можливе значення (константа MAXTARG = 7) p>
if maxTgt = 0 then maxTgt: = MAXTARG; p>
for T: = 0 to maxTgt-1 do// починаємо
перебирати всі об'єкти SCSI p>
begin p>
for L: = 0 to MAXLUN-1 do// і всі логічні
номери пристроїв p>
begin p>
// ініціалізіруем структуру TSRB_GDEVBlock p>
FillChar (sd, sizeof (sd), 0); p>
p>
// команда запитує тип пристрою для
об'єкта SCSI (константа p>
// SC_GET_DEV_TYPE = $ 01) p>
sd.SRB_Cmd: = SC_GET_DEV_TYPE; p>
sd.SRB_HaID: = H; p>
sd.SRB_Target: = T; p>
sd.SRB_Lun: = L; p>
SendASPI32Command (@ sd);// посилаємо ASPI-команду p>
p>
// якщо виконано без помилок, і є пристрій CD-ROM, p>
// заповнюємо змінну Cdroms. p>
if (sd.SRB_Status = SS_COMP) and
(sd.SRB_DeviceType = DTYPE_CDROM) then p>
begin p>
Cdroms.Cdroms [Cdroms.CdromCount]. HaID: = H; p>
Cdroms.Cdroms [Cdroms.CdromCount]. Target: =
T; p>
Cdroms.Cdroms [Cdroms.CdromCount]. Lun: = L; p>
// отримуємо інформацію про це CD-ROM p>
CdromInfo (Cdroms.CdromCount); p>
// збільшуємо лічильник кількості пристроїв
CD-ROM p>
inc (Cdroms.CdromCount); p>
end; p>
end; p>
end; p>
end; p>
end; p>
p>
Result: = Cdroms.CdromCount;//
присвоюємо результату функції кількість CD-ROM p>
end; p>
Ви, напевно, звернули увагу на те, що в коді
використовується процедура CdromInfo. Це процедура, за допомогою якої, ми отримуємо
інформацію про наш CD-ROM. Перед тим, як привести її опис, я хочу
розповісти вам про те, як відбувається управління SCSI-пристроями за допомогою
спеціальних команд, і як при цьому використовується структура TSRB_ExecSCSICmd. p>
Ось поля структури 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 і так далі). p>
Існують SCSI-команди, які підтримують всі
пристрою, і є команди, які специфічні для певного типу
пристроїв. Перша команда, яку ми розглянемо, команда INQUIRY, є
обов'язковою для всіх пристроїв. Вона запитує інформацію про SCSI-пристрої.
А тепер перейдемо власне до коду процедури: p>
// параметр, який передається
процедурою - номер CD-ROM. p>
procedure CdromInfo (const Number: Byte); p>
var p>
// буфер буде містити інформацію про
приводі p>
buffer: array [1 .. 100] of Char; p>
begin p>
// ініціалізіруем буфер (просто Обнуляємо
його) p>
Fillchar (buffer, sizeof (buffer), 0); p>
// ініціалізіруем структуру TSRB_ExecSCSICmd
(глобальна змінна Srb) p>
Fillchar (Srb, sizeof (TSRB_ExecSCSICmd), 0); p>
hEvent: = CreateEvent (nil,
true, false, nil);// створюємо подія p>
ResetEvent (hEvent);// перемикаємо на наше подія p>
with Srb do p>
begin p>
SRB_Cmd: = SC_EXEC_SCSI_CMD; p>
SRB_HaId: =
Cdroms.Cdroms [Number]. HaID; p>
SRB_Target: =
Cdroms.Cdroms [Number]. Target; p>
SRB_Lun: =
Cdroms.Cdroms [Number]. Lun; p>
// тут додається ще один прапор SRB_EVENT_NOTIFY ($ 40),
повідомляючі p>
// систему про подію p>
SRB_Flags: = SRB_DIR_IN or
SRB_EVENT_NOTIFY; p>
SRB_BufLen: = sizeof (buffer);
//Вказуємо розмір буфера p>
SRB_BufPointer: = @ buffer;// визначаємо покажчик на наш буфер p>
SRB_SenseLen: = SENSE_LEN;// визначаємо
довжину буфера значення p>
SRB_CDBLen: = 6;// ця команда --
шестібайтная p>
SRB_PostProc: = Pointer (hEvent);//
процедура постинга - створене подія p>
CDBByte [0]: = $ 12;// код команди INQUIRY p>
// сюди поміщаємо старший байт довжини буфера p>
CDBByte [3]: = HIBYTE (sizeof (buffer)); p>
// а сюди поміщаємо молодший байт довжини буфера p>
CDBByte [4]: = LOBYTE (sizeof (buffer)); p>
end; p>
p>
// після того, як заповнили структуру TSRB_ExecSCSICmd, посилаємо p>
// ASPI-команду p>
dwASPIStatus: =
SendASPI32Command (@ Srb); p>
if dwASPIStatus = SS_PENDING then
p>
WaitForSingleObject (hEvent,
INFINITE);// чекаємо закінчення обробки команди p>
p>
CloseHandle (hEvent);// закриваємо Хендлі події p>
p>
// якщо команда виконана без помилок,
заповнюємо дані про пристрій: p>
if Srb.SRB_Status = SS_COMP then p>
begin p>
with Cdroms.Cdroms [Number] do p>
begin p>
// вісім байт буфера, починаючи з дев'ятого,
містять p>
// ідентифікатор виробника p>
VendorID: = PChar (Copy (buffer, 9, 8)); p>
// шістнадцять байт, починаючи з сімнадцятого,
містять p>
// ідентифікатор продукту p>
ProductID: = PChar (Copy (buffer, 17, 16 )); p>
// чотири байти, починаючи з тридцять
третє, містять номер p>
// зміни продукту p>
Revision: = PChar (Copy (buffer, 33, 4)); p>
// двадцять байт, починаючи з тридцять
сьомого, містять p>
// специфікацію виробника p>
VendorSpec: = PChar (Copy (buffer, 37, 20)); p>
end; p>
end; p>
end; p>
Я розумію, що багатьом ця процедура здасться
нецікавою - я її привів лише для того, щоб показати основи роботи зі
SCSI-пристроями. P>
Наступні дві процедури, на мій погляд, зацікавлять
більше число користувачів. Упевнений, багато хто з вас постійно користуються, або
користувалися раніше, програмами, які керують швидкістю приводу CD-ROM
(наприклад, програмою CDSlow). Хочете написати подібну програму самі?
Дозвольте допомогти вам кодом, що складається з двох процедур, один з яких
визначає поточну і максимально підтримувану швидкість приводу, а інша
встановлює необхідну користувачеві швидкість. p>
Для цього я скористався SCSI-командою MODE
SENSE (10). Цифра десять означає, що команда десятібайтная. Це важливо, тому
що існує така ж шестібайтная команда. У принципі, можна було б
скористатися і шестібайтной командою, але оскільки команда MODE SENSE (10)
більш досконала, я зупинив свій вибір на ній. Отже, для чого ж потрібна дана
команда? Все просто, вона читає значення режимів (Mode Sense), встановлених
для SCSI-пристрої. Існують так звані сторінки режиму (Mode Page), в
яких зберігається деяка інформація (наприклад, параметри швидкості приводу,
параметри для запису CD-R/RW-дісков і багато чого іншого). Доступ до цих сторінок
здійснюється за їх коду з використанням команди MODE SENSE. p>
Опишемо допоміжний тип TCDSpeeds. p>
type p>
TCDSpeeds = record p>
MaxSpeed,// максимальна швидкість читання p>
CurrentSpeed,// поточна швидкість читання p>
MaxWriteSpeed,// максимальна швидкість
запису p>
CurrentWriteSpeed: integer;// поточна
швидкість запису p>
end; p>
Тепер, я думаю, зрозуміло для чого ця структура потрібна. p>
// які параметри передавати
функції, пояснювати, по моєму, не треба p>
function GetCDSpeeds (Host, Target, Lun: Byte): TCDSpeeds; p>
var p>
buffer: array [0 .. 29] of Byte;// буфер для
прийнятих даних p>
Тут я зроблю невелике пояснення щодо
розміру буфера. Дані, що повертаються при використанні стра?? учениці режиму CD
Capabilities and Mechanical Status Page, мають розмір 20 байт. Але, як ви
помітили, я використав буфер розміром 30 байт, і ось чому. Перед самою
сторінкою режиму, йдуть заголовок режиму параметрів, код сторінки і її розмір.
Розмір заголовка при використанні шестібайтной команди MODE SENSE становить 4
байти, а при використанні команди MODE SENSE (10) - 8 байт. p>
Продовжимо. Код, який вже зустрічався раніше, наведено
без коментарів: p>
begin p>
hEvent: = CreateEvent (nil,
true, false, nil); p>
FillChar (buffer, sizeof (buffer), 0); p>
FillChar (Srb, sizeof (TSRB_ExecSCSICmd), 0); p>
Srb.SRB_Cmd: =
SC_EXEC_SCSI_CMD; p>
Srb.SRB_Flags: = SRB_DIR_IN or
SRB_EVENT_NOTIFY; p>
Srb.SRB_Target: = Target; p>
Srb.SRB_HaId: = Host; p>
Srb.SRB_Lun: = Lun; p>
Srb.SRB_BufLen: =
sizeof (buffer); p>
Srb.SRB_BufPointer: = @ buffer; p>
Srb.SRB_SenseLen: = SENSE_LEN; p>
Srb.SRB_CDBLen: = $ 0A;// це десятібайтная команда p>
Srb.SRB_PostProc: = Pointer (hEvent); p>
Srb.CDBByte [0]: = $ 5A;// код команди MODE SENSE (10) p>
// код сторінки CD Capabilities
and Mechanical Status Page p>
Srb.CDBByte [2]: = $ 2A; p>
Srb.CDBByte [7]: =
HIBYTE (sizeof (buffer )); p>
Srb.CDBByte [8]: =
LOBYTE (sizeof (buffer )); p>
ResetEvent (hEvent); p>
dwASPIStatus: =
SendASPI32Command (@ Srb); p>
if dwASPIStatus = SS_PENDING then p>
WaitForSingleObject (hEvent, INFINITE); p>
p>
if
Srb.SRB_Status <> SS_COMP then p>
// якщо помилка, Обнуляємо структуру TCDSpeeds p>
FillChar (Result, sizeof (TCDSpeeds), 0); p>
else begin p>
// чому сума байт ділиться на 176? 176 --
це швидкість передачі p>
// даних, що дорівнює одному кілобайт в
секунду. p>
Result.MaxSpeed: = ((buffer [16] shl 8) +
buffer [17]) div 176; p>
Result.CurrentSpeed: =
((buffer [22] shl 8) + buffer [23]) div 176; p>
Result.MaxWriteSpeed: =
((buffer [26] shl 8) + buffer [27]) div 176; p>
Result.CurrentWriteSpeed: =
((buffer [28] shl 8) + buffer [29]) div 176; p>
end; p>
p>
CloseHandle (hEvent); p>
end; p>
Отже, швидкості ми визначили, тепер потрібно навчитися
ними управляти. p>
Для цього скористаємося SCSI-командою SetCDSpeed. p>
// параметри ReadSpeed і
WriteSpeed - швидкість читання і запису відповідно p>
function SetSpeed ( p>
Host, Target, Lun: Byte; p>
ReadSpeed, WriteSpeed:
integer): boolean; p>
begin p>
if ReadSpeed = 0 then p>
result: = false p>
else p>
begin p>
hEvent: = CreateEvent (nil,
true, false, nil); p>
FillChar (Srb, sizeof (TSRB_ExecSCSICmd), 0); p>
Srb.SRB_Cmd: =
SC_EXEC_SCSI_CMD; p>
// зверніть увагу тут дані передаються з програми в p>
// пристрій (прапор SRB_DIR_OUT) p>
Srb.SRB_Flags: = SRB_DIR_OUT or
SRB_EVENT_NOTIFY; p>
Srb.SRB_Target: = Target; p>
Srb.SRB_HaId: = Host; p>
Srb.SRB_Lun: = Lun; p>
Srb.SRB_SenseLen: = SENSE_LEN; p>
Srb.SRB_CDBLen: = $ 0C;// ця команда двенадцатібайтная p>
Srb.SRB_PostProc: = Pointer (hEvent); p>
Srb.CDBByte [0]: = $ BB;// код команди Set CD Speed p>
// встановлюємо швидкість читання p>
Srb.CDBByte [2]: = Byte ((ReadSpeed * 176) shr
8); p>
Srb.CDBByte [3]: = Byte (ReadSpeed * 176); p>
p>
if WriteSpeed <> 0 then// якщо привід пише p>
begin p>
// ... встановлюємо швидкість запису p>
Srb.CDBByte [4]: =
Byte ((WriteSpeed * 176) shr 8); p>
Srb.CDBByte [5]: = Byte (WriteSpeed
* 176); p>
end; p>
p>
ResetEvent (hEvent); p>
dwASPIStatus: =
SendASPI32Command (@ Srb); p>
p>
if dwASPIStatus = SS_PENDING then p>
WaitForSingleObject (hEvent, INFINITE); p>
p>
if
Srb.SRB_Status <> SS_COMP then p>
result: = false p>
else p>
result: = true; p>
end; p>
end; p>
Наостанок хочу розповісти про те, як дізнатися про все
швидкості, які підтримує привід. Розмістіть на формі компоненти TComboBox
і TButton. У обробнику події OnClick компоненту TButton помістіть наступний
код: p>
var p>
i: integer; p>
begin p>
ComboBox1.Items.Clear;// очищаємо елементи
випадаючого списку p>
p>
with Cdroms.Cdroms [0] do// використовуємо
перший CD-ROM p>
begin p>
// відкриваємо цикл від 1 до максимальної
швидкості приводу p>
for i: = 1 to GetCDSpeeds (HaID, Target,
Lun). MaxSpeed do p>
begin p>
SetSpeed (HaID, Target, Lun, i,
0);// встановлюємо швидкість, рівну i p>
p>
if i = GetCDSpeeds (HaID,
Target, Lun). CurrentSpeed then p>
// порівнюємо, якщо поточна швидкість дорівнює i, заносимо це p>
// значення в список, що випадає p>
ComboBox1.Items.Add (IntToStr (i)); p>
end; p>
end; p>
end; p>
Ось і все. Наступна частина статті присвячена роботі з
SPTI-інтерфейсом. P>
Використання інтерфейсу SPTI
p>
Отже, у попередній статті було розказано, як
управляти приводом CD-ROM, використовуючи інтерфейс ASPI. p>
Проте інтерфейс ASPI підтримується в операційних
системах сімейства Win9x, які зараз використовуються вкрай рідко. Тут я
розповім про те, як здійснювати управління CD-ROM за допомогою SPTI-інтерфейсу,
який підтримується в операційних системах WinNT, 2000, XP, 2003 Server.
Почну з опису основних структур, які при цьому знадобляться: p>
type p>
TScsiPassThrough = record p>
Length: Word;// Розмір структури TScsiPassThrough p>
ScsiStatus: Byte;// Статус SCSI-запиту p>
PathId: Byte;// Ідентифікатор SCSI-адаптера p>
TargetId: Byte;// Ідентифікатор об'єкта SCSI p>
Lun: Byte;// Logical Unit
Number (LUN - логічний номер пристрою) p>
// Довжина CDB (Command Descriptor Block - блоку дескриптора команди) p>
CDBLength: Byte; p>
SenseInfoLength: Byte;// Довжина буфера значення p>
DataIn: Byte;// Байт, що визначає тип запиту (введення або виведення) p>
DataTransferLength: DWORD;// Розмір
переданих даних p>
TimeOutValue: DWORD;// Час очікування
запиту в секундах p>
DataBufferOffset: DWORD;// Зміщення буфера
даних p>
SenseInfoOffset: DWORD;// Зміщення буфера
значення p>
// SCSI Command Descriptor Block (Блок дескриптора команди) p>
CDB: array [0 .. 15] of Byte; p>
end; p>
Наступна структура: p>
TScsiPassThroughWithBuffers = record p>
spt: TScsiPassThrough; p>
bSenseBuf: array [0 .. 31] of
Byte;// Буфер значення p>
bDataBuf: array [0 .. 191] of
Byte;// Буфер даних p>
end; p>
p>
ScsiPassThroughWithBuffers = TScsiPassThroughWithBuffers; p>
PScsiPassThroughWithBuffers = ^ TScsiPassThroughWithBuffers; p>
Як бачите, ця структура містить тип
TScsiPassThrough і два буфера. Для зручності ми будемо використовувати структуру
TScsiPassThroughWithBuffers. P>
Тепер спробую пояснити принцип використання
інтерфейсу SPTI. p>
Спочатку, за допомогою функції CreateFile, створюємо Хендлі
для доступу до пристрою. Потім заповнюємо даними структуру
TScsiPassThroughWithBuffers. І, нарешті, за допомогою функції DeviceIoControl,
посилаємо пристрою керуючий код. p>
Виглядає це приблизно так: p>
procedure GetSPTIDrives;//
Процедура отримує інформацію про CD-ROM p>
var p>
j: integer; p>
s: string; p>
len, returned: DWORD; p>
sptwb:
TScsiPassThroughWithBuffers; p>
Cdroms: TCdroms;// Структура Tcdroms описана в попередній статті p>
const p>
SCSI_IOCTL_DATA_IN = 1; p>
IOCTL_SCSI_PASS_THROUGH =
($ 00000004 shl 16) p>
or (($ 0001 or $ 0002) shl 14) or
($ 0401 shl 2) or (0); p>
begin p>
// Крім рядка '. E:', можна
використовувати, 'cdrom0', 'cdrom1' і т.д. p>
// в залежності від кількості пристроїв p>
hDevice: = CreateFile ( '. E:
', GENERIC_READ or GENERIC_WRITE, p>
FILE_SHARE_READ or
FILE_SHARE_WRITE, p>
nil, OPEN_EXISTING, 0, 0); p>
p>
if hDevice = INVALID_HANDLE_VALUE
then p>
ShowMessage ( 'INVALID_HANDLE_VALUE'); p>
p>
sptwb.Spt.Length: =
sizeof (TSCSIPASSTHROUGH); p>
sptwb.Spt.CdbLength: = 6;// Шестібайтная команда p>
sptwb.Spt.SenseInfoLength: =
24; p>
// Команда буде отримувати дані від пристрою (введення) p>
sptwb.Spt.DataIn: = SCSI_IOCTL_DATA_IN; p>
// Встановлюємо розмір переданих даних p>
sptwb.Spt.DataTransferLength: =
sizeof (sptwb.bDataBuf); p>
sptwb.Spt.TimeOutValue: = 10;
//Час очікування - 10 секунд p>
sptwb.Spt.DataBufferOffset: =
DWORD (@ sptwb.bDataBuf)-DWORD (@ sptwb); p>
sptwb.Spt.SenseInfoOffset: =
DWORD (@ sptwb.bSenseBuf)-DWORD (@ sptwb); p>
len: =
sptwb.Spt.DataBufferOffset + sptwb.spt.DataTransferLength; p>
// Команда INQUIRY вам вже відома за попередній статті p>
sptwb.Spt.CDB [0]: = SCSI_INQUIRY; p>
sptwb.Spt.CDB [3]: =
HiByte (sizeof (sptwb.bDataBuf )); p>
sptwb.Spt.CDB [4]: =
LoByte (sizeof (sptwb.bDataBuf )); p>
if DeviceIoControl (hDevice,
IOCTL_SCSI_PASS_THROUGH, @ sptwb, p>
len, @ sptwb, len, Returned,
nil) and (sptwb.Spt.ScsiStatus = $ 00) then p>
begin p>
// Нижченаведені цикли призначені для
розділення інформації про p>
// виробника, специфікації і т.д. Якщо
вашій програмі це не потрібно, p>
// можна зробити так: ShowMessage (PChar (@ sptwb.bDataBuf [8 ])); p>
s: =''; p>
p>
for j: = 8 to 15 do p>
s: = s +
Chr (sptwb.bDataBuf [j ]); p>
// Ідентифікатор виробника p>
Cdroms.Cdroms [Cdroms.ActiveCdrom]. VendorID
: = S; p>
s: =''; p>
p>
for j: = 16 to 31 do p>
s: = s +
Chr (sptwb.bDataBuf [j ]); p>
p>
Cdroms.Cdroms [Cdroms.ActiveCdrom]. ProductID
: = S;// Ідентифікатор продукту p>
s: =''; p>
for j: = 32 to 35 do p>
s: = s + chr (sptwb.bDataBuf [j ]); p>
p>
Cdroms.Cdroms [Cdroms.ActiveCdrom]. Revision
: = S;// Номер зміни p>
s: =''; p>
p>
for j: = 36 to 55 do p>
s: = s + chr (sptwb.bDataBuf [j ]); p>
p>
// Специфікація виробника p>
Cdroms.Cdroms [Cdroms.ActiveCdrom]. VendorSpec
: = S; p>
end; p>
end; p>
Якщо ви помітили, використання параметрів PathId,
TargetId і Lun для інтерфейсу SPTI не є обов'язковим (на відміну від ASPI).
Тому, якщо ви все ж хочете, щоб ваша програма визначала ідентифікатор
SCSI-адаптера, ідентифікатор об'єкта SCSI і логічний номер пристрою, можу
порадити скористатися таким кодом: p>
procedure Get_PathId_TargetId_Lun; p>
var p>
buf: array [0 .. 1023] of Byte; p>
pscsiAddr: PSCSI_ADDRESS; p>
const p>
IOCTL_SCSI_GET_ADDRESS =
$ 41018; p>
begin p>
ZeroMemory (@ buf, sizeof (buf )); p>
pscsiAddr: =
PSCSI_ADDRESS (@ buf); p>
pscsiAddr ^. Length: =
sizeof (TSCSI_ADDRESS); p>
p>
if (DeviceIoControl (hDevice,
IOCTL_SCSI_GET_ADDRESS, nil, 0, p>
pscsiAddr,
sizeof (TSCSI_ADDRESS), returned, nil)) then p>
begin p>
Cdroms.Cdroms [Cdroms.ActiveCdrom]. HaID: =
pscsiAddr ^. PortNumber; p>
Cdroms.Cdroms [Cdroms.ActiveCdrom]. Target: =
pscsiAddr ^. TargetId; p>
Cdroms.Cdroms [Cdroms.ActiveCdrom]. Lun: =
pscsiAddr ^. Lun; p>
end else p>
ShowMessage (SysErrorMessage (GetLastError )); p>
end; p>
У цьому шматку коду використовується структура
PSCSI_ADDRESS, яка виглядає таким чином: p>
type p>
TSCSI_ADDRESS = record p>
Length: LongInt;// Розмір структури TSCSI_ADDRESS p>
PortNumber: Byte;// Номер адаптера SCSI p>
PathId: Byte;// Ідентифікатор адаптера SCSI p>
TargetId: Byte;// Ідентифікатор об'єкта SCSI p>
Lun: Byte;// Логічний номер пристрою p>
end; p>
SCSI_ADDRESS = TSCSI_ADDRESS; p>
PSCSI_ADDRESS =
^ TSCSI_ADDRESS; p>
Як ви вже встигли помітити, SCSI-команди для
інтерфейсів ASPI і SPTI однакові, тому необхідно знати лише самі команди і
заповнювати відповідним чином CDB (Command Descriptor Block). Для
наочності наведу приклад використання інтерфейсу SPTI для встановлення швидкості
CD-ROM. Порівняйте цей код з таким же, але використовують інтерфейс ASPI, і ви самі
побачите всі відмінності. p>
function SPTISetSpeed (ReadSpeed, WriteSpeed: integer): Boolean; p>
var p>
spti: TScsiPassThroughWithBuffers; p>
const p>
SCSI_IOCTL_DATA_OUT = 0; p>
Rate = 176; p>
begin p>
spti.Spt.Length: =
sizeof (TSCSIPASSTHROUGH); p>
spti.Spt.CdbLength: = 10; p>
spti.Spt.SenseInfoLength: = 24; p>
spti.Spt.DataIn: =
SCSI_IOCTL_DATA_OUT; p>
spti.Spt.TimeOutValue: = 10; p>
spti.spt.DataBufferOffset: =
DWORD (@ spti.bDataBuf)-DWORD (@ spti); p>
spti.spt.SenseInfoOffset: =
DWORD (@ spti.bSenseBuf)-DWORD (@ spti); p>
spti.Spt.DataTransferLength: =
sizeof (spti.bDataBuf); p>
spti.spt.CDB [0]: = $ BB; p>
spti.spt.CDB [2]: =
BYTE (ReadSpeed * Rate shr 8); p>
spti.spt.CDB [3]: =
BYTE (ReadSpeed * Rate); p>
p>
if WriteSpeed <> 0 then p>
begin p>
spti.spt.CDB [4]: =
BYTE (WriteSpeed * Rate shr 8); p>
spti.spt.CDB [5]: =
BYTE (WriteSpeed * Rate); p>
end else p>
spti.spt.CDB [4]: = $ FF; p>
spti.spt.CDB [5]: = $ FF; p>
if DeviceIoControl (hDevice,
IOCTL_SCSI_PASS_THROUGH, @ spti, len, @ spti, len, returned, nil) and p>
(spti.spt.ScsiStatus = $ 00) then
result: = true p>
else p>
result: = false; p>
end; p>
Думаю, даний код не має потреби в поясненнях. p>
До речі, все вищесказане (у тому числі і в попередній
статті) відноситься не тільки до пристроїв CD-ROM, але і до інших
SCSI-пристроїв. Відмінності лише в командах. Є команди, які обов'язкові
для всіх пристроїв (MODE SELECT, MODE SENSE, INQUIRY і т.д.), і є команди,
які специфічні для різних типів пристроїв (BLANK - для пристроїв CD-RW,
PRINT - для принтерів, SCAN - для сканерів, і т.д.). P>
Тепер ви знаєте, як здійснюється управління
пристроями, підключеними до шини SCSI. Який використовувати інтерфейс, ASPI або
SPTI, або обидва разом - справа ваша. Можу сказати лише, що для використання двох
інтерфейсів раціональніше буде або створити дві програми для двох родин
операційних систем Windows, або створити два окремі бібліотеки і довантажувати
їх в залежності від операційної системи, оскільки підтримка двох інтерфейсів
в одному додатку може негативно позначитися на його розмірі і обсязі
використовуваної оперативної пам'яті. p>
Список літератури h2>
Для підготовки даної роботи були використані
матеріали з сайту http://www.rsdn.ru/
p>