65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Image Mastering API 2.0 (IMAPI2) 刻录 CD/DVD 介质

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (75投票s)

2007年12月29日

CPOL

5分钟阅读

viewsIcon

564710

downloadIcon

16617

使用 IMAPI2 的 CD/DVD 刻录应用程序示例。

引言

如果您正在寻找 C#/.NET 的示例,请查看我的文章:“使用 C# 和 IMAPI2 刻录和擦除 CD/DVD 介质”。

Windows 在 Vista 操作系统发布时引入了新的 IMAPIv2.0,这相比原始 IMAPI 有了很大的改进。原始 IMAPI 对于 CDROM 非常好,但它有一些巨大的限制,例如无法写入 DVD 介质。我相信这个限制是由于在 2001 年 Windows XP 发布时几乎没有人拥有 DVD 刻录机。IMAPIv2 允许您写入 CD 和 DVD 介质,以及读取和写入 ISO 文件。IMAPIv2.0 自发布以来就存在一个问题,因为它只随 Windows Vista 提供。但在 2007 年 6 月,微软为 Windows XP 和 Windows 2003 发布了更新包。您可以 在此处 下载更新。

您还需要下载并安装 Microsoft Windows 软件开发工具包 (SDK),以获取编译应用程序所需的头文件。您可以 在此处 下载 SDK。

下载并安装 SDK 后,您需要确保 SDK 的 include 目录在 Visual Studio 的 *Include* 路径中。在使用 Visual Studio 2005 时,您还必须将 SDK 的 *LIB* 路径作为 *LIB* 目录列表的第一个条目。

任何想要开发完整应用程序的人还应该阅读“Joliet 录制规范”。

Using the Code

我已经创建了几个围绕 IMAPIv2 接口的包装器,以协助管理接口实例

CDiscMaster 包装了 IDiscMaster2 接口,该接口允许您确定计算机是否安装了任何光驱,如果安装了,则允许您枚举计算机上安装的 CD 和 DVD 驱动器。

CDiscRecorder 包装了 IDiscRecorder2 接口,该接口代表每个物理驱动器。您可以使用此接口检索有关驱动器的信息,包括制造商信息、逻辑驱动器和支持的介质。

CDiscFormatData 包装了 IDiscFormat2Data 接口,该接口用于将数据写入介质。

CDiscFormatDataEvent 包装了 DDiscFormat2DataEvents 通知,用于接收 IDiscFormat2Data 写入函数的状态。

对于文件系统,我创建了一个基类 CBaseObject,它具有三个函数:GetPath()GetName()GetSizeOnDisc()GetPath 返回文件或目录在计算机上的完整路径。GetName 仅返回文件名或目录名,用于显示目的,也用作录制镜像根目录中文件或目录的名称。GetSizeOnDisc 是一个纯虚函数,它由派生自 CBaseObject 的两个类 CFileObjectCDirObject 实现。CFileObject 返回文件在介质上占用的空间。CDirObject 返回目录中所有文件和子目录的总大小。

我将在这里介绍几个要点,但您应该下载源代码来查看所有代码。

您需要在应用程序中包含 *imapi2* 头文件

#include <imapi2.h>
#include <imapi2error.h>
#include <imapi2fs.h>
#include <imapi2fserror.h>

需要 imapi2.himapi2error.h 来进行 imapi2 接口。需要 imapi2fs.himapi2fserror.h 来进行 imapi2 文件系统接口。

然后,我创建一个 CDiscMaster 类的实例,初始化它,并获取每个设备的唯一 ID。唯一 ID 用于初始化 CDiscRecord 对象,我使用该对象获取组合框条目的显示信息并存储项数据的指针。

CDiscMaster discMaster;
.
.
discMaster.Initialize();
.
.
long totalDevices = discMaster.GetTotalDevices();
for (long deviceIndex = 0; deviceIndex < totalDevices; deviceIndex++)
{
 CString recorderUniqueID = discMaster.GetDeviceUniqueID(deviceIndex);
 CDiscRecorder* pDiscRecorder = new CDiscRecorder();
 pDiscRecorder->Initialize(recorderUniqueID);
 //
 // Get the volume path(s). usually just 1
 //
 CString volumeList;
 ULONG totalVolumePaths = pDiscRecorder->GetTotalVolumePaths();
 for (ULONG volIndex = 0; volIndex < totalVolumePaths; volIndex++)
 {
  if (volIndex)
   volumeList += _T(",");
  volumeList += pDiscRecorder->GetVolumePath(volIndex);
 }
 //
 // Add Drive to combo and IDiscRecorder as data
 //
 CString productId = pDiscRecorder->GetProductID();
 CString strName;
 strName.Format(_T("%s [%s]"), (LPCTSTR)volumeList, (LPCTSTR)productId);
 int comboBoxIndex = m_deviceComboBox.AddString(strName);
 m_deviceComboBox.SetItemDataPtr(comboBoxIndex, pDiscRecorder);
}

