Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Программирование для начинающих и не только


Информационный Канал Subscribe.Ru

©Gigabyte 2004

CD-ROM под властью Delphi

По материалам www.gigabyte.iatp.org.ua

Один знакомый программер когда речь заходит о том кто чем занимается в Delphi вечно сводит наш разговор к одному: <<Как мне програмно открыть и закрыть дверьку в CDROM-ме...>>

*

...и при этом он добавляет, что работа через посыл MCI команд не работает на системах где установлено более одного CD-ROMа т.к. открывает только тот из них, который идет первым, а остальные не видит. Не разбираясь детально в том почему это так, мы займемся решением этой проблемы обходным путем.

С чего начать

Итак, нам необходимо научится програмно закрывать и открывать каретку CD-ROMa и при этом конечно мы не будем довольствоваться одним, доступ к которому нам предоставляет технология MCI, но также имет возможность выбора утсройства которое бы выскакивало по команде. Для этого нам с самого начала необходимо понять, как сама система работает со всеми устройствами этого типа.

Во первых скажу, что CD-ROM система Windows (как впрочем и Unix) воспринимает как SCSI устройство. И если в Unix доступ програмистов к таким устройствам организован очень просто и грамотно, то для работы с ними в Windows-е придется изрядно попотеть.

SCSI

