» Запись дисков в Delphi Borland Delphi. Win Api. windows. Блог программистов


Блог программистов






200929 Янв

Запись дисков в 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.

Добавлено 27.04.2009:

Программа-пример не всегда нормально работает, если запущена прямо из Delphi. Поэтому в некоторых случаях рекомендуется запускать его отдельно от Delphi.

Официальная страница IMAPI на узле MSDN

Официальная страница с обновлением KB932716 на узле Microsoft.com

Скачать архив с исходником на Delphi

Скачать обновление KB932716 для Win XP SP2 Rus

Комментарии

  1. 17 февраля, 2009 | 18:22

    Отлично! Спасибо за статью. Теперь сделаю конкурента Nero 🙂 А на самом деле, бухи заставили отчётность на диски бекапить, буду пробовать.

  2. Анатолий
    18 марта, 2009 | 16:32

    Спасибо Вам за статью. Очень полезный материал. Это единственная статья по записи дисков с помощью IMAPI которую мне удалось найти в интернете. Но, к моему большому сожалению, при перекомпиляции проекта программа не работает корректно, например при нажатии кнопки \стереть диск\ выдается ошибка Project raised exeption class EAccessViolation with Message \access violation at adress 42615168 in module imapi2.dll. Read of adress 00000000. Process stoped. Use step to Run to continue. При нажатии других кнопок возникают подобные ошибки. Скажите, пожалуйста, в чем на Ваш взгдяд может быть ошибка?

  3. 7ser7ega7
    27 марта, 2009 | 19:23

    Спасибо, довольно полезная статья. Мне как раз в универ необходимо написать курсач — программу управления сд-ромом при помощи api win 32, думаю что этот пример подойдёт отлично.

  4. lex
    29 ноября, 2009 | 17:04

    омг, большое спасибо

  5. Вадим
    7 мая, 2010 | 09:51

    Отлично, спасибо большое. Все работает, но еще хотелось бы узнать, можно ли выбирать скорость записи

  6. rpy3uH
    10 мая, 2010 | 18:04

    да, можно. в принципе через IMAPI2 доступны все возможности необходимые при записи дисков

  7. lionme
    20 июня, 2010 | 19:05

    Ехе запускается нормально..Всё работает..А вот исходники программы не компилируются,программа не запускается.
    Компоненты встали нормально! delphi7! Windows XP! Всё установил как в статье!

  8. Аня
    2 марта, 2011 | 18:47

    Спасибо большое за статью! Лаконично и понятно 🙂 Но я бы хотела узнать как создавать multi session диск, и дописывать в него данные. Если можно, пожалуйста напишите про это тоже, мне очень надо )

  9. Влад
    24 апреля, 2012 | 14:32

    Помогите, пожалуйста!
    У меня та же проблема, что и у Анатолия, Access Violation независимо от того, запускать ли из под Delphi или отдельно, как же починить??
    Может ли проблема решиться, если поставить Windows 7 вместо XP? (машина рабочая)

  10. Влад
    24 апреля, 2012 | 14:36

    При этом, что интересно, экзешник из архива до перекомпиляции работает совершенно нормально. Может, там какие-то опции компилятора надо поменять??

  11. rpy3uH
    26 апреля, 2012 | 22:39

    возможны косяки при импорте библиотеки типов, различия версий и любой тысяч возможных глюков и косяков

  12. Bill
    24 августа, 2012 | 21:38

    Я использую Google Translate. Я надеюсь, что это работает.

    Спасибо за статью. Это спасло меня много времени, потому что
    У меня нет COM опыта. Но у меня есть опыт в программировании
    с «SCSI Multi Media Command» установить для записи данных на компакт-диски
    и DVD-дисков.

    Я продлил IMAPI2, чтобы иметь возможность сохранить файл ISO на
    Жесткий диск «HD», чтобы импортировать файл ISO с жесткого диска «HD»
    для записи и копирования данных на компакт или DVD в ISO. Я сделал
    приложение, чтобы продемонстрировать, как реализовать эти функции
    В Delphi.

    Я хотел бы сделать некоторые компоненты таким Delphi программисты могут
    положить их на форму и CD / DVD / BD Suppport. Но мне нужна помощь.
    Заинтересованы ли вы в помощи? Или вы знаете кого-то, кто может
    быть заинтересован в этом? Я могу послать вам демонстрационную программу.

    С уважением,
    Bill

  13. Bill Mudd
    25 августа, 2012 | 22:34

    Мой английский язык. Если вы знаете английский я
    могли бы объяснить подробнее.

    С уважением