当在设备组合框中选择一个项时,我获取所选设备的 CDiscRecorder 对象,该对象存储在项的数据中。然后,我使用 CDiscRecorder 对象获取支持的介质类型。支持的介质类型返回一个在 enum IMAPI_MEDIA_PHYSICAL_TYPE 类型中定义的整数。由于我得到的值如 IMAPI_MEDIA_TYPE_DVDPLUSRIMAPI_MEDIA_TYPE_DVDDASHR,我设置了三个成员变量(m_isCdromSupportedm_isDvdSupportedm_isDualLayerDvdSupported),如果任何一个系列介质受支持,则将它们设置为 true。然后,我将这些介质类型添加到介质类型组合框中,让用户选择他们将使用的介质类型。然后,我使用一个非常“粗略”的估计来确定用户已填充了介质的多少。

void CBurnCDDlg::OnCbnSelchangeDeviceCombo()
{
  m_isCdromSupported = false;
  m_isDvdSupported = false;
  m_isDualLayerDvdSupported = false;
  m_mediaTypeCombo.ResetContent();
  int selectedIndex = m_deviceComboBox.GetCurSel();
  ASSERT(selectedIndex >= 0);
  if (selectedIndex < 0)
  {
   return;
  }
  CDiscRecorder* discRecorder =
   (CDiscRecorder*)m_deviceComboBox.GetItemDataPtr(selectedIndex);
  if (discRecorder != NULL)
  {
   CDiscFormatData discFormatData;
   if  (!discFormatData.Initialize(discRecorder, CLIENT_NAME))
   {
    return;
   }
   //
   // Display Supported Media Types
   //
   CString supportedMediaTypes;
   ULONG totalMediaTypes = discFormatData.GetTotalSupportedMediaTypes();
   for (ULONG volIndex = 0; volIndex < totalMediaTypes; volIndex++)
   {
    int mediaType = discFormatData.GetSupportedMediaType(volIndex);
    if (volIndex > 0)
     supportedMediaTypes += _T(", ");
    supportedMediaTypes += GetMediaTypeString(mediaType);
   }
   m_supportedMediaTypes.SetWindowText(supportedMediaTypes);
   //
   // Add Media Selection
   //
   if (m_isCdromSupported)
   {
    int stringIndex = m_mediaTypeCombo.AddString(_T("700MB CD Media"));
    m_mediaTypeCombo.SetItemData(stringIndex, CD_MEDIA);
   }
   if (m_isDvdSupported)
   {
     int stringIndex = m_mediaTypeCombo.AddString(_T("4.7GB DVD Media"));
     m_mediaTypeCombo.SetItemData(stringIndex, DVD_MEDIA);
   }
   if (m_isDualLayerDvdSupported)
   {
    int stringIndex = m_mediaTypeCombo.AddString(_T("8.5GB Dual-Layer DVD"));
    m_mediaTypeCombo.SetItemData(stringIndex, DL_DVD_MEDIA);
   }
   m_mediaTypeCombo.SetCurSel(0);
   OnCbnSelchangeMediaTypeCombo();
  }
}

当用户将文件添加到列表时,我创建一个 CFileObject 并将其添加到文件列表框中。然后,我调用 UpdateCapacity 函数,该函数计算文件列表框中所有项所需的总存储空间,并更新容量进度条。

void CBurnCDDlg::OnBnClickedAddFilesButton()
{
  CFileDialog fileDialog(TRUE, NULL, NULL, OFN_FILEMUSTEXIST, _T
        ("All Files (*.*)|*.*||"), NULL, 0);
  if (fileDialog.DoModal() == IDOK)
  {
    CFileObject* pFileObject = new CFileObject(fileDialog.GetPathName());
    int addIndex = m_fileListbox.AddString(pFileObject->GetName());
    m_fileListbox.SetItemDataPtr(addIndex, pFileObject);
    UpdateCapacity();
    EnableBurnButton();
  }
}

当用户将文件夹添加到列表时,我创建一个 CDirObject 并将其添加到文件列表框中。与文件对象一样,我调用 UpdateCapacity 函数来计算文件列表框中所有项所需的总存储空间,并更新容量进度条。

void CBurnCDDlg::OnBnClickedAddFolderButton()
{
  BROWSEINFO bi = {0};
  bi.hwndOwner = m_hWnd;
  bi.ulFlags = BIF_RETURNONLYFSDIRS|BIF_USENEWUI;
  LPITEMIDLIST lpidl = SHBrowseForFolder(&bi);
  if (!lpidl)
    return;
  TCHAR selectedPath[_MAX_PATH] = {0};
  if (SHGetPathFromIDList(lpidl, selectedPath))
  {
    CDirObject* pDirObject = new CDirObject(selectedPath);
    int addIndex = m_fileListbox.AddString(pDirObject->GetName());
    m_fileListbox.SetItemDataPtr(addIndex, pDirObject);
    UpdateCapacity();
    EnableBurnButton();
  }
}

当用户按下“刻录”按钮时,我禁用用户界面并启动另一个线程 BurnThread 来执行刻录。这将使 UI 在刻录过程中保持响应。

