Версия для печати

Запись CD-DVD дисков в Delphi

Оцените материал
(6 голосов)

Запись CD-DVD дисков в Delphi

Доброго времени суток уважаемые любители Delphi. В этой статье я расскажу про запись CD\DVD дисков в среде Delphi. Общие принципы, изложенные в этой статье подойдут не только для языка Delphi, но и для языка С++. Для прочтения этой статьи с максимальной пользой, читателю рекомендуется получить базовые понятия об OLE\COM, впрочем даже незнание этих понятий вряд ли помешает понимаю этой статьи, так как классы и компоненты Delphi (так же как и классы С++), которые мы будет использовать полностью скрывают от нас все тонкости и неудобства использования COM интерфейсов для записи дисков.

Технология, которая будет описываться в этой статье это технология IMAPI v2.0. Одной статьёй эту технологию описать не представляется возможным, поэтому в этой статье будут описаны только основы работы с IMAPI2. Эта технология довольно-таки новая, и поддерживается операционными системами Windows XP SP2, 2003 Server, Vista и т.д. Т.е. перед тем как использовать технологию IMAPI2 следует убедиться, в том, что операционная система на компьютере, на котором будет работать программа, является как минимум одной из вышеперечисленных или более новая (например, Windows 7). «Олицетворением» IMAPI2 являются две библиотеки imapi2.dll и imapi2fs.dll. Перед тем как использовать эту технологию вы или программа, которая будет работать на компьютере, должны убедиться, что эти две DLL существуют. Если их нет, то следует установить обновление KB932716 с узла Microsoft.com (ссылки на скачку обновления, а также обновление для Win XP смотрите в конце статьи).

Итак, мы убедились, что IMAPI v2 поддерживается операционной системой. Теперь нам нужны заголовочные файлы, вернее нам надо установить компоненты для записи дисков. Программистам С++ по этой части проще, так как в Platform SDK есть заголовочные файлы и их надо только подключить чтобы воспользоваться классами для записи дисков. Но в Delphi тоже не очень сложно, в Delphi надо импортировать библиотеку типов. После импорта библиотеки типов Delphi сама создаст компоненты и классы для записи дисков и установит их в палитру. Что нам надо для этого сделать. Объясняю, как импортировать библиотеку типов для пользователей Delphi 7 (англ). Выбираем меню Project -> Import Type Library. В открывшемся окне в списке находим «Microsoft IMAPI2 Base Functionality (Version 1.0)», внизу ставим галочку «Generate Component Wrapper» (по умолчанию она поставлена), нажимаем кнопку «Install». Выбираем вкладку «Into new package» и выбираем файл, куда будет сохранён пакет, нажимаем кнопку ОК. После чего мастер сразу предложит установить эти компоненты, нажимаем «Yes» после установки перезагружаем Delphi. По-умолчанию новые компоненты должны установить на вкладку ActiveX. После чего проделываем то же самое с пунктом «Microsoft IMAPI2 File System Image Creator (Version 1.0)».

Итак, компоненты установлены, можно программировать. Как уже было сказано, Delphi сама создаёт классы и компоненты, которые являются оболочками вокруг соответствующих COM интерфейсов, что максимально упрощает программирование. Но есть один минус. Если мы используем интерфейсы напрямую, то если их работа методов заканчивается неудачно, то они просто возвращают ошибку, когда мы используем «дельфийские» классы, то методы классов в случае неудачи генерируют исключения, поэтому рекомендуется заключать вызовы методов и важные участки кода заключать в блоки try/except. Во время отладки (когда мы запускаем программу нажатием кнопки F9) сообщения об ошибках, всё равно выводятся, что довольно-таки раздражительно. В этой статье будут описаны методы COM интерфейсов, так как зная методы и названия COM интерфейсов мы сможем писать программы не только на Delphi, но и на С++ и VB. Методы и свойства компонентов Delphi описываться не будут, так как в них и так всё интуитивно понятно и просто.

Первое что надо сделать - это получить список приводов, которые могут прожигать диски и к которым можно получить доступ через IMAPI2. Это можно сделать через интерфейс IDiscMaster2. Первое что нам надо узнать это поддерживает ли хотя бы один привод в системе запись дисков и к нему можно обратиться через интерфейс IMAPI2. Для этого надо вызвать метод IDiscMaster2::get_IsSupportedEnvironment. Если мы получим результат true, то в системе есть, хотя бы один подходящий для нас привод. Для получения общего количества приводов в системе надо вызвать метод IDiscMaster2::get_Count. Для получения уникального идентификатора привода надо вызвать метод IDiscMaster2::get_Item.

