|
|||||||||||||||||||||||
|
Image Mastering API (IMAPI) Library for VBIMAPI is provided with Windows XP and above to provide full control over the creation of audio and data discs. This sample provides a wrapper around the API allowing it to be used from VB. About IMAPIThe Image Mastering API allows an application to stage and burn a simple audio or data image to CD-R and CD-RW devices. All XP and above systems come with the Adaptec implementation of IMAPI, which is controlled through the MSDiscMasterObj COM object. In theory other implementations of the API could be made available by vendors but I have not seen any details of any other implementations. The API interfaces are briefly described in the diagram and table below:
Although IMAPI is implemented using COM, it is not implemented in a VB-friendly way. The DLLs containing the implementations do not have type libraries, so you cannot add a reference to them in Visual Basic; and even if they did they use some interfaces which cause a lot of problems to VB apps, not least IPropertyStorage and the use of enumerations with structures as return values. Not just that, but the interface is not even defined using IDL or ODL; it is instead provided in a nasty MIDL generated file making it usable (just) from C and C++ applications. The first step in making this usable from VB is to define a Type Library for the interfaces. I'll cover this in the next section. Implementing an IMAPI Type Library for VBThere's a fair amount of code involved in implementing a type library like this. To explain how it works we'll take the example of the IDiscMaster interface. First, here's an excerpt of what you get from the Platform SDK, which includes the declares in imapi.h: EXTERN_C const IID IID_IDiscMaster; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("520CCA62-51A5-11D3-9144-00104BA11C5E") IDiscMaster : public IUnknown { public: virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Open( void) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE EnumDiscMasterFormats( /* [out] */ IEnumDiscMasterFormats **ppEnum) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GetActiveDiscMasterFormat( /* [out] */ LPIID lpiid) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE SetActiveDiscMasterFormat( /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppUnk) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE EnumDiscRecorders( /* [out] */ IEnumDiscRecorders **ppEnum) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE GetActiveDiscRecorder( /* [out] */ IDiscRecorder **ppRecorder) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE SetActiveDiscRecorder( /* [in] */ IDiscRecorder *pRecorder) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE ClearFormatContent( void) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE ProgressAdvise( /* [in] */ IDiscMasterProgressEvents *pEvents, /* [retval][out] */ UINT_PTR *pvCookie) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE ProgressUnadvise( /* [in] */ UINT_PTR vCookie) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE RecordDisc( /* [in] */ boolean bSimulate, /* [in] */ boolean bEjectAfterBurn) = 0; virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Close( void) = 0; }; Converting this into a type library which you can use from VB requires a few steps:
Performing the first step can be done by looking at the source code for another type library, or using the OLE/COM Object Viewer (OLEView.exe, provided with the Platform SDK) to generate a type library from a DLL you create in Visual Basic with similar method declarations. The second step means you need to also implement the IEnumDiscMasterFormats, IEnumDiscRecorders, IDiscRecorder and IDiscMasterProgressEvents interfaces, as well as providing a UUID type for use in the REFIID and LPIID parameters. Note that some of the interfaces have an out parameter using an interface with the **pp[Type] parameter description. This means that the parameter will be set to a new instance of a COM object. In these cases you need to be able to call AddRef and Release on the object, and so you also need an IUnknown implementation. The last step is the most tricky and needs some experience with VB Type Libraries, or some judicious copying of other samples. Downloading the type library source code from the site as well as from Edanmo's VB Page should help when doing this. In this case the changes are:
The final ODL for this interface then becomes: // -------------------------------------------------------- // IDiscMaster // -------------------------------------------------------- [ odl, uuid(520CCA62-51A5-11D3-9144-00104BA11C5E), helpstring("Visual Basic version of IDiscMaster interface") ] interface IVBDiscMaster : IUnknown { [helpstring("Opens an IMAPI object")] HRESULT _stdcall Open(); [helpstring("Retrieves a format enumerator")] HRESULT _stdcall EnumDiscMasterFormats( [out] IVBEnumDiscMasterFormats **ppEnum); [helpstring("Retrieves the currently selected recorder format")] HRESULT _stdcall GetActiveDiscMasterFormat( [out] UUID *lpiid); [helpstring("Sets a new active recorder format")] HRESULT _stdcall SetActiveDiscMasterFormat( [in] UUID *riid, [out] stdole.IUnknown **ppUnk); [helpstring("Retrieves a recorder enumerator")] HRESULT _stdcall EnumDiscRecorders( [out] IVBEnumDiscRecorders **ppEnum); [helpstring("Gets the active disc recorder")] HRESULT _stdcall GetActiveDiscRecorder( [out] IVBDiscRecorder **ppRecorder); [helpstring("Sets the active disc recorder")] HRESULT _stdcall SetActiveDiscRecorder( [in] IVBDiscRecorder *pRecorder); [helpstring("Clears the contents of an unburnt image")] HRESULT _stdcall ClearFormatContent(); [helpstring("Registers for progress notifications")] HRESULT _stdcall ProgressAdvise( [in] IVBDiscMasterProgressEvents *pEvents, [out] long *pvCookie); [helpstring("Cancels progress notifications")] HRESULT _stdcall ProgressUnadvise( [in] long vCookie); [helpstring("Burns the staged image to the active recorder")] HRESULT _stdcall RecordDisc( [in] long bSimulate, [in] long bEjectAfterBurn); [helpstring("Closes the interface")] HRESULT _stdcall Close(); } Note that I didn't understand the [reval] attribute when I started doing this, but in the case of ProgressAdvise method making the last parameter [out, retval] would have meant the method would return the cookie, rather than requiring a ByRef As Long parameter to be passed in. Once this process has been completed for all the required interfaces, then the Type Library can be built using the MkTypLib.exe command line utility provided with the Platform SDK. Wrapping the Type LibraryIn theory, the type library can be used directly in an application. However, in practice that turns out to be somewhat awkward. A case in point is reading the list of properties associated with a recorder (which include things like the drive speed). Here's the code needed to enumerate all the properties for an IPropertyStorage instance: Private m_props As IPropertyStorage Private m_colProps As Collection Sub GetProperties(props As IPropertyStorage) Set m_colProps = New Collection Set m_props = props ' Nasty IPropertyStorage Dim hR As Long Dim fetched As Long Dim enumProps As IEnumSTATPROPSTG Dim propStg As STATPROPSTG Dim propSpecifier As PROPSPEC Dim sName As String Dim lSize As Long Dim Value As Variant Dim cProp As cProperty Dim lErr As Long ' Get the property enumerator: Set enumProps = m_props.Enum ' Add a reference to it: enumProps.AddRef Do ' Get a property from the enumerator: hR = enumProps.Next(1, propStg, fetched) If Not (FAILED(hR)) And (fetched > 0) Then ' Get the value of the property propSpecifier.ID_or_LPWSTR = propStg.propid propSpecifier.ulKind = PRSPEC_PROPID props.ReadMultiple 1, propSpecifier, Value ' Place the id, name and value of the property into ' a VB class (note Value may be of unsupported type, ' in which case trying to access it throws an error): Set cProp = New cProperty On Error Resume Next cProp.fInit propStg.propid, lpwstrPtrToString(propStg.lpwstrName), Value lErr = err.Number On Error GoTo 0 If (lErr = 0) Then m_colProps.Add cProp End If ' Free string returned by the property read (part of ' the IEnumSTATPROPSTG contract): CoTaskMemFree propStg.lpwstrName End If Loop While Not (FAILED(hR)) And fetched > 0 ' Release the reference to the property enumerator ' and ensure VB does not try to release it again enumProps.Release CopyMemory enumProps, 0&, 4 End Sub Private Function lpwstrPtrToString(ByVal lpwstrPtr As Long) As String Dim lSize As Long If Not (lpwstrPtr = 0) Then lSize = lstrlenW(ByVal lpwstrPtr) If (lSize > 0) Then ReDim b(0 To (lSize * 2) - 1) As Byte CopyMemory b(0), ByVal lpwstrPtr, lSize * 2 lpwstrPtrToString = b End If End If End Function As you see, the use of VB-hostile COM structures means that the code becomes fairly difficult to read and use; the code is basically low-level C++ but without the useful bits like smart pointers! For this reason, the vbalIMAPI Library was created as a wrapper around the API. This allows you to use the IMAPI functions without quite so much pain. Using the vbalIMAPI DLLTo use the vbalIMAPI library you will need a reference both to vbalIMAPI.DLL and to the VB CD Mastering API Type Library (IVBIMAPI.tlb). Note the Type Library is only required at design-time. The main objects provided by this library are shown in the diagram below: The next five sections describe the main parts of the API. The property collections are not covered here, but should be fairly self explanatory as they are simply a collection of cProperty objects; these objects have an ID and an associated Name and a variant Value. 1. cDiscMasterThe cDiscMaster class is the only directly instantiable class in the library and controls access to all of the other functions. The methods are as follows:
The class also raises these events:
2. cDiscRecordersThis class provides access to the collection of recorders connected to the system.
3. cDiscRecorderThis class provides information about a recorder attached to the system and allows a recorder to be made the active recorder for disc mastering. It also provides functions to query the disc media in the drive and to erase CD-RW media.
4. cRedbookDiscMasterThis class is allows a Redbook (audio) CD image to be constructed in the stash, which can be subsequently burnt using the BurnCD method of the cDiscMaster object. The class expects audio data to be presented as raw Wave data in the CD audio format, which is 16-bit stereo with 44,100 samples/second. The data should be arranged as alternate left/right signed 16-bit sample pairs.
5. cJolietDiscMasterThis class is allows a Joliet (data) CD image to be constructed in the stash, which can be subsequently burnt using the BurnCD method of the cDiscMaster object. The class requires that all files to be added are added using COM IStorage objects, which makes this class particularly hard to use from VB.
Sample ApplicationThe sample application provided with the downloads enumerates recorders on the system and displays information about the recorder, media and supported buring formats. This exercises most of the classes in the sample other than the Redbook and Joliet stash classes. To run the sample, you will need the following registered on your system:
Any application which wants to use the library requires an instance of the cDiscMaster interface. The code to set up and clear up this object is shown below. Note that the ClearUp method tends to take a little while, so you might want to provide the user with some feedback whilst it's happening: Private m_cDiscMaster As cDiscMaster Private Sub Form_Load() Set m_cDiscMaster = New cDiscMaster m_cDiscMaster.Initialise End Sub Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer) Screen.MousePointer = vbHourglass m_cDiscMaster.ClearUp Screen.MousePointer = vbDefault End Sub The next thing is to enumerate through the recorders on the system and show the basic recorder information: Private Sub ShowRecorders() Dim cRecorders As cDiscRecorders Dim cRecorder As cDiscRecorder Dim iRecorder As Long Set cRecorders = m_cDiscMaster.Recorders For iRecorder = 1 To cRecorders.Count Set cRecorder = cRecorders(iRecorder) With cRecorder Debug.Print "PnPID=" & .PnPID Debug.Print "VendorID=" & .VendorID Debug.Print "ProductID=" & .ProductID Debug.Print "RevisionID=" & .RevisionID ' ... More to be added here End With Next iRecorder End Sub If the recorder has a disc in it, then we can show the media information. In order to gain access to the media information, you must open the recorder exclusively; as far as I know this is the only time you should do this and it is wise to close the recorder again as soon as possible: Dim cMedia As cMediaInfo .OpenExclusive Set cMedia = cRecorder.MediaInfo Debug.Print "Media:" If cMedia.MediaPresent Then Debug.Print " Media Type=" & cMedia.MediaType Debug.Print " Media Flags=" & cMedia.MediaFlags Debug.Print " Sessions=" & cMedia.Sessions Debug.Print " LastTrack=" & cMedia.LastTrack Debug.Print " Start=" & cMedia.StartAddress Debug.Print " Next=" & cMedia.LastWritable Debug.Print " Free=" & cMedia.FreeBlocks Else Debug.Print " No Media in drive" End If .CloseExclusive Finally, we can display the recorder properties and supported burning formats (although it is very unlikely you'll have a recorder that doesn't support audio and data): Dim iProperty As Long Debug.Print "Properties:" With .Properties For iProperty = 1 To .Count With .Property(iProperty) Debug.Print " " & .Name & " = " & .Value End With Next iProperty End With Debug.Print "Recording Formats:" If (.SupportsRedbook) Then Debug.Print " Redbook (audio)" End If If (.SupportsJoliet) Then Debug.Print " Joliet (data)" End If That's pretty much it for this demo. Other articles in this section show more sophisticated uses of the library to actually burn CDs. ConclusionThis article provides a wrapper allowing the Image Mastering API to be used from VB. The wrapper is provided because although IMAPI is a COM interface, it is pretty hostile to use directly from VB and requires a fairly low-level approach to COM programming to make it work.
|
||||||||||||||||||||||
|