Microsoft DirectX 8.0

Logging Errors

Microsoft® DirectShow® Editing Services (DES) provides a built-in mechanism for logging errors that occur when loading, constructing, or rendering a DES project. This article presents a sample console application that loads an XML project file and attempts to render it. If an error occurs, the application prints an error message in the console window. The sample code presented in this article builds on the example given in Loading and Previewing a Project.

Note  Your application is not required to implement error logging. DES does not log errors unless you explicitly request it.

This article assumes that you understand COM client programming and the DES timeline model. In addition, you need to understand the basics of COM object programming. For information about timelines in DES, see The Timeline Model.

This article contains the following sections.

Overview of Error Logging

To give applications maximum flexibility in handling errors, DES uses a callback mechanism. Your application implements a method for logging an error. At run time, if an error occurs, DES calls the method you have provided. The method takes parameters that describe the error. What the method does with this information is up to you. (It should return as quickly as possible, however, or it might interfere with the execution of the program.)

The error logging callback method is contained in a COM interface, IAMErrorLog. Your application must implement this interface. Like all COM interfaces, IAMErrorLog inherits the IUnknown interface, so your application must implement that as well.

You have several choices for implementing these COM interfaces. You can use the Active Template Library (ATL), which provides stock implementations of the IUnknown methods. DirectShow also provides a C++ base class, CUnknown, that makes it easy to implement a COM interface. For information on using CUnknown, see How to Implement IUnknown.

The sample code in this article defines a self-contained C++ class, which implements both IUnknown and IAMErrorLog. The result is not a true COM object, because it does not support CoCreateInstance. However, this approach is adequate for the purpose of the example.

Creating an Error Logging Class

First, declare a class that will implement error logging. The class inherits the IAMErrorLog interface. It contains declarations for the three IUnknown methods, and for the single method in IAMErrorLog. The class declaration is as follows:

class CErrReporter : public IAMErrorLog
{
protected:
    long    m_lRef; // Reference count.

public:
    CErrReporter() { m_lRef = 0; }

    // IUnknown
    STDMETHOD(QueryInterface(REFIID, void**));
    STDMETHOD_(ULONG, AddRef());
    STDMETHOD_(ULONG, Release());

    // IAMErrorLog
    STDMETHOD(LogError(LONG, BSTR, LONG, HRESULT, VARIANT*));
};

The only member variable in the class is m_lRef, which holds the object's reference count.

Next, define the methods in IUnknown. For details, refer to the sample code at the end of this article. With the COM framework in place, you can now implement the IAMErrorLog interface. The next section describes how to do this.

Implementing IAMErrorLog

The IAMErrorLog interface contains a single method, LogError. The parameters to the method contain information about the error that occurred.

STDMETHODIMP LogError(
    LONG Severity,          // Reserved. Do not use.
    BSTR ErrorString,       // Description.
    LONG ErrorCode,         // Error code.
    HRESULT hresult,        // HRESULT that caused the error.
    VARIANT *pExtraInfo);   // Extra information about the error.

The error code and the error string are defined by DirectShow Editing Services. For a list of errors, see Rendering Errors.

The pExtraInfo parameter contains a pointer to a VARIANT type that holds additional information about the error. The data type and contents of the VARIANT depend on the specific error that occurred. For example, if the error was caused by an incorrect file name, the VARIANT is a string with the bad file name. Some errors do not have extra information, so pExtraInfo might be NULL. The following code shows how to test the vt member of the VARIANT, which indicates the data type, and format a message accordingly.

if( pExtraInfo )    // Report extra information, if any. 
{                           
    printf("\tExtra info: ");
    if( pExtraInfo->vt == VT_BSTR )      // Extra info is a BSTR.
    {
        char szExtra[256];
        WideCharToMultiByte(CP_ACP, 0, pExtraInfo->bstrVal, -1, szExtra, 256, 0, 0);
        printf("%s\n", szExtra);
    } 
    else if( pExtraInfo->vt == VT_I4 )   // Extra info is an integer.
        printf("%d\n", pExtraInfo->lVal);

    else if( pExtraInfo->vt == VT_R8 )   // Extra info is floating-point.
        printf("%f\n", pExtraInfo->dblVal);
}

Note  Do not free the VARIANT pointed to by pExtraInfo. Also, the VARIANT becomes invalid after the method returns, so do not reference it later.

Setting the Error Log

After you implement the error logging class, create a new instance of the class. Then, give DES a pointer to it by calling the IAMSetErrorLog::put_ErrorLog method on the timeline. Query the timeline for the IAMSetErrorLog interface. To ensure that all errors are logged, you should call this method before you load, save, or render the timeline.

IAMSetErrorLog  *pSetLog = NULL;
IAMErrorLog     *pLog = new CErrReporter();

pTL->QueryInterface(IID_IAMSetErrorLog, (void **)&pSetLog);
pSetLog->put_ErrorLog(pLog);
pSetLog->Release();

Error logging has no effect on the return values that you receive when you call methods in your application. Error logging complements but does not replace the usual error handling techniques. To create a robust application, always check HRESULT values.