void CBurnCDDlg::OnBnClickedBurnButton()
{
  if (m_isBurning)
  {
    SetCancelBurning(true);
  }
  else
  {
    SetCancelBurning(false);
    m_isBurning = true;
    UpdateData();
    EnableUI(false);
    AfxBeginThread(BurnThread, this, THREAD_PRIORITY_NORMAL);
  }
}
UINT CBurnCDDlg::BurnThread(LPVOID pParam)
{
  IStream* dataStream = NULL;
  CBurnCDDlg* pThis = (CBurnCDDlg*)pParam;
  if (!CreateMediaFileSystem(pThis, &dataStream))
  { // CreateMediaFileSystem reported error to UI
   return false;
  }
  //
  // Get the selected recording device from the combobox
  //
  int selectedIndex = pThis->m_deviceComboBox.GetCurSel();
  ASSERT(selectedIndex >= 0);
  if (selectedIndex < 0)
  {
    pThis->SendMessage(WM_BURN_FINISHED, 0, 
      (LPARAM)_T("Error: No Device Selected"));
    return 0;
  }
  CDiscRecorder* pOrigDiscRecorder =
    (CDiscRecorder*)pThis->m_deviceComboBox.GetItemDataPtr(selectedIndex);
  if (pOrigDiscRecorder == NULL)
  {
    //
    // This should never happen
    //
    pThis->SendMessage(WM_BURN_FINISHED, 0,
        (LPARAM)_T("Error: No Data for selected device"));
    return 0;
  }
  //
  // Did user cancel?
  //
  if (pThis->GetCancelBurning())
  {
    pThis->SendMessage(WM_BURN_FINISHED, 0, (LPARAM)_T("User Canceled!"));
    return 0;
  }
  pThis->SendMessage(WM_BURN_STATUS_MESSAGE, 0,
        (LPARAM)_T("Initializing Disc Recorder..."));
  //
  // Create another disc recorder because we're in a different thread
  //
  CDiscRecorder discRecorder;
  CString errorMessage;
  if (discRecorder.Initialize(pOrigDiscRecorder->GetUniqueId()))
  {
    //
    //
    //
    if (discRecorder.AcquireExclusiveAccess(true, CLIENT_NAME))
    {
     CDiscFormatData discFormatData;
     if (discFormatData.Initialize(&discRecorder, CLIENT_NAME))
     {
      discFormatData.SetCloseMedia(pThis->m_closeMedia ? true : false);
      //////////////////////////////
      //
      // Burn the media here
      //
      discFormatData.Burn(pThis->m_hWnd, dataStream);
      //
      // Release the IStream after burning
      //
      dataStream->Release();
      //
      // Eject Media if they chose
      //
      if (pThis->m_ejectWhenFinished)
      {
       discRecorder.EjectMedia();
      }
     }
     discRecorder.ReleaseExclusiveAccess();
     //
     // Finished Burning, GetHresult will determine if it was successful or not
     //
     pThis->SendMessage(WM_BURN_FINISHED, discFormatData.GetHresult(),
      (LPARAM)(LPCTSTR)discFormatData.GetErrorMessage());
    }
    else
    {
     errorMessage.Format(_T("Failed: %s is exclusive owner"),
      (LPCTSTR)discRecorder.ExclusiveAccessOwner());
     pThis->SendMessage(WM_BURN_FINISHED, discRecorder.GetHresult(),
      (LPARAM)(LPCTSTR)errorMessage);
    }
   }
   else
   {
    errorMessage.Format(_T("Failed to initialize recorder - Unique ID:%s"),
     (LPCTSTR)pOrigDiscRecorder->GetUniqueId());
    pThis->SendMessage(WM_BURN_FINISHED, discRecorder.GetHresult(),
     (LPARAM)(LPCTSTR)errorMessage);
   }
   return 0;
}

UI 通知

工作线程通过 SendMessage 命令与 UI 通信。我从工作线程向 UI 发送状态消息 (WM_BURN_STATUS_MESSAGE) 和刻录完成 (WM_BURN_FINISHED) 消息。

我通过 CDiscFormatDataEvent::Update 函数中的 WM_IMAPI_UPDATE 消息将事件通知发送到 UI。我在 CDiscFormatData::Burn 函数中创建一个 CDiscFormatDataEvent 类的实例,该类实现了 DDiscFormat2DataEvents 接口。当它收到事件时,它会获取数据并将其发送到 UI,以便 UI 可以更新状态。

历史

  • v1 - 2007 年 12 月 29 日

    • 初始发布。
  • v2 - 2008 年 1 月 8 日

    • 将项目从 Visual Studio 2008 转换为 Visual Studio 2005。
    • CDiscFormatDataEvent 类中移除了 ATL。
    • 示例应用程序静态链接 MFC DLL。

    v3 - 2008 年 1 月 16 日

    • 错误修复 - 未将镜像设置为介质大小。
    • 支持超大文件。感谢 Dale Stewart。
    • InitializeDiscRecorder 因虚拟 CDROM 等原因失败时,不会断言。
  • v4 - 2009 年 12 月 12 日

    • 更新了指向最新 Windows SDK 的链接。
    • 修复了一个 IStream 未释放的泄漏。
© . All rights reserved.