使用 IMAPI 2.0 从托管代码刻录光盘镜像






4.95/5 (11投票s)
使用 IMAPI v2.0 和 C# 刻录 ISO 文件

引言
如果您看过我之前的文章,如何使用 IMAPIv2.0 创建光盘镜像,您可能会期望我对这个镜像文件做些什么,比如……刻录它。我使用相同的 Image Mastering API v2.0 来实现这个目标,所以在运行此应用程序之前,您可能需要先安装它。可能存在其他刻录引擎,但我选择使用此 API,因为它很可能获得 Microsoft 的长期支持。使用 COM 互操作包装本机组件有一些我试图修复的怪癖,但由于有 Microsoft 的示例,这并不算太难。如果您只对将文件夹和文件刻录到介质上而不创建文件镜像感兴趣,那么 Eric Haddan 的精彩文章会更适合您。如果您需要更深入地了解内部工作原理或 COM 互操作问题,我建议您查看上述文章。我对上一篇文章的代码只做了小的修改,并且还从我下面引用的来源中获得了一些灵感。
背景
如果您想知道在可以冷刻录文件的情况下,为什么还需要刻录 ISO 镜像,那么您应该知道,使用 IMAPI 2.0 无法轻松创建所有格式。但是,如果刻录机支持该介质类型,刻录文件流则没有问题。换句话说,如果您有一个包含 DVD 电影的 ISO 文件,您将能够刻录它,但即使您复制了该介质上的所有文件,也无法创建正确的 DVD 光盘。虽然您可能可以在您的计算机和一些新播放器上播放后者,但大多数 DVD 播放器将无法播放它,因为它们期望定制的 UDF 格式。在这种情况下,刻录由其他工具创建的现有 ISO 文件镜像可以解决此问题。
刻录光盘文件镜像
刻录光盘镜像归结为将一个流写入您的介质。写入和格式化的代码与 Eric 文章中的代码类似,但有一些改动。使用我在上一篇文章中描述的 ImageRepository.LoadCOMStream
来获取流接口。我已经将大部分刻录功能打包在一个名为 BurnHelper
的帮助类中,以下是它的 WriteStream
方法
public int WriteStream(System.Runtime.InteropServices.ComTypes.IStream stream,
string initburner, string clientName,
bool forceMediaToBeClosed, int speed, bool eject)
{
MsftDiscRecorder2 discRecorder = null;
MsftDiscFormat2Data discFormat = null;
int result = -1;
try
{
discRecorder = new MsftDiscRecorder2();
discRecorder.InitializeDiscRecorder(initburner);
discFormat = new MsftDiscFormat2Data();
//remove the comment for next 2 lines
discFormat.Recorder = discRecorder;
discRecorder.AcquireExclusiveAccess(true, clientName);
// initialize the IDiscFormat2Data
discFormat.ClientName = clientName;
discFormat.ForceMediaToBeClosed = forceMediaToBeClosed;
// add the Update event handler
discFormat.Update += DiscFormatData_Update;
//this is how it worked for my burner
//speed = 0 => minimum speed descriptor in update
// 0 < speed < minimum speed descriptor => half of minimum
// speed descriptor in update
// minimum speed descriptor <= speed < next speed
// descriptor => minimum speed descriptor in update
// next speed descriptor <= speed => next speed descriptor in update
discFormat.SetWriteSpeed(speed, true);
//write the stream
discFormat.Write(stream);
if (_backgroundWorker.CancellationPending)
{
return 1;
}
if (eject)
{
//wait to flush all the content on the media
IMAPI_FORMAT2_DATA_MEDIA_STATE state =
IMAPI_FORMAT2_DATA_MEDIA_STATE.IMAPI_FORMAT2_DATA_MEDIA_STATE_UNKNOWN;
while (state == IMAPI_FORMAT2_DATA_MEDIA_STATE.
IMAPI_FORMAT2_DATA_MEDIA_STATE_UNKNOWN &&
!_backgroundWorker.CancellationPending)
{
try
{
state = discFormat.CurrentMediaStatus;
}
catch (Exception)
{
state = IMAPI_FORMAT2_DATA_MEDIA_STATE.
IMAPI_FORMAT2_DATA_MEDIA_STATE_UNKNOWN;
System.Threading.Thread.Sleep(3000);
}
}
if (!_backgroundWorker.CancellationPending)
discRecorder.EjectMedia();
}
result = 0;
}
finally
{
if (_backgroundWorker.CancellationPending)
result = 1;
if (discFormat != null)
{
// remove the Update event handler
//
discFormat.Update -= DiscFormatData_Update;
Marshal.FinalReleaseComObject(discFormat);
}
if (discRecorder != null)
{
//discRecorder.EnableMcn();
discRecorder.ReleaseExclusiveAccess();
Marshal.FinalReleaseComObject(discRecorder);
}
}
return result;
}
如果您查看我关于写入速度的代码注释,您会注意到可能存在 IMAPI 未作为描述符显示的刻录速度。您可能可以通过使用描述符显示在进度更新 UI 中的数字以外的其他数字来发现它们。我还必须延迟弹出介质,直到所有内容都刷新到介质上,以避免崩溃。
检测介质更改
不应想当然地认为介质会与 UI 同步。某些系统可能已通过 Active Directory 策略或注册表设置禁用了介质通知。我最初尝试使用 WMI 来检测这些事件,但在插入空白介质时没有收到任何通知。因此,我最终使用了老式的 WM_DEVICECHANGE
Windows 消息来获取介质更改。DeviceMonitor
类中有相当多的代码来实现这一点,该类以此文章为起点。以下是该类的最重要的方法
protected override void WndProc(ref Message m)
{
// There has been a change in the devices
const int WM_DEVICECHANGE = 0x0219;
switch (m.Msg)
{
case WM_DEVICECHANGE:
{
if (m.LParam == IntPtr.Zero)
break;
DEV_BROADCAST_VOLUME vol = (DEV_BROADCAST_VOLUME)
Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_VOLUME));
if ((vol.dbcv_devicetype == _dt) && (vol.dbcv_flags == _vf))
PostMessage(new HandleRef(this, Handle),
POST_MSG_DEVICECHANGE, m.WParam,
(IntPtr)vol.dbcv_unitmask);
}
break;
default:
if (POST_MSG_DEVICECHANGE == m.Msg)
{
DeviceEventBroadcast ev = (DeviceEventBroadcast)m.WParam.ToInt32();
List<char> drives = DriveNames((uint)m.LParam);
List<char>notifyDrives = new List<char>(1);
_drives.ForEach(delegate(char drv)
{
if (drives.Contains(drv)) notifyDrives.Add(drv);
});
if (DeviceAction != null && notifyDrives.Count > 0)
DeviceAction(notifyDrives, ev);
}
break;
}
base.WndProc(ref m);
}
如果您想知道为什么我需要重新发送原始的 WM_DEVICECHANGE
到 POST_MSG_DEVICECHANGE
,那是因为 WM_DEVICECHANGE
发生在 IMAPI 意识到介质更改之前,否则会导致崩溃。
其他特性
与之前的版本不同,我无法使用 Application.DoEvents
通过消息泵使用唯一的 UI 线程,因为 IMAPI 事件太稀疏,无法提供流畅的用户体验,所以我不得不使用 BackgroundWorker
进行刻录。您可以选择格式化 RW 介质,有时甚至可以强制格式化无法识别的介质,这是一个我在其他刻录程序中找不到的简单功能。与我的上一篇文章相比,您还会发现许多新功能和增强功能。我必须提到,重启、睡眠等功能基于 mentalis 的代码,并且通过添加文件使介质可启动的功能存在一个我尚未修复的 bug。如果您想尝试一下,代码在 ImageRepositoty.CreateBootDisc
方法中,所以请随意修改。我尝试了 IMAPI2FS.FsiStream
和 COMTypes.IStream
作为参数,但都没有成功。如果您找到了解决方法,请告诉我。
通过应用程序“选择文件”选项卡上的复选框即可添加启动镜像选项,如下面的图片所示

如何使用它
由于我的环境有限,我没有进行广泛的测试,因此在我的系统上有效的方法可能在您的系统上无效。创建光盘文件镜像与我上一篇文章中的操作相同,只是缺少可选的可启动选项。从“构建镜像”选项卡刻录文件镜像应该很容易,您需要选择镜像和刻录机,可选地选择速度、弹出并关闭介质,然后点击“刻录镜像”按钮。如果您愿意,应该可以取消操作,但如果介质不是 RW,可能会损坏它。
代码重用
该解决方案包含两个项目,用于将 UI 与实际执行镜像创建和刻录的类分开。您可以通过添加对 ISOImage.dll 或 OpticalImage
项目的引用来轻松重用此功能,该项目提供 Visual Studio 2005 和 2008 的两个版本。如果您运行的不是 Vista,则必须安装 Image Mastering API v2.0 才能使用它。
历史
- 2008 年 5 月 16 日:初始版本
- 2008 年 5 月 29 日:版本 2.0.0.1 - 修复了一些 UI 和
VolumeName
的 bug