|
Запись CD с
данными используя IMAPI
Скачать к этой статье:
IMAPI Type Library,
Source.
Эта статья демонстрирует
возможность записи CD с данными (формат
Joliet) с использованием интерфейса
IMAPI.
Иллюстрация:

Запись дата-CD.
Перед началом работы
обратите внимание, что пример из этой статьи использует реализацию
IMAPI Wrapper. Для запуска примера у Вас
должна быть зарегистрирована библиотека
acclImapiWrapper.DLL в Глобальном кэше сборки (GAC).
Если вы расположите эту библиотеку в папке Debug
или Release Вашего проекта, Вы
также сможете запустить пример, но, возможно, понадобится изменить
ссылки (references) проекта.
Запись дата-CD
с использованием функций IMAPI состоит из
следующих шагов:
-
Инициализация
библиотеки путем получения экземпляра класса
DiscMaster, установка режима сборки в
Joliet путем получения экземпляра
JolietDiscMaster для сборки исходного образа, который
будет записан на диск, и установки активного привода.
-
Построение
структуры
JolietDiscMasterStorage, в которой представлены файлы,
которые вы хотите записать на диск, и имена этих файлов на
диске.
-
Вызов
JolietDiscMaster для копирования
файлов из структуры JolietDiscMasterStorage
в файл образа диска, и затем запись этого образа на диск.
Во время последнего шага
функция DiscMaster будет вызывать большое
количество событий, описывающих процесс записи и позволит прервать
запись.
Перечисленные выше шаги
далее будут описаны на языке VB.NET.
Программа на языке C# очень похожа,
поэтому подробно на ней останавливаться не будем.
1. Инициализация
библиотеки.
Единственный напрямую
доступный и инициализируемый класс в
IMAPI
Wrapper - это объект
DiscMaster, из которого могут быть получены все другие
классы. Этот объект должен описываться в VB
как WithEvents, так как мы хотим
отлавливать сообщения о процессе записи и посылать ему сообщения о
прерывании записи. Далее, режим записи задается путем инициализации
экземпляра объекта JolietDiscMaster.
Ссылка на этот экземпляр должна быть сохранена, так как она
потребуется для организации образа диска (повторный вызов
JolietDiscMaster() приведет к получению
нового экземпляра, что в данном случае недопустимо). После
выполнения всего выше перечисленного список записывающих приводов
системы помещается в комбо-бокс и определяется индекс выбранного
пользователем привода.
Private WithEvents discMaster As discMaster = Nothing
Private jolietDiscMaster As JolietDiscMaster = Nothing
Private Sub GetRecorders()
Dim ex As Exception
Try
discMaster = New DiscMaster()
jolietDiscMaster = discMaster.JolietDiscMaster()
Dim recorder As DiscRecorder
For Each recorder In discMaster.DiscRecorders
cboRecorder.Items.Add( _
New ComboRecorderWrapper(recorder))
Next
If (cboRecorder.Items.Count > 0) Then
cboRecorder.SelectedIndex = 0
End If
Catch ex
MessageBox.Show( _
Me, String.Format("Unable to initialise the IMAPI library {0}", ex), _
Text, MessageBoxButtons.OK, MessageBoxIcon.Stop)
If Not (discMaster Is Nothing) Then
discMaster.Dispose()
End If
End Try
End Sub
Private Sub cboRecorder_SelectedIndexChanged( _
ByVal sender As Object, _
ByVal e As System.EventArgs _
) Handles cboRecorder.SelectedIndexChanged
If (cboRecorder.SelectedIndex > -1) Then
Dim wrapper As ComboRecorderWrapper = cboRecorder.SelectedItem
discMaster.DiscRecorders.ActiveDiscRecorder = wrapper.DiscRecorder
End If
End Sub
|
Как отмечалось ранее (в
других статьях - Б.С.), вы должны быть уверены, что вызываете метод
Dispose объекта
DiscMaster до завершения вашего приложения. Невыполнение
этого условия приведет к тому, что все последующие обращения любых
программ к интерфейсу IMAPI будут
заканчиваться ошибкой "Stash In Use". Для
решения этой проблемы я добавил событие
Application ThreadException, которое выполняется при
возникновении любой неотслеживаемой ошибки или при экстренном
завершении работы приложения.
Public Sub New()
' ...
'Add any initialization after the InitializeComponent() call
AddHandler Application.ThreadException,
AddressOf application_ThreadException
Show()
Refresh()
GetRecorders()
End Sub
Private Sub application_ThreadException( _
ByVal sender As Object, _
ByVal e As ThreadExceptionEventArgs)
MessageBox.Show(Me, _
String.Format("An untrapped exception occurred: {0}.", _
e.Exception), _
Text, MessageBoxButtons.OK, MessageBoxIcon.Error)
Close()
End Sub
Protected Overrides Sub OnClosing( _
ByVal e As System.ComponentModel.CancelEventArgs)
' ....
'// Clear up:
MyBase.OnClosing(e)
Cursor.Current = Cursors.WaitCursor
sbrMain.Text = "Closing IMAPI interface..."
jolietDiscMaster.Dispose()
discMaster.Dispose()
Cursor.Current = Cursors.Default
End Sub
|
В этом примере я напрямую
отслеживаю исключение в стеке; в реальных приложениях такой способ
не используется, исключения должны протоколироваться и при их
возникновении нужно выводить более информативные сообщения.
2. Построение иерархии
JolietDiscMasterStorage.
Объект
JolietDiscMaster имеет свойство
RootStorage, которое возвращает экземпляр объекта
JolietDiscMasterStorage, который
представляет собой корневую директорию CD-диска.
Вы можете добавлять файлы, а также поддиректории к этому объекту;
поддиректории также представляются в виде экземпляров объекта
JolietDiscMasterStorage. При добавлении
файла вам нужно указать путь к файлу, который будет скопирован на
диск, и имя файла, под которым он будет записан на диске. У
подкаталогов есть только имена. В данном примере я просто задаю
папку для добавления, причем возможно рекурсивное добавление, то
есть добавление всех подпапок.
''' <summary>
''' Adds a directory to the storage, optionally including
''' any subdirectories.
''' </summary>
''' <param name="path">Path to the directory to add</param>
''' <param name="recurse"><c>true</c> to include
subdirectories,
''' <c>false</c> otherwise.</param>
Public Sub AddDirectory(ByVal path As String, ByVal recurse
As Boolean)
Dim storage As
JolietDiscMasterStorage = jolietDiscMaster.RootStorage
AddFilesToStorage(storage, path,
recurse)
End Sub
Private Sub AddFilesToStorage( _
ByVal storage As
JolietDiscMasterStorage, _
ByVal startPath As String, _
ByVal recurse As Boolean)
If (recurse) Then
Dim dir As
String
For Each dir
In Directory.GetDirectories(startPath)
Dim subStorage As JolietDiscMasterStorage = _
storage.CreateSubFolder(Path.GetFileName(dir))
AddFilesToStorage(subStorage, dir, recurse)
Next
End If
Dim file As String
For Each file In Directory.GetFiles(startPath)
storage.AddFile(file, Path.GetFileName(file))
Next
End Sub
3. Запись диска.
После того, как выполнены
все предыдущие шаги, дальнейший процесс создания диска достаточно
прост: во-первых нужно удалить старое содержимое образа (если оно
имеется); затем добавить данные, используя метод
AddData объекта JolietDiscMaster,
и, наконец, записать диск используя метод
DiscMasterRecordDisc:
''' <summary>
''' Creates a data CD from the specified files
''' </summary>
''' <param name="simulate">Simulate CD burning</param>
''' <param name="ejectWhenComplete"><c>true</c> to eject the CD
''' tray when the burn is complete, <c>false</c> otherwise</param>
''' <param name="overwrite"><c>true</c> to overwrite existing files
''' on CDRW media, <c>false</c> otherwise</param>
Public Sub CreateCD( _
ByVal simulate As Boolean, _
ByVal ejectWhenComplete As Boolean, _
ByVal overwrite As Boolean
)
'// Ensure we don't have anything in the stage
discMaster.ClearFormatContent()
'// Stage the content
jolietDiscMaster.AddData(overwrite)
'// burn the disc
discMaster.RecordDisc(simulate, ejectWhenComplete)
'// Easy!
End Sub
|
В процессе записи диска
могут возникать следующие 6 событий:
-
AddProgress - вызывается при добавлении файлов в образ;
-
PreparingBurn - возникает один раз, когда создание образа
завершено, и диск готов к записи;
-
BlockProgress - возникает по окончании записи каждого
блока данных (2048 байтов) на диск;
-
ClosingDisk - возникает единственный раз, когда запись
образа на диск завершена и диск (или сессия) готов к закрытию;
-
Complete - запись завершена;
-
QueryCancel - вызывается при создании образа или в
процессе записи диска. Установка параметра
Cancel в TRUE приведет к отмене
или прерыванию операции.
Вот и все, что вам
действительно необходимо для создания диска. Однако, если вы
захотите добавить в свое приложение возможность прерывания записи
или отображение хода операции, вам необходимо переписать программу
так, чтобы процесс записи осуществлялся в параллельном фоновом
потоке. Конструкция
IMAPI Wrapper
позволяет выполнить это достаточно просто, используя метод
BeginInvoke.
Использование
BeginInvoke для повышения удобства
использования приложения.
Запись компакт-диска - это
довольно длительная операция. Если вы будете вызывать процесс записи
из основного потока программы, то в процессе записи пользователю
будет казаться, что ваша программа зависла (блокирована). Кроме
того, в системах Windows XP и выше введена
функция, которая позволяет "заморозить" заголовок и границы окна,
если система определит, что приложение блокировано достаточно
продолжительное время. В приложениях для платформы
.NET после этого любые вызовы для
обновления элементов управления в окне приложения не будут иметь
эффекта.
Таким образом, нам
требуется способ выделения записи диска в отдельный фоновый поток.
Так как интерфейс IMAPI Wrapper
обеспечивает вызов события для определения, был ли прерван процесс
записи и отображение процесса записи, то это также является простым
способом прерывания процесса в любой момент.
Любой метод класса в
.NET может быть вызван в асинхронном
режиме путем создания делегата (delegate)
для него. Как только вы это сделаете, компилятор готов для
автоматического создания трех методов для делегата:
-
Метод
Invoke. который имеет те же параметры
и возвращаемое значение, что и сам делегат, и позволяет вызвать
метод в синхронном режиме.
-
Метод
BeginInvoke, который в целом
аналогичен Invoke, за исключением
того, что он добавляет еще два параметра: делегат
AsyncCallback и объект для хранения
состояния.
-
Метод
EndInvoke, который возвращает то же
значение, что и делегат, а все параметры ref
и out, а также внешние
параметры сохраняются в объекте IAsyncResult.
Все это делает асинхронное
обращение достаточно простым. Оно состоит из 4 шагов:
-
Объявление делегата
для метода, который вам необходимо вызвать асинхронно.
-
Написать полный
обработчик события для вызываемого метода, который будет вызван
по окончании работы метода.
-
Создать обработчик
AsyncCallback и подсоединить его к
созданному на предыдущем этапе обработчику события.
-
Вызвать метод-делегат
асинхронно, используя метод BeginInvoke.
Далее приведен код на
VB и C#, который
выполняет перечисленные этапы. Неудивительно, что этот код очень
прост; единственное отличие программы на VB
заключается в использовании ключевого слова
AddressOf для обращения к делегатам. В данном случае метод,
который я хочу вызвать - это метод CreateCD,
который включен в мой класс DataCDCreator.
Этот метод получает три булевских параметра (для имитации записи,
извлечения диска по окончании записи и перезапись), и не возвращает
никаких значений:
Код на
VB для асинхронного обращения
''' <summary>
''' Delegate representing the CreateCD method
''' </summary>
Public Delegate Sub CreateCDDelegate( _
ByVal simulate As Boolean, _
ByVal ejectWhenComplete As Boolean, _
ByVal overwrite As Boolean)
Private creator As DataCDCreator = Nothing
Private createCDHandler As CreateCDDelegate = Nothing
' Asynchronously invoke the creator
creator = New DataCDCreator(discMaster, jolietDiscMaster)
creator.AddDirectory(txtFolder.Text, True)
createCDHandler = AddressOf creator.CreateCD
Dim callback As AsyncCallback = AddressOf createCD_Complete
Dim ar As IAsyncResult = createCDHandler.BeginInvoke( _
simulate, ejectWhenComplete, overwrite, callback, Nothing)
''' <summary>
''' Called when CD creation completes
''' </summary>
''' <param name="ar">Result of method call (none)</param>
Private Sub createCD_Complete(ByVal ar As IAsyncResult)
' NB should surround with try - catch - end try
SetApplicationMode(False)
createCDHandler.EndInvoke(ar)
End Sub
|
Код на
C# для асинхронного обращения
/// <summary>
/// Delegate representing the CreateCD method
/// </summary>
public delegate void CreateCDDelegate(
bool simulate,
bool ejectWhenComplete,
bool overwrite);
private DataCDCreator creator = null;
private CreateCDDelegate createCDHandler = null;
// Asynchronously invoke the creator
creator = new DataCDCreator(discMaster, jolietDiscMaster);
creator.AddDirectory(txtFolder.Text, true);
createCDHandler = new CreateCDDelegate(creator.CreateCD);
AsyncCallback callback = new AsyncCallback(createCD_Complete);
IAsyncResult ar = createCDHandler.BeginInvoke(
simulate, ejectWhenComplete, overwrite, callback, null);
/// <summary>
/// Called when CD creation completes
/// </summary>
/// <param name="ar">Result of method call (none)</param>
private void createCD_Complete(IAsyncResult ar)
{
// NB should surround with try-catch
SetApplicationMode(false);
createCDHandler.EndInvoke(ar);
}
|
Идеи по улучшению
приложения.
Приложение, описанное в
данной статье, очень простое, и все, что оно позволяет сделать - это
скопировать существующий каталог на CD.
Однако это не является серьезным ограничением, так как когда вы
создаете хранилище
JolietDiscMasterStorage, вы можете
указать файлы, находящиеся в других папках или по-другому
расположить их на диске. Более полное приложение должно позволить
пользователю перетащить файлы из любого места на диск и затем
переименовать их если это необходимо.
|