Следующий пункт это инициализация привода и получение от него информации, пока нам понадобится только VendorId и ProductId привода. Именно эти две строки дают нам то названием привода, которое выводится в диспетчере устройств. Для получения этих двух идентификаторов надо вызвать методы IDiscRecorder2::get_VendorId, IDiscRecorder2::get_ProductId. Разумеется, сначала надо вызвать метод IDiscRecorder2::InitializeDiscRecorder, который принимает уникальный идентификатор привода, полученный от метода IDiscMaster2::get_Item.

Итак, займёмся самой «дельфёй». Ставим на форму компонент TMsftDiscMaster2 и TMsftDiscRecorder2. Далее приведу код получения списка доступных нам рекордеров:

procedure TForm1.UpdateRecordersButtonClick(Sender: TObject);
var
  i:integer;
begin
  RecordersComboBox.Clear;
  for i:=0 to MsftDiscMaster.Count-1 do
   begin
    try
     MsftDiscRecorder.InitializeDiscRecorder(MsftDiscMaster.Item);
     RecordersComboBox.Items.Add(MsftDiscRecorder.VendorId+' '+MsftDiscRecorder.ProductId);
     MsftDiscRecorder.Disconnect;
    except
     RecordersComboBox.Items.Add('---');
    end;
   end;
  RecordersComboBox.ItemIndex:=0;
end;

В ComboBox будут выведены все доступные пишущие рекордеры. Если какой-либо рекордер будет недоступен, то вместо его имени будет выведено «—».

Едем далее, мы нашли нужный нам рекордер, теперь надо создать образ диска для записи. За создание образа отвечает интерфейс IFileSystemImage. Файлы и папки в этом интерфейсе представляются интерфейсами IFsiFileItem и IFsiDirectoryItem. Для получения корневой папки надо вызвать метод IFileSystemImage::get_Root. Получив интерфейс корневой папки можно спокойно добавлять в образ файлы и папки. Есть несколько методов добавления файлов и папок в образ, я опишу наиболее простые из них.

Чтобы добавить папку со всеми её файлами удобно использовать метод IFsiDirectoryItem::AddTree, ему надо передать два параметра. Первый параметр это путь к папке, второй параметр имеет тип Boolean, если он равен true, то в образ будет добавлена сама папка и все содержащиеся в ней файлы и папки, если параметр равен false, то в образ будут добавлены только файлы и папки, содержащиеся в искомой (искомая папка не будет добавлена).

Для добавления файла необходимо использовать метод IFsiDirectoryItem::AddFile, первый параметр задаёт имя файла в образе, второй параметр задаёт интерфейс IStream с содержимым искомого файла. Для получения IStream c содержимым нужного файла можно использовать функцию SHCreateStreamOnFileEx. Ни в одном заголовочной файле Delphi нет этой функции (как минимум в Delphi 7), поэтому приведу объявление этой функции

Function SHCreateStreamOnFileEx(
    pszFile: PWChar; 
    grfMode:DWORD; 
    dwAttributes:DWORD;  
    fCreate:BOOL; 
    pstmTemplate:IStream; 
    var ppstm:IStream): DWORD;stdcall; external 'shlwapi.dll' name 'SHCreateStreamOnFileEx'; 

В этой функции нам интересно только два параметра, первый и последний. Первый параметр это путь к искомому файлу, в последний параметр будет сохранён поток с содержимым файла. Теперь можно написать код, которые создаёт образ диска из файлов пути, к которым занесены в ListBox

     DiscRoot:=MsftFileSystemImage.Root;
 
  for i:=0 to FilesListBox.Count-1 do
   begin
    if DirectoryExists(FilesListBox.Items) then
     DiscRoot.AddTree(FilesListBox.Items,true);
    if FileExists(FilesListBox.Items)  then
     begin
      wstr:=FilesListBox.Items;
      SHCreateStreamOnFileEx(PWideChar(wstr) ,0, 0, False ,nil, IStream(fstream));
      DiscRoot.AddFile(ExtractFileName(FilesListBox.Items),IMAPI2FS_TLB.IStream(fstream));
     end;
   end;

Чуть не забыл, чтобы задать имя диска надо вызвать метод IFileSystemImage::put_VolumeName. Для того чтобы установить настройки файловой системы в зависимости от того, какой диск вставлен в привод надо вызвать метод IFileSystemImage::ChooseImageDefaults передав ему в качестве параметра интерфейс IDiscRecorder2.\

Идём дальше, для того чтобы создать результирующий образ надо вызвать метод IFileSystemImage::CreateResultImage. После его вызова мы получим интерфейс IFileSystemImageResult. Для записи нам понадобится его IStream, для этого надо вызвать метод IFileSystemImageResult::get_ImageStream.

Мы получили результирующий образ, нам осталось только записать его. За запись образа отвечает интерфейс IDiscFormat2Data. Также есть интерфейсы IDiscFormat2RawCD, IDiscFormat2TrackAtOnce, IDiscFormat2Erase (для стирания дисков), работа с ними аналогична работе с IDiscFormat2Data.

