Loading a File


Loading a File

So, how do you use the Loader to load files? As we saw in the HelloWorld example, you must first use the COM CoCreateInstance() call to create a Loader.

CoCreateInstance(CLSID_DirectMusicLoader, NULL,
       CLSCTX_INPROC, IID_IDirectMusicLoader8,
       (void**)&pLoader);
 

This creates the Loader and gets it ready for business.

Remember that you will need to call Release() on the Loader when you are done using it, which should be after all file I/O is completed for your application.

Note 

Because the Loader caches files for efficiency, it is very important that you keep the same Loader around. I have seen situations where a clever programmer got rid of one pesky variable by creating a Loader every time a file needed to be loaded and then released it. This solution worked, but memory usage skyrocketed as each Loader reloaded the same styles, waves, and DLS Collections instead of sharing them. In addition, file I/O and download times took longer for the same reason.

Once you have an instance of a Loader, you need to tell it where to look for your file. Call the SetSearchDirectory() method to tell the Loader where you want it to search for referenced files. You might also use SetObject() if there are specific referenced files that you want it to know about. Then, load the file, using either the LoadObjectFromFile() or GetObject() methods.

Let's look at each of these methods in detail.

SetSearchDirectory()

SetSearchDirectory() tells the Loader which directory to go to when searching for a file that is referenced by the file currently being loaded.

HRESULT SetSearchDirectory(
  REFGUID rguidClass,
  WCHAR* pwszPath,
  BOOL fClear
);
 

  • REFGUID rguidClass: This is the class ID for the type of file (a REFGUID is a pointer to a GUID or, in this case, CLSID). You can set the Loader to look in different directories for different types of files. For example, to set the search directory for DLS files, this is set to CLSID_DirectMusicCollection. To set the search directory for all types, use GUID_DirectMusicAllTypes.

  • WCHAR *pwszPath: The Loader will look in this directory path for files. DirectMusic uses Unicode characters for all naming and filenames, hence the 16-bit WCHAR instead of ASCII char.

  • BOOL fClear: This parameter indicates whether SetSearchDirectory() should flush the current directory of all references. The fClear option guards against files with the same name in different directories. SetSearchDirectory() flushes the files in the old directory from the Loader if this flag is set. However, if an object is already loaded, the fClear option does not remove it from the cache.

LoadObjectFromFile()

You can use the Loader to read a file in two ways: GetObject() and LoadObjectFromFile(). LoadObjectFromFile() is the more convenient approach. GetObject() has all kinds of clever options for loading from files, streams, or memory. However, LoadObjectFromFile() provides a very straightforward way to read DirectX Audio objects directly from files.

HRESULT LoadObjectFromFile(
  REFGUID rguidClassID,
  REFIID iidInterfaceID,
  WCHAR *pwzFilePath,
  void ** ppObject
);
 

  • REFGUID rguidClassID: This is the class ID for the type of object to load. The Loader does not know anything about how to load a particular type of file. Instead, it calls COM to create an instance of the object, which then does the actual file reading and, when finished, returns the object to the caller. For example, to load a Segment, this parameter is CLSID_DirectMusicSegment.

    Note 

    GUID_DirectMusicAllTypes is not valid for LoadObjectFromFile() because it does not define a specific object type.

  • REFIID iidInterfaceID: You must also provide an interface identifier. An interface of the requested type returns in ppObject. For example, when loading a Segment, you can request the standard Segment interface IDirectMusicSegment8 with IID_IDirectMusicSegment8.

  • WCHAR *pwzFilePath: The file path provides the file name and can be a complete path or one relative to the search directory as set with SetSearchDirectory(). The Loader first assumes that the file name is a complete path and tries to load from that absolute address. If that fails, it concatenates the file name onto the current search directory and tries to look there.

  • void ** ppObject: The loaded object returns in ppObject, which is a pointer to the variable that receives the returned object interface.

GetObject()