Управлением SCSI устройствами в системе Windows занимается библиотека wnaspi32.dll разработанная Adaptec и имеющаяся практически на всех системах (по крайней мере на тех, с которыми я работаю). Но одной библиотеки нам будет мало, нам потребуются еще соответствующие ей хедеры, которые можно найти к примеру по этому (http://www.zianet.com/jgray/dat/files/ASPISDK.exe) адресу, так в архиве размером в 1,5Мб вместе с этими файлами также поставляется подробная справка по работе с SCSI устройствами в PDF формате, а также несколько примеров и дополнительных програм для отладки приложений обращающихся к любым утройствами с этим интерфейсом. Но и этот архив кроме как нескольких скудных примеров нам недает, а файлы-хедеры нам Delphi'йцам ничем не могут помочь. Как раз для такого случая я подготовил Delphi- адаптации этих файлов.

Заглянув в соответствующий архив вы увидите 2 файла:
  • Wnaspi32.pas - здесь содержатся описания свех необходимых вам структур а так же объявления используемых функций.
  • Scsidefs.pas - здесь объявлены константы возращаемых функциями значений, а также команды для работы с некоторыми распространенными типами устройств с SCSI интерфейсом в том числе CD-ROM, сканеры, принтеры, комуникационные утстройства и т.п.

CD-ROM

Собственно CD-ROM'ам здесь отведено лишь небольшая часть, которой явно недостаточно для решения поставленой нами задачи.

Порывшысь около часа в инете я обнаружил информацию о том, что все современные CD- ROMы поддерживают так званных MMC (Multi Media Commands) набор команд. Как раз эти команды мы и будем использовать для разработки нашего приложения.

Практика

Для начала нам как всегда необходимо запустить среду разработки Delphi и, щелкнув на кнопке New, создать прототип нашей программы. Далее мы как всегда для демонстрации разместим на форме кнопку, по нажатии которой и будут происходить запланироватнные действия.

Перед тем как посылать какие либо команды CD-ROM-у нам необходимо определить сколько таких устройств имеется в системе (не забывайте для чего мы пишем эту програмку). Для этого мы сперва просканируем шину на предмет набичия на ней нескольких устройств и выведем их на екран.

Для этого мы воспользуемся функцией SendASPI32Command, прототип которой определен в модуле wnaspi32.pas и выглядит следующим образом:
function SendASPI32Command(_para1:LPSRB):DWORD;cdecl;external wnaspi32 name 'SendASPI32Command';
Здесь _para1 - указатель на одну из структур определенных в этом же модуле. Выбор конкретной структуры зависит от команды, которую мы посылаем.Результат - соответственно содержит состояние команды: удачно, неудачно, не совсем удачно и т.п. И хотя это и не обязательно, но мы все же сделаем в нашей программе предварительную проверку на наличие соответствующего драйвера при помощи функции:
function GetASPI32SupportInfo:DWORD;cdecl;external wnaspi32 name 'GetASPI32SupportInfo';
результат этой функции в <нижнем> (Low) байте нижнего слова содержит количество устройств к которым можно осуществить доступ при помощи этого интерфейса (это значение не рекомендуется использовать для организации перечисления истройств потому что оно не всегда содержит правильное значение). <Верхний> (Hi) байт верхнего слова содержит состояние команды, которое может принимать одно из следующих значений:

  • SS_COMP - все в порядке и можно приступать к дальнейшей работе
  • SS_NO_ASPI - нет ASPI.
  • SS_OLD_MANAGER - старая версия менеджера.
  • SS_ILLEGAL_MODE - неправильный режим работы.
После того как эта функция возвратила SS_COMP (другое значение мне как-то не удавалось получить), мы можем смело переходить к сканированию шины.

Сканирование шины.

Для организации этого процесса нам понадобится сперва определить переменную info типа SRB_HAInquiry, которую мы будем использовать в качестве параметра для функции SendASPI32Command, а также переменную info1 типа SRB_GDEVBlock для получения информации о каждом подключенном устройстве.

Итак для получения количества доступных нам устройств мы пишем следующий код:


ZeroMemory(@Info,SizeOf(Info));
info.SRB_Cmd:=SC_HA_INQUIRY;
info.SRB_HaId:=0;
SendASPI32Command(@Info);
if Info.SRB_Status<>SS_COMP then begin ListBox1.Items.Add('Error');Exit; end;
Count:=Info.HA_Count;
Здесь мы сперва обнуляем структуру info, после чего не обращая внимания на другие поля заполняем SRB_Cmd значением SC_HA_INQUIRY и передаем ее команде SendASPI32Command которая возвращает SS_COMP при нормальном завершении работы, после которого мы считываем значение количества доступных устройств в переменную Count. Если же результат не оправдывает наши ожидания, то просто выходим из процедуры обработки и выводим сообщение об ошбке.

Если же все таки Count больше <0>, то следующим нашим делом будет организация цыкла перечисления устройств:


For i:=0 to Count-1 do
 begin
  ZeroMemory(@Info,SizeOf(Info));
  Info.SRB_Cmd:=SC_HA_INQUIRY;
  Info.SRB_HaId:=i;
  SendASPI32Command(@Info);
  if Info.SRB_Status<>SS_COMP then begin Listbox1.Items.Add('Error 1'); exit; 
end;
  ListBox1.Items.Add(Info.HA_Identifier);
  For j:=0 to 7 do
   for k:=0 to 7 do
    begin
     ZeroMemory(@Info1,SizeOf(Info1));
     Info1.SRB_Cmd:=SC_GET_DEV_TYPE;
     Info1.SRB_HaId:=i;
     Info1.SRB_Target:=j;
     Info1.SRB_Lun:=k;
     SendASPI32Command(@Info1);
     if Info1.SRB_Status<>SS_COMP then continue;
     if Info1.SRB_DeviceType=DTYPE_CDROM then
      ListBox1.Items.Add(Format('HaId: %d. Target: %d. Lun: %d',[i,j,k]));
    end;
 end;
в этом фрагменте кода мы как всегда сперва очищаем область памяти отведенную под переменную info, а далее мы заполняем поля SRB_Cmd значением SC_HA_INQUIRY кторое указывает на то, что мы хотели бы получить справку по устройству, индекс которого задается поле SRC_HaId. Далее после SendASPI32Command, если все прошло удачно, мы организовываем еще 2 цыкла под двум значениям, которые однозначно определяют наше устройство это Target - интекс логической единицы (Logical Unit) на которой находится наше устройство и Lun (Logical Unit Number) - адрес логической единицы. Далее как всегда идет вызов SendASPI32Command неудачное завершение которого не должно ввести вас в тупик, просто на данной системе нет устройства с заданными номерами Target и LUN. В этом нет ничего страшного и мы просто должны продолжыть перечисление. Если же работа функции была удачной, то тогда мы проверяем тип устройства и если оно - CD-ROM то тогда это наше устройство. На этом этапе мы должны запомнить значения всех идентифицырующих его элементов, HaID, Target, LUN для того чтоб потом при отправке команды на открытие мы могли отправит ее правильному устройству.

Открытие.

Теперь, просканировав всю шыну и заполучив в свое распоряжение информацию о наличии в системе необходимых устройств. Мы можем приступать к посылу команд нашим устройствам.

Посыл команд осуществляется при помощи уже другой структуры SRB_ExecSCSICommand которая так же передается в качестве параметров SendASPI32Command. Эта структура, как написато в документации к ASPI, заполняется в соответствии со стандартами того устройства, которому посылается команда. И эдинственные команды, которые можно посылать ко всем устройствам определены в модуле scsidefs.pas в секции . Здесь же как нельзя к стати вам пригодится полистать документацию по MMC которая выставлена на диске либо по адресу (%%%%), так же было бы не плохо полистать список и аргументы команд для конкретной модели CD-ROMа т.к. даже в документации по MMС некоторые команды объявлены как Optional и не описаны. Так что к ним можно достучатся только через документацию к конкретному устройству.

Так почитав документацию по MMC я обнаружил, что самым стандартным из всех стандартных способов "открывания рта" у CD-ROMa есть посыл ему команты START/STOP UNIT. Но к сожалению самого описания этой команды в стандарте MMC я не нашел и поэтому обратился за помошью к pdf-кам разработчиков CD-ROMов. Так я нашел в документации от Pioneer описание этой команды, которую сейчас и постараюсь объяснить. Следует так же заметить, что эта команда работает не только с устройствами этой фирмы, потому как у мой TEAC CD-W552E тоже нормально реагировал на эту команду.

Итак, для того чтоб заставить CD-ROM <высутуть язык> мы должны послать ему команду START/STOP UNIT с кодом $1B (1Bh). И в качестве значения параметра CDBByte (одного из полей структуры SRB_ExecSCSICommand) передать следующие значения:


Exec.CDBByte[0]:=$1B;
Exec.CDBByte[1]:=0;
Exec.CDBByte[4]:=3;
Здесь в байт <0> записывается значение команды $1B; в байт <1> - помещается значение 0 если мы хотим узнать результат выполенения команды после ее фактического выполнения. Значение 1 в байте <1> говорит о том что мы хотим знать результат сразу же, а не после явного выполнения команды.

Значение байта <4> можно определить из следующей таблицы:
Бит 1Бит 0ОписаниеЗначение байта
00Останавливает движок CD-ROMa0
01Запускает движок CD-ROMa1
10CD-ROM <<показывает язык>>2
11CD-ROM закрывается и запускает движок3
Все другие байты и биты этой структуры зарезервированы и не используются.

Ниже приведен исходный код для выполнения открытия CD-ROMa:


procedure TForm1.SendEjectCommand(ha,target,lun:byte);
var Buf:Array[0..Size_InquiryBuffer-1] of Char;
    Exec:SRB_ExecSCSICmd;
    SenseKey:Byte;ASC,ASCQ:Byte;
begin
ZeroMemory(@Buf,Size_InquiryBuffer);
ZeroMemory(@Exec,SizeOf(Exec));
Exec.SRB_Cmd:=SC_EXEC_SCSI_CMD;
Exec.SRB_HaId:=ha;
Exec.SRB_Target:=target;
Exec.SRB_Lun:=lun;
Exec.SRB_Flags:=SRB_DIR_IN or SRB_EVENT_NOTIFY;
Exec.SRB_PostProc:=Pointer(MyEvent);
Exec.SRB_BufLen:=Size_InquiryBuffer;
Exec.SRB_BufPointer:=Addr(Buf);
Exec.CDBByte[0]:=$1B;
Exec.CDBByte[1]:=0;
Exec.CDBByte[4]:=2;
Exec.SRB_CDBLen:=11;
Exec.SRB_SenseLen:=SENSE_LEN;
ResetEvent(MyEvent);
SendASPI32Command(@Exec);
if Exec.SRB_Status=SS_PENDING then WaitForSingleObject(MyEvent,INFINITE);
if Exec.SRB_Status<>SS_COMP then
 begin
  Listbox1.Items.Add(format('Error %d HaStat:%d 
TargetStat:%d',[Exec.SRB_Status,Exec.SRB_HaStat,Exec.SRB_TargStat]));
  SenseKey:=Byte(Exec.SenseArea[2]);
  SenseKey:=SenseKey and 15;
  ASC:=Byte(Exec.SenseArea[12]);
  ASCQ:=Byte(Exec.SenseArea[13]);
  ListBox1.Items.Add(Format('SenseKey:%x  ASC:%x  
ASCQ:%x',[SenseKey,ASC,ASCQ]));
 end
 else
  begin
   Listbox1.Items.Add('Ok');
  end;
end;
Несколько уточнений. Перед тем как вы начнете разбираться с вышеприведенным кодом, вам следует учесть несколько мелочей, которые здесь используются. Так как все команды посылаемые этим устройствам - асинхронные, то вам понадобится каким-то образом организовать механизм ожидания завершения команды и в таких случае Microsoft рекомендует воспользоваться специальными функциями.Вообще это довольно большая тема, которой хватит как минимум еще на одну статью но здесь я это описывать не буду, а приведу только кратко перечень действий которые вам необходимо сделать.

Так здесь используется работа с событиями (не путать с событиями компонентов типа OnClick). Эти события - простые идентификаторы, которые могут принимать значения <Сработало/Несработало> и применяются восновном в паре с функцией WaitForSingleObject, которая осущестляет задержку до наступления заданного события. Для использования кода написанного выше вам следует в секцию private вашей формы включить определение события MyEvent: MyEvent:THandle;. После этого в при создании формы вы должны создать это событие путем написания следующего кода:


procedure TForm1.DoCreateEvent;
begin
MyEvent:=CreateEvent(nil,True,False,nil);
end;
При выходе из приложения вы должны уничнодить это события дабы оно не засоряло память:

procedure TForm1.FormDestroy(Sender: TObject);
begin
CloseHandle(MyEvent);
end;
Следующее уточнение касается обработки ошибок. Так например если устройство не содержит диск, то у вас не получится раскрутить его мотор и команда просто вылетит с ошибкой, которая полностью идентифицируется триплетом: SenseKey, ASC, ASCQ. Как его достать из параметра SenseArea структуры SRB_ExecSCSICommand вы можете увидеть в следующем фрагменте кода:

  SenseKey:=Byte(Exec.SenseArea[2]);
  SenseKey:=SenseKey and 15;
  ASC:=Byte(Exec.SenseArea[12]);
  ASCQ:=Byte(Exec.SenseArea[13]);

А подробнее о возможных ошибках вы можете почитать в приложении A в документации по MMC.

©Gigabyte 2004

http://subscribe.ru/
http://subscribe.ru/feedback/
Подписан адрес:
Код этой рассылки: comp.soft.prog.programmershelp
Отписаться

В избранное