![]() |
|
URL dieses Artikels:
zu Ausgabe:
6.2004
Burn Baby, Burn
Unter Windows XP CDs mit Delphi brennen
von Bernd Ua
Eine der neuen APIs, die unter Windows XP neu dazu gekommen sind, heißt Imapi. Auch wenn der Name es nahe legt, handelt es sich nicht um ein neues Imaging API oder die Erweiterung der altbekannten Mapi-Schnittstelle für Mail, sondern um eine Betriebssystem-Schnittstelle für das Brennen von Daten und Audio-CDs (Image Mastering API). Um mit den aktuellen Brennprogrammen in Punkto Multimedia-CDs zu konkurrieren, ist die Schnittstelle vielleicht nicht leistungsfähig genug, aber eine durchaus praktische Angelegenheit, wenn man eigene Programme um Möglichkeiten für die schnelle Datensicherung erweitern möchte. Einen Überblick über die Möglichkeiten dieser Schnittstelle gibt Ihnen der folgende Artikel.
Die Funktionen des Image Mastering API in Windows XP stellt ein Systemdienst namens Imapi.exe zur Verfügung. Ist dieser Dienst nicht gestartet, stehen die Funktionen nicht zur Verfügung. Erkennt Windows XP einen CD-Brenner im System, wird der Dienst automatisch gestartet. In älteren Windows-Versionen stehen die Funktionen nicht zur Verfügung. Imapi.Exe fungiert als COM-Server und stellt die Funktionen der Schnittstelle als eine Sammlung von COM-Schnittstellen zur Verfügung. In Anbetracht des GDI+ API, das mit seinen exportierten C++-Objekten in vollem Funktionsumfang nur den C++-Entwicklern zur Verfügung steht, kann man als Delphi-Entwickler schon mal aufatmen. Der Versuch, die Typbibliothek aus der ausführbaren Datei des Dienstes zu importieren, schlägt nur leider fehl. Imapi in Delphi verwenden Im Platform SDK oder auf der MSDN-CD finden sich zumindest eine rudimentäre Beschreibung der Schnittstellen und ein kleines How-to für deren Einsatz. Im Windows SDK sind die neuen Schnittstellen und deren Fehlercodes in den Headerdateien Imapi.h und imapierror.h definiert. Ein Blick in das entsprechende Source-Verzeichnis von Delphi offenbart schnell, dass Borland diese Headerdatei nicht übersetzt hat. Leider gilt das für die meisten neuen Funktionen, die in Windows XP respektive Windows Server 2003 in dem API ergänzt wurden. Bevor Sie jedoch selbst Hand anlegen und die Headerdateien in Pascal-Import-Units umsetzen, sollten Sie sich beim Projekt Jedi (Joint Effort of Delphi Innovators) umschauen. Hier finden Sie neben anderen nützlichen Komponenten und Headerübersetzungen auch eine vollständigere Umsetzung des Windows APIs (win32api.zip) als die von Borland gelieferte. Autor der API-Übersetzung ist Marcel van Brakel, auf dessen Seite Sie in der Regel auch die aktuellste Version der Headerübersetzung vorfinden. In diesen Api-Dateien finden Sie auch die benötigten pas-Dateien jwaImapi.pas und jwaImpaiErrors.pas, die Sie in die uses-Anweisung aufnehmen sollten. Den Einsatz dieser Schnittstellen sollten Sie vorsichtshalber gegen einen Aufruf unter älteren Windows-Versionen absichern. Um zwischen Windows 2000 und Windows XP sowie Windows Server 2003 zu unterscheiden, müssen Sie die Nebenversionsnummer prüfen, die Sie in der Delphi-Konstante Win32MinorVersion (Unit SysUtils) finden. Win32MajorVersion ist bei den oben genannten Systemen 5, während die Nebenversion für Imapi mindestens 1 sein sollte (XP). Von den vier Hauptobjekte der Schnittstelle (MSDiscMasterObj, MSDiscRecorderObj, MSDiscStashObj und MSBurnEngineObj) sind nur die ersten beiden für den Entwickler interessant. Die letzten beiden werden lediglich intern verwendet, um die Engine und das Imagefile zu verwalten. MSDiscMasterObj ist das Hauptobjekt der Schnittstelle und stellt Methoden zur Verfügung, um die Schnittstelle zu öffnen und zu schließen, eine CD zu brennen und eine Fortschrittbenachrichtigung einzurichten. Daneben verwaltet das Hauptobjekt den aktiven Recorder und das aktive Format. Beides müssen Sie zunächst einstellen. Prinzipiell unterstützt Imapi zwei Formate, das Audio-Format Redbook, das immerhin schon 1980 von Sony und Philips entwickelt wurde und die Datenformate Joliet und ISO 9660. Das Joliet-Format wurde 1995 von Microsoft als Erweiterung des ISO-9660-Formats entwickelt, um lange Dateinamen und tiefere Verzeichnisstrukturen zu unterstützen. Theoretisch unterstützt nicht jeder Recorder jedes Format, in der Praxis allerdings konnte ich keinen aktuellen Brenner finden, der eines der Formate nicht unterstützt. Den DiscMaster verwenden Da die bequeme, automatisch erzeugte Hilfsklasse des Typbibliotheksimports fehlt, kommen Sie nicht umhin, dieses Objekt MSDiscMasterObj über CreateComObject mit der gleichnamigen ID aus jwaImapi.pas selbst zu erzeugen. Wenn Sie vermeiden wollen, bei folgenden Aufrufen den Fehlercode IMAPI_E_NOTOPENED zu erhalten, öffnen Sie die Schnittstelle anschließend auch gleich. FDiscMaster : IDiscMaster;Um die unterstützten Formate zu ermitteln, verwenden Sie die Methode EnumDiscMasterFormats, die Ihnen einen COM-Enumerator zurückliefert, mit dessen Hilfe Sie die unterstützten Formate aufzählen können. Der Enumerator liefert zurzeit maximal die zwei Formate Joliet und Redbook. Die Next-Methode des Enumerators liefert die GUID der entsprechenden Schnittstelle IJolietDiscMaster oder IRedbookDiscMaster zurück (Listing 1). Listing 1 procedure TFrmMain.ListFormats(aList: TStrings);Der Enumerator ist die einzige Methode, die Ihnen zur Verfügung steht, um sicherzustellen, dass die Formate unterstützt werden. ![]() Abb. 1: Das Imapi-Hauptobjekt MSDiscMasterObj Neben der Standardschnittstelle IDiscMaster unterstützt das Objekt MSDiscMasterObj auch die beiden spezialisierten Schnittstellen für Redbook und Joliet (Abb. 1), die Sie via QueryInterface von dem Objekt unabhängig davon erhalten können, ob das Format unterstützt wird oder nicht. Sie müssen also entweder die oben gezeigte Methode zur Prüfung verwenden oder versuchen, das Format mit der IDiscMaster-Methode SetActiveFormat ohne Prüfung einzustellen und riskieren hier eine Exception. SetActiveFormat erwartet als Eingabe die Interface-ID des Formats und liefert die Schnittstelle zurück: varÜber die formatspezifischen Schnittstellen haben Sie die Möglichkeit, neue Daten oder Audiotracks in das Image zu schreiben oder den verfügbaren Platz zu ermitteln. Das Format sollten Sie möglichst frühzeitig wählen, da bei einem Wechsel ein gegebenenfalls begonnenes CD-Image verworfen und auch die Liste der verfügbaren Brenner entsprechend dem Format neu aufgebaut wird. CD-Brenner abfragen Die Liste der Brenner, die in Ihrem System von Windows XP erkannt wurden, fragen Sie ebenfalls über einen Enumerator ab. Die Methode EnumRecorders liefert Ihnen die Schnittstellen für alle Recorder im System zurück. Mithilfe der erhaltenen IDiscRecorder-Schnittstellen können Sie weitere Informationen zu den vorhandenen CD-Brennern abfragen. Die Methode GetDisplayNames liefert Ihnen nötige Informationen für die Anzeige im GUI (z.B. Herstellername, Model und Revision). Mit GetPath ermitteln sie den eindeutigen Device-Pfad wie beispielsweise \Device\CdRom1. Der folgende Beispielcode kopiert Schnittstellen für die Recorder des Systems in eine Delphi-Klasse TInterfaceList und schreibt die interessanten Strings für die Anzeige in eine zweite String-Liste (Listing 2). Listing 2 procedure TFrmMain.ListRecorders<br></br> (aIntfList: TInterfaceList;Daneben können Sie mit den weiteren Methoden der Schnittstelle den Typ (CDR oder CDRW) und den aktuellen Status des Recorders, d.h., ob er gerade brennt oder geöffnet ist, abfragen (Listing 3). Listing 3 // Recorder-Eigenschaften in Listbox lbProps ausgebenLeider fehlt eine direkte Möglichkeit, die Geschwindigkeit des Recorders abzufragen. Das Lesen und Schreiben dieser Eigenschaft hat Microsoft hinter den Methoden Get- bzw. SetRecorderProperties versteckt. Die Methoden arbeiten mit einem Schnittstellenzeiger auf IPropertyStorage. Den Vorteil, dass auch herstellerspezifische Eigenschaften gelesen und gesetzt werden können, bezahlt der Entwickler nur leider mit etwas erhöhtem Aufwand. Wollten Sie alle Eigenschaften auslesen, die zur Verfügung gestellt werden, müssten Sie zunächst einen Enumerator von IPropertyStorage anfordern und sich alle Eigenschaften listen lassen, um sich diese anschließend mit ReadMultiple abzufragen. Die interessanteste Eigenschaft, nämlich die Schreibgeschwindigkeit, heißt für die meisten Recorder WriteSpeed. Der Zugriff auf eine bekannte Property mit bekanntem Datentyp ist noch recht kurz zu schreiben: varEinige weitere Funktionen des Schnittstelle IDiscRecorder stehen Ihnen allerdings nur dann zur Verfügung, wenn Sie den Recorder mit der Methode OpenExclusive exklusiv für Ihre Anwendung geöffnet haben. Zu diesen Methoden gehören beispielsweise Eject, Erase, Close und die Methode QueryMediaType, um Informationen über das eingelegte Medium in Erfahrung zu bringen. Sie sollten den Recorder nur möglichst kurz exklusiv öffnen, da der Recorder solange für den Rest des Systems gesperrt. Für das Schreiben einer CD ist das exklusive Öffnen nicht erforderlich, sondern verhindert es sogar. IDiscMaster.RecordDisc bricht mit einer Exception ab, wenn der zu verwendende Recorder exklusiv geöffnet ist. Imapi Callbacks Wenn Sie Ihrer Anwendung die Möglichkeit spendieren wollen, den Brennvorgang abzubrechen oder eine Fortschrittsanzeige einbauen wollen, können Sie das fast mit einem einzigem Methodenaufruf erledigen. Die IDiscMaster-Schnittstelle stellt dafür die Methode ProgressAdvise zur Verfügung. Der einzige Haken besteht darin, dass die Methode als Parameter eine Schnittstelle erwartet, die sie implementieren müssen. Es handelt sich dabei um die Schnittstelle IDiscMasterProgressEvents, die ihrerseits neun Methoden hat, die von Imapi bei der passenden Gelegenheit aufgerufen werden. Dank der guten Schnittstellenunterstützung in Delphi sollte die Herausforderung aber nicht allzu groß sein. Es ist nicht einmal ein COM-Objekt erforderlich. Theoretisch können Sie die Schnittstelle sogar in direkt einem Formular implementieren. Der Beispielcode verwendet allerdings einen Nachfahren von TInterfacedObject dafür, der im Constructor das Formular übergeben bekommt (Listing 4). Listing 4 TCallBackObj = class(TInterfacedObject,IDiscMasterProgressEvents)Die Methoden der Schnittstelle haben sprechende Namen, sodass nicht schwer zu erraten ist, dass NotifyBurnComplete beim Ende des Brennvorgangs aufgerufen wird oder QueryCancel es Ihnen ermöglicht, den Brennvorgang abzubrechen. Sie müssen auch nicht alle Methoden vollständig implementieren. Wenn Sie beispielsweise nur Daten und keine Audiotracks brennen wollen, liefern Sie in der Methode NotifyTrackProgress nur den Wert E_NOTIMPL in HResult zurück. Gleiches gilt für alle anderen Methoden, die für Sie nicht von Interesse sind. Haben Sie ein Ereignis erfolgreich verarbeitet, sollte der Rückgabewert stattdessen S_OK sein. Im Beispielprogramm gibt das Hilfsobjekt die Informationen direkt an ein Memo oder Fortschrittsanzeigen seines Eigentümer-Formulars weiter, wie im folgenden Methodenrumpf: function TCallBackObj.NotifyAddProgressDas Hilfsobjekt wird durch einen Schnittstellenzeiger im Formular im Speicher gehalten und an die ProgressAdvise-Methode übergeben. Das Diskmaster-Objekt liefert daneben einen eindeutigen Cookie zurück, den Sie sich merken sollten, um die Benachrichtigungen auch wieder abschalten zu können. ProgressUnadvise möchte von Ihnen eben diesen Integerwert als Parameter bekommen: FCookie : Cardinal; ![]() Abb. 1: Fortschrittsanzeige dank IDiscMasterProgressEvents Image Dateien erstellen und brennen Bevor Sie die CD brennen, bauen Sie zunächst ein CD-Image auf, das intern vom bereits erwähnten Stash-Objekt verwaltet wird. Durch die Formatauswahl haben Sie ja bereits die Schnittstelle IJolietDiscMaster oder im Falle einer Audio-CD die Schnittstelle IRedbookDiscMaster erhalten. Mithilfe dieser Schnittstellen fügen Sie Daten dem Image hinzu. Im Falle der Audio-CD verwenden Sie dazu die Methode CreateTrack und nachfolgend Aufrufe von AddAudioTrackBlocks, um Wav-Dateien einem Track zuzufügen. Im Falle der Daten-CD rufen Sie die Methode AddData auf. Imapi verwendet für die Daten Structured Storage und erwartet als Eingabe-Parameter für AddData ein IStorage-Interface. Alle Dateien und Verzeichnisse, die Sie dem CD-Image zufügen wollen, müssen Sie also zunächst in ein IStorage schreiben. Für Verzeichnisse legen Sie dafür Sub-Storages an und Dateien legen Sie entsprechend als Streams in dem Storage an. Im Beispiel zum Artikel kann ein beliebiges Verzeichnis ausgewählt und alle Dateien und Unterverzeichnissen auf CD gebrannt werden. Dafür wird zunächst ein temporäres (Name ist nil) Wurzel-Storageobjekt erzeugt: VarDer Flag STGM_DELETEONRELEASE sorgt dafür, dass der Speicher nach der Freigabe der Schnittstelle mit freigegeben wird. Die Hilfsfunktion AddDirToStorage durchsucht dann rekursiv mit FindFirst/FindNext die Unterverzeichnisse und fügt Dateien als Streams und Verzeichnisse als Substorages hinzu. Der folgende Code zeigt, wie Sie mithilfe eines TFilestreams eine Datei in den Stream eines Structured Storage schreiben. Zunächst wird der Filestream erzeugt, dann die IStream-Schnittstelle im Structured Storage und anschließend der FileStream-Inhalt mithilfe von TOleStream in das Storage kopiert (Listing 5). Listing 5 function AddDirToStorage (aStorage : IStorage; aPath: String): Boolean;Für Verzeichnisse wird lediglich ein neues Storage unterhalb des im Parameter übergeben Storage angelegt. Achten Sie darauf, dass der Name des Storage nicht den kompletten Pfad umfasst, sondern nur den letzten Verzeichnisnamen. ...Sobald das Storage gefüllt ist, können Sie es der Methode AddData übergeben. hr := aJDMaster.AddData(aRootStorage,1);Bei der Methode AddData können nicht nur die im Platform SDK definierten Imapi-Fehler (s.o.) auftauchen, sondern weitere Fehler, die beim Zugriff auf das Structured Storage entstehen. Achten Sie darauf, dass Sie alle Schnittstellen für Streams im Storage auf nil gesetzt haben (im Beispiel kümmert sich dank dem Unterprogrammaufruf von AddDirToStorage Delphis Compiler darum). Sobald Sie auch nur eine IStream-Schnittstelle vergessen freizugeben, wird der Methodenaufruf AddData mit einem Zugriffsfehler fehlschlagen, da der IStream zum Schreiben zwangsweise mit dem Flag STGM_SHARE_EXCLUSIVE geöffnet werden muss. Wenn Sie viele Dateien hinzufügen wollen, sollen Sie zudem mehrere AddData-Aufrufe verwenden, sonst bauen Sie mit dem temporären Storage noch mal eine riesige Datei auf - obwohl es ja schon die Image-Datei von Imapi gibt. Ist das Image gefüllt, müssen Sie noch den aktiven Recorder setzen, falls Sie dies noch nicht getan haben. Im Gegensatz zum voreingestellten Format, ist bei Imapi kein Recorder vorausgewählt, sodass Sie im Zweifelsfall vor dem Brennen prüfen sollten, ob der aktive Recorder noch da ist. Im schlimmsten Fall hat der Anwender gerade vor dem Brennen den USB-Brenner abgezogen. Der folgende Code verwendet eine Hilfsroutine GetFirstRecorder aus dem Beispiel, die immer den ersten Recorder der Enumeration zurückliefert: ARecorder : IDiscRecorder;Bei einigen Fehlercodes wie IMAPI_E_DISCFULL oder IMAPI_E_MEDIUM_NOTPRESENT, welche die Methode SetActiveDiscRecorder zurückliefert, wird der Recorder trotz Fehlercode korrekt ausgewählt, sodass Sie fortfahren können, sobald der Anwender eine CD eingelegt hat, ohne SetActiveDiscRecorder erneut aufrufen zu müssen. War bis dahin alles in Ordnung, können Sie die CD mit dem Methodenaufruf IDiscMaster.RecordDisc dann brennen. Mit den Parametern der Methode steuern Sie, ob Sie zunächst nur simulieren wollen und ob Sie die CD nach dem Ende des Brennvorgangs auswerfen möchten. Standardmäßig brennt Imapi die CDs im Multisession-Mode und wechselt gegebenenfalls in diesen Modus, wenn bereits eine Multisession-CD eingelegt ist. Möchten Sie in den Single-Session-Mode wechseln, rufen Sie dazu die Methode IDiscMaster.ClearFormatContent auf. Allerdings sollten Sie diese Entscheidung frühzeitig treffen, da die Methode nicht nur das Format zurücksetzt, sondern auch den Image-Stash leert. Alle Daten, die Sie vorher bereits in das Imagefile geschrieben haben, gehen mit dem Aufruf von ClearFormatContent verloren. Wie Sie gesehen haben, kommen Sie mit der Imapi-Schnittstelle relativ schnell zu einer gebrannten CD mit beliebigen Daten. Wem Imapi noch zu kompliziert ist, hat theoretisch unter Windows XP noch eine weitere Möglichkeit CDs zu brennen. Seit XP kennt die Windows-Shell eine Schnittstelle ICDBurn, die für einfache Aufgaben ausreicht. Einziger Wermutstropfen dabei ist, dass diese Schnittstelle sich bisher weder im Delphi-Source findet noch in der API-Übersetzung von Marcel Van Brakel. Den kompletten Quellcode zum Artikel finden Sie auf der Heft-CD oder im Internet unter www.uaconsulting.de/de/downloads/imapidelphi.htm. Links und Literatur [1] Marcel van Brakel -API Header Translations : members.chello.nl/m.vanbrakel2/ [2] Project Jedi: www.delphi-jedi.org/ [3] Imapi und Delphi: www.uaconsulting.de/de/downloads/downloads.htm [4] MSDN Online: msdn.microsoft.com/library/default.asp?url=/library/en-us/ devio/base/about_the_image_mastering_api.asp |
||
|