Sample Code

The following sample code presents a complete console application that loads and previews an XML project file, using the error logging class described in this article. The name of the project file is hard-coded into the application.

To make the code shorter, the console application uses ATL smart pointers, which removes the need to call QueryInterface and Release. If you prefer, you can modify the sample application in Loading and Previewing a Project. Simply add the code shown in the previous section.

#include <atlbase.h>
#include <dshow.h>
#include <qedit.h>
#include <stdio.h>

// Error logging class
class CErrReporter : public IAMErrorLog
{
protected:
    long    m_lRef; // Reference count.

public:
    CErrReporter() { m_lRef = 0; }

    // IUnknown
    STDMETHOD(QueryInterface(REFIID, void**));
    STDMETHOD_(ULONG, AddRef());
    STDMETHOD_(ULONG, Release());

    // IAMErrorLog
    STDMETHOD(LogError(LONG, BSTR, LONG, HRESULT, VARIANT*));
};

STDMETHODIMP CErrReporter::QueryInterface(REFIID riid, void **ppv)
{
    if (ppv == NULL)
        return E_POINTER;
    *ppv = NULL;

    if (riid == IID_IUnknown)
        *ppv = (IUnknown*)this;
    else if (riid == IID_IAMErrorLog)
        *ppv = (IAMErrorLog*)this;
        
    if (*ppv) 
    {
        AddRef();
        return S_OK;
    }
    return E_NOINTERFACE;
}

STDMETHODIMP_(ULONG) CErrReporter::AddRef()
{
    return InterlockedIncrement(&m_lRef);
}

STDMETHODIMP_(ULONG) CErrReporter::Release()
{
    if (InterlockedDecrement(&m_lRef) == 0)
    {
        delete this;
        return 0;
    }
    return m_lRef;
}

STDMETHODIMP CErrReporter::LogError(
        LONG Severity, 
        BSTR ErrorString,
        LONG ErrorCode, 
        HRESULT hresult, 
        VARIANT *pExtraInfo)
{
    char szError[256];
    WideCharToMultiByte(CP_ACP, 0, ErrorString, -1, szError, 256, 0, 0);
    printf("Error %d: %s\n", ErrorCode, szError);
    
    if( pExtraInfo )    // Report extra information, if any. 
    {                           
        printf("\tExtra info: ");
        if( pExtraInfo->vt == VT_BSTR )      // Extra info is a BSTR.
        {
            char szExtra[256];
            WideCharToMultiByte(CP_ACP, 0, pExtraInfo->bstrVal, -1, szExtra, 256, 0, 0);
            printf("%s\n", szExtra);
        } 
        else if( pExtraInfo->vt == VT_I4 )   // Extra info is an integer.
            printf("%d\n", pExtraInfo->lVal);

        else if( pExtraInfo->vt == VT_R8 )   // Extra info is floating-point.
            printf("%f\n", pExtraInfo->dblVal);
    }
    return hresult;
};

void __cdecl main(void)
{
    CoInitialize(NULL);
    {
    HRESULT hr;
    CComPtr< IAMTimeline >   pTL;
    CComPtr< IRenderEngine > pRenderEngine; 
    CComPtr< IXml2Dex >      pXML; 
    CComPtr< IGraphBuilder > pGraph;

    hr = CoCreateInstance(CLSID_AMTimeline, NULL, CLSCTX_INPROC_SERVER, 
                IID_IAMTimeline, (void**) &pTL);

    hr = CoCreateInstance(CLSID_Xml2Dex, NULL, CLSCTX_INPROC_SERVER, 
                IID_IXml2Dex, (void**) &pXML);

    hr = CoCreateInstance(CLSID_RenderEngine, NULL, CLSCTX_INPROC_SERVER,
                IID_IRenderEngine, (void**) &pRenderEngine);

    // Set the error log.
    CComQIPtr<IAMSetErrorLog, &IID_IAMSetErrorLog> pSetLog(pTL);
    if (pSetLog)
    {
        IAMErrorLog *pLog = new CErrReporter;    
        pSetLog->put_ErrorLog(pLog);
    }
   
    // Load and preview the project.
    hr = pXML->ReadXMLFile(pTL, L"C:\\Example.xtl"); 
    if (SUCCEEDED(hr))
    {
        hr = pRenderEngine->SetTimelineObject(pTL);
        hr = pRenderEngine->ConnectFrontEnd( );
        hr = pRenderEngine->RenderOutputPins( );
        hr = pRenderEngine->GetFilterGraph(&pGraph);

        CComQIPtr<IMediaControl, &IID_IMediaControl> pControl(pGraph);
        CComQIPtr<IMediaEvent, &IID_IMediaEvent> pEvent(pGraph);
        pControl->Run();

        long evCode;
        hr = pEvent->WaitForCompletion(INFINITE, &evCode);
        pControl->Stop();
    }
    // Clean up.
    pRenderEngine->ScrapIt();
    }
    CoUninitialize();
}