Для установки привода, на котором будет производиться запись надо вызвать метод IDiscFormat2Data::put_Recorder. Чтобы запустить запись диска надо вызвать метод IDiscFormat2Data::Write передав ему в качестве параметра IStream с содержимым образа диска. Теперь можно написать функцию, которая записывает на диск файлы и папки указанные в ListBox.

procedure TForm1.BurnButtonClick(Sender: TObject);
var
  wstr:WideString;
  i:integer;
  DiscRoot:IFsiDirectoryItem;
  resimage:IFileSystemImageResult;
  DiscStream,fstream:IMAPI2_TLB.IStream;
  DR:IDiscRecorder2;
begin
 
  if RecordersComboBox.Items[RecordersComboBox.ItemIndex]='---' then exit;
  if FilesListBox.Count=0 then exit;
  MsftDiscRecorder.InitializeDiscRecorder(MsftDiscMaster.Item[RecordersComboBox.ItemIndex]);
 
  DiscRoot:=MsftFileSystemImage.Root;
 
  MsftDiscFormat2Data.Recorder:=MsftDiscRecorder.DefaultInterface;
  MsftDiscFormat2Data.ClientName:='IMAPI';
 
  DR:=IDiscRecorder2(MsftDiscRecorder.DefaultInterface);
  MsftFileSystemImage.ChooseImageDefaults(DR);
  MsftFileSystemImage.VolumeName:= DiscVolumeNameEdit.Text;
 
  for i:=0 to FilesListBox.Count-1 do
   begin
    if DirectoryExists(FilesListBox.Items) then
     DiscRoot.AddTree(FilesListBox.Items,true);
    if FileExists(FilesListBox.Items)  then
     begin
      wstr:=FilesListBox.Items;
      SHCreateStreamOnFileEx(PWideChar(wstr),0,0,False,nil,IStream(fstream));
      DiscRoot.AddFile(ExtractFileName(FilesListBox.Items),IMAPI2FS_TLB.IStream(fstream));
     end;
   end;
 
  resimage:=MsftFileSystemImage.CreateResultImage;
  DiscStream:=IMAPI2_TLB.IStream(resimage.ImageStream);
 
  LogListBox.Clear;
  LogListBox.Items.Add('запись началась');
  MsftDiscFormat2Data.Write(DiscStream);
  MsftDiscRecorder.EjectMedia;
  MsftDiscRecorder.Disconnect;
  BurnButton.Enabled:=false;
  ShowMessage('запись закончилась');
end;

Вроде бы всё. Осталось только выводить текущее состояние записи. Для этого нам надо создать свой интерфейс с методом Update

HRESULT Update(
  [in]  IDispatch *object,
  [in]  IDispatch *progress
);

Параметр object указывает на текущий интерфейс IDiscFormat2Data которые осуществляет запись. Параметр progress указывает на интерфейс IDiscFormat2DataEventArgs содержащий текущее состояние записи. Интерфейс IDiscFormat2DataEventArgs является потомком интерфейса IWriteEngine2EventArgs. Перед записью диска, необходимо, созданный нами интерфейс с обработчиком подсоединить его к интерфейсу IDiscFormat2Data. К счастью мастер Delphi избавил нас от этой мороки и создал компоненты со свойствами событиями, таким образом поставить свой обработчик также легко, как и поставить обработчик нажатия кнопки. Приводить полный код обработчика не имеет смысла, поэтому далее приведён только код который выводит текущий прогресс:

procedure TForm1.MsftDiscFormat2DataUpdate(ASender: TObject; const object_,
  progress: IDispatch);
var
  CurProgress: IDiscFormat2DataEventArgs;
  CurDiscF2D:IDiscFormat2Data;
  writtensectors:int64;
begin
  CurProgress:= progress as IDiscFormat2DataEventArgs;
  CurDiscF2D:=object_ as IDiscFormat2Data;
………….
  if CurProgress.CurrentAction =  IMAPI_FORMAT2_DATA_WRITE_ACTION_WRITING_DATA then
   begin
    writtensectors :=CurProgress.LastWrittenLba -CurProgress.StartLba;
    ProgressBar1.Position :=round((writtensectors/ CurProgress.SectorCount )*100);
   end;
………………
end;

Для получения текущего количества записанных секторов надо вычесть из адреса последнего записанного сектора, адрес сектора, с которого началась запись.

Вот, пожалуй, и всё на сегодня. Если нужны ещё какие-то аспекты записи дисков, которые вам хотелось бы, чтобы я осветил в следующих статьях, пожалуйста, оставляем комментарии со своими пожеланиями. Возможны неточности и ошибки, буду рад любой конструктивной критике. В архиве находится исходник программы-примера к этой статье. Так же прилагается архив с обновлением KB932716 для Win XP SP2 Rus.

Прочитано 15171 раз