While LoadObjectFromFile() does the job 90 percent of the time, there are scenarios where a little more flexibility is needed.

  • The Loader can identify every file with alternate information, including a name, version, date stamp, unique GUID, and, of course, file name. The application may need to use one or more of these other variables to search for the file, especially if it is already cached. LoadObjectFromFile() can only use the file name.

  • The data might be stored in a memory buffer or stream instead of a file. LoadObjectFromFile() can only load from a file.

GetObject() provides a way to read from alternate sources. It does so by using the DMUS_OBJECTDESC structure, which allows for a very thorough description of the object.

typedef struct _DMUS_OBJECTDESC {
  DWORD   dwSize;          // Size of this, should it grow in later versions.
  DWORD   dwValidData;     // Flags indicating which fields have valid data.
  GUID    guidObject;      // Every object can have a unique ID.
                           // This is saved in the file.
  GUID    guidClass;       // Every object must belong to a predefined class.
  FILETIME  ftDate;        // The date when the object was created.
  DMUS_VERSION  vVersion;  // Major and minor version info.
  WCHAR   wszName[DMUS_MAX_NAME];           // Friendly name of object.
  WCHAR   wszCategory[DMUS_MAX_CATEGORY];   // Optional category name.
  WCHAR   wszFileName[DMUS_MAX_FILENAME];   // The file name, optionally with path.
  LONGLONG  llMemLength;   // If memory is used instead of a file, sets length.
  LPBYTE    pbMemData;     // Pointer to memory data.
  IStream   *pStream       // Or, stream to read data from.
} DMUS_OBJECTDESC, *LPDMUS_OBJECTDESC;
 

Flag bits in dwValidData indicate which fields are valid. For example, the flag DMUS_OBJ_NAME means that the string in wszName is a valid name for the object. Conversely, a cleared bit in dwValidData indicates that the corresponding data field is empty. The fields that are most important for tracking resources are wszFileName and guidObject. These help identify and validate the correct file. The GUID is guaranteed to be unique but must be authored into the files, typically via DirectMusic Producer.

In addition to identification fields, the caller can use pbMemData and pStream to present data in a memory or stream format. We cover an example of loading a Segment from memory later in this chapter (see the "Loading from a Resource" section).

HRESULT GetObject(
  LPDMUS_OBJECTDESC pDesc,
  REFIID riid,
  LPVOID FAR * ppObject
);
 

  • LPDMUS_OBJECTDESC pDesc: This is a pointer to a DMUS_OBJECTDESC structure that describes the object.

    Note 

    The DMUS_OBJECTDESC structure is quite large because of all the Unicode strings embedded within it. If you are managing many structures that have DMUS_OBJECTDESC embedded within them, be aware that it eats up 848 bytes for each instance.

  • REFIID riid: Like LoadObjectFromFile() or even CoCreateInstance(), you must provide the interface ID for the interface that you intend to get back. For example, to get back a Segment interface, pass in IID_DirectMusicSegment8.

  • void **ppObject: The loaded object returns in ppObject, which is a pointer to the variable that receives the returned object interface.

SetObject()

Sometimes, you need to point the Loader at a file source before another object references it. This could be because the file path is broken or, more likely, the reference object loads from memory or an IStream. Therefore, you need to point the Loader directly at it using SetObject(). This is almost identical to GetObject(), except it does not actually load anything. It just tells the Loader where the object is, and the Loader pulls it in later when needed.

HRESULT SetObject(
  LPDMUS_OBJECTDESC pDesc
);
 

  • LPDMUS_OBJECTDESC pDesc: This is a pointer to a DMUS_OBJECTDESC structure that describes the object. In particular, the location of the object, be it a file path (wszFileName), memory pointer (pbMemData), or IStream (pStream), must be filled in and the appropriate flags in dwValidData must be set. SetObject() will use that to parse the file header to retrieve additional information, including the name and GUID.