|
|
|||||||||||||||||||||||
|
Image Mastering API (IMAPI) Library for VB
IMAPI 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: ![]() MS Disc Master Object
CD Burning and IMAPI Interfaces 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: ![]() Main VBIMAPI Library Objects. 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.
|
||||||||||||||||||||||
|
|
|||||||||||||||||||||||