//------------------------------------------------------------------------------
// File: DMOImpl.h
//
// Desc: Classes to implement a DMO.
//
// Copyright (c) 2000-2001, Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------------------------


#ifndef _dmoimpl_h_
#define _dmoimpl_h_

#ifdef _DEBUG
#include <crtdbg.h>
#endif

//  Class to implement a DMO
//
//
//       Assumes the number of input and output streams is fixed
//       (these are template parameters)
//
//       Provides following services:
//
//          Basic parameter checking and locking
//          Fully implements :
//                 GetStreamCount
//                 SetInputType
//                 SetOutputType
//                 GetCurrentInputType
//                 GetCurrentOutputType
//
//          Checks if all types are set before streaming
//          Automatically calls AllocateStreamingResources before streaming
//              if it's not been called already
//          Prevents streaming until the types on all non-optional streams
//              have been set
//
//
//  Derived class implements the following methods :
//
/*
   HRESULT InternalGetInputStreamInfo(DWORD dwInputStreamIndex, DWORD *pdwFlags);
   HRESULT InternalGetOutputStreamInfo(DWORD dwOutputStreamIndex, DWORD *pdwFlags);
   HRESULT InternalCheckInputType(DWORD dwInputStreamIndex, const DMO_MEDIA_TYPE *pmt);
   HRESULT InternalCheckOutputType(DWORD dwOutputStreamIndex, const DMO_MEDIA_TYPE *pmt);
   HRESULT InternalGetInputType(DWORD dwInputStreamIndex, DWORD dwTypeIndex,
                            DMO_MEDIA_TYPE *pmt);
   HRESULT InternalGetOutputType(DWORD dwOutputStreamIndex, DWORD dwTypeIndex,
                            DMO_MEDIA_TYPE *pmt);
   HRESULT InternalGetInputSizeInfo(DWORD dwInputStreamIndex, DWORD *pcbSize,
                            DWORD *pcbMaxLookahead, DWORD *pcbAlignment);
   HRESULT InternalGetOutputSizeInfo(DWORD dwOutputStreamIndex, DWORD *pcbSize,
                             DWORD *pcbAlignment);
   HRESULT InternalGetInputMaxLatency(DWORD dwInputStreamIndex, REFERENCE_TIME *prtMaxLatency);
   HRESULT InternalSetInputMaxLatency(DWORD dwInputStreamIndex, REFERENCE_TIME rtMaxLatency);
   HRESULT InternalFlush();
   HRESULT InternalDiscontinuity(DWORD dwInputStreamIndex);
   HRESULT InternalAllocateStreamingResources();
   HRESULT InternalFreeStreamingResources();
   HRESULT InternalProcessInput(DWORD dwInputStreamIndex, IMediaBuffer *pBuffer,
                               DWORD dwFlags, REFERENCE_TIME rtTimestamp,
                               REFERENCE_TIME rtTimelength);
   HRESULT InternalProcessOutput(DWORD dwFlags, DWORD cOutputBufferCount,
                               DMO_OUTPUT_DATA_BUFFER *pOutputBuffers,
                           DWORD *pdwStatus);
   HRESULT InternalAcceptingInput(DWORD dwInputStreamIndex);
   void Lock();
   void Unlock();

   Notes:
       The derived class is meant to do most work to initialize streaming
       in AllocateStreamingResources rather than when types are set.

       This centralizes the work to one
       clear place based on the types set for all streams.

       The derived class implements locking.

       The derived class implements the IUnknown methods

   Usage example (1 input and 1 output) :
   class CMyDMO : public IMediaObjectImpl<CMyDmo, 1, 1>,
                  ...
*/


#define INTERNAL_CALL(_T_, _X_) \
    static_cast<_T_ *>(this)->Internal##_X_

template <class _DERIVED_, int NUMBEROFINPUTS, int NUMBEROFOUTPUTS>
class IMediaObjectImpl : public IMediaObject
{
private:
    // Member variables
    struct {
        DWORD   fTypeSet:1;
        DWORD   fIncomplete:1;
        DMO_MEDIA_TYPE CurrentMediaType;
    } m_InputInfo[NUMBEROFINPUTS], m_OutputInfo[NUMBEROFOUTPUTS];

    bool m_fTypesSet;
    bool m_fFlushed;
    bool m_fResourcesAllocated;

protected:

    //  Helpers
    bool InputTypeSet(DWORD ulInputStreamIndex) const
    {
        _ASSERTE(ulInputStreamIndex < NUMBEROFINPUTS);
        return 0 != m_InputInfo[ulInputStreamIndex].fTypeSet;
    }

    bool OutputTypeSet(DWORD ulOutputStreamIndex) const
    {
        _ASSERTE(ulOutputStreamIndex < NUMBEROFOUTPUTS);
        return 0 != m_OutputInfo[ulOutputStreamIndex].fTypeSet;
    }
    const DMO_MEDIA_TYPE *InputType(DWORD ulInputStreamIndex)
    {
        if (!InputTypeSet(ulInputStreamIndex)) {
            return NULL;
        }
        return &m_InputInfo[ulInputStreamIndex].CurrentMediaType;
    }
    const DMO_MEDIA_TYPE *OutputType(DWORD ulOutputStreamIndex)
    {
        if (!OutputTypeSet(ulOutputStreamIndex)) {
            return NULL;
        }
        return &m_OutputInfo[ulOutputStreamIndex].CurrentMediaType;
    }


    class LockIt
    {
    public:
        LockIt(_DERIVED_ *p) : m_p(p)
        {
            static_cast<_DERIVED_ *>(m_p)->Lock();
        }
        ~LockIt()
        {
            static_cast<_DERIVED_ *>(m_p)->Unlock();
        }
        _DERIVED_ *const m_p;
    };

    bool CheckTypesSet()
    {
        m_fTypesSet = false;
        DWORD dw;
        for (dw = 0; dw < NUMBEROFINPUTS; dw++) {
            if (!InputTypeSet(dw)) {
                return false;
            }
        }
        for (dw = 0; dw < NUMBEROFOUTPUTS; dw++) {
            if (!OutputTypeSet(dw)) {
                //  Check if it's optional
                DWORD dwFlags;
#ifdef _DEBUG
                dwFlags = 0xFFFFFFFF;
#endif
                INTERNAL_CALL(_DERIVED_, GetOutputStreamInfo)(dw, &dwFlags);
                _ASSERTE(0 == (dwFlags & ~(DMO_OUTPUT_STREAMF_WHOLE_SAMPLES |
                                         DMO_OUTPUT_STREAMF_SINGLE_SAMPLE_PER_BUFFER |
                                         DMO_OUTPUT_STREAMF_FIXED_SAMPLE_SIZE |
                                         DMO_OUTPUT_STREAMF_DISCARDABLE |
                                         DMO_OUTPUT_STREAMF_OPTIONAL)));
                if (!(dwFlags & DMO_OUTPUT_STREAMF_OPTIONAL)) {
                    return false;
                }
            }
        }
        m_fTypesSet = true;
        return true;
    }


    IMediaObjectImpl() :
        m_fTypesSet(false),
        m_fFlushed(true),
        m_fResourcesAllocated(false)
    {
        ZeroMemory(&m_InputInfo, sizeof(m_InputInfo));
        ZeroMemory(&m_OutputInfo, sizeof(m_OutputInfo));
    }

    virtual ~IMediaObjectImpl() {
        DWORD dwCurrentType;

        for (dwCurrentType = 0; dwCurrentType < NUMBEROFINPUTS; dwCurrentType++) {
            if(InputTypeSet(dwCurrentType)) {
                MoFreeMediaType(&m_InputInfo[dwCurrentType].CurrentMediaType);
            }
        }

        for (dwCurrentType = 0; dwCurrentType < NUMBEROFOUTPUTS; dwCurrentType++) {
            if(OutputTypeSet(dwCurrentType)) {
                MoFreeMediaType(&m_OutputInfo[dwCurrentType].CurrentMediaType);
            }
        }
    }


    // IMediaObject methods


    //
    // IMediaObject methods
    //
    STDMETHODIMP GetStreamCount(unsigned long *pulNumberOfInputStreams, unsigned long *pulNumberOfOutputStreams)
    {
        LockIt lck(static_cast<_DERIVED_ *>(this));
        if (pulNumberOfInputStreams == NULL ||
            pulNumberOfOutputStreams == NULL) {
            return E_POINTER;
        }
        *pulNumberOfInputStreams  = NUMBEROFINPUTS;
        *pulNumberOfOutputStreams = NUMBEROFOUTPUTS;
        return S_OK;
    }

    STDMETHODIMP GetInputStreamInfo(ULONG ulStreamIndex, DWORD *pdwFlags)
    {
        LockIt lck(static_cast<_DERIVED_ *>(this));
        if (ulStreamIndex >= NUMBEROFINPUTS) {
            return DMO_E_INVALIDSTREAMINDEX;
        }
        if (pdwFlags == NULL) {
            return E_POINTER;
        }
        HRESULT hr = INTERNAL_CALL(_DERIVED_, GetInputStreamInfo)(ulStreamIndex, pdwFlags);
        _ASSERTE(0 == (*pdwFlags & ~(DMO_INPUT_STREAMF_WHOLE_SAMPLES |
                                   DMO_INPUT_STREAMF_SINGLE_SAMPLE_PER_BUFFER |
                                   DMO_INPUT_STREAMF_FIXED_SAMPLE_SIZE |
                                   DMO_INPUT_STREAMF_HOLDS_BUFFERS)));
        return hr;
    }

    STDMETHODIMP GetOutputStreamInfo(ULONG ulStreamIndex, DWORD *pdwFlags)
    {
        LockIt lck(static_cast<_DERIVED_ *>(this));
        if (ulStreamIndex >= NUMBEROFOUTPUTS) {
            return DMO_E_INVALIDSTREAMINDEX;
        }
        if (pdwFlags == NULL) {
            return E_POINTER;
        }
        HRESULT hr = INTERNAL_CALL(_DERIVED_, GetOutputStreamInfo)(ulStreamIndex, pdwFlags);
        _ASSERTE(0 == (*pdwFlags & ~(DMO_OUTPUT_STREAMF_WHOLE_SAMPLES |
                                   DMO_OUTPUT_STREAMF_SINGLE_SAMPLE_PER_BUFFER |
                                   DMO_OUTPUT_STREAMF_FIXED_SAMPLE_SIZE |
                                   DMO_OUTPUT_STREAMF_DISCARDABLE |
                                   DMO_OUTPUT_STREAMF_OPTIONAL)));
        return hr;
    }

    STDMETHODIMP GetInputType(ULONG ulStreamIndex, ULONG ulTypeIndex, DMO_MEDIA_TYPE *pmt) {
        if (ulStreamIndex >= NUMBEROFINPUTS) {
            return DMO_E_INVALIDSTREAMINDEX;
        }
        LockIt lck(static_cast<_DERIVED_ *>(this));
        return INTERNAL_CALL(_DERIVED_, GetInputType)(ulStreamIndex, ulTypeIndex, pmt);
    }

    STDMETHODIMP GetOutputType(ULONG ulStreamIndex, ULONG ulTypeIndex, DMO_MEDIA_TYPE *pmt) {
        if (ulStreamIndex >= NUMBEROFOUTPUTS) {
            return DMO_E_INVALIDSTREAMINDEX;
        }
        LockIt lck(static_cast<_DERIVED_ *>(this));
        return INTERNAL_CALL(_DERIVED_, GetOutputType)(ulStreamIndex, ulTypeIndex, pmt);
    }

    STDMETHODIMP GetInputCurrentType(ULONG ulStreamIndex, DMO_MEDIA_TYPE *pmt) {
        if (ulStreamIndex >= NUMBEROFINPUTS) {
            return DMO_E_INVALIDSTREAMINDEX;
        }
        if (NULL == pmt) {
            return E_POINTER;
        }
        LockIt lck(static_cast<_DERIVED_ *>(this));
        if (InputTypeSet(ulStreamIndex))
            return MoCopyMediaType(pmt,
                                   &m_InputInfo[ulStreamIndex].CurrentMediaType);
        else
           return DMO_E_TYPE_NOT_SET;
    }

    STDMETHODIMP GetOutputCurrentType(ULONG ulStreamIndex, DMO_MEDIA_TYPE *pmt) {
        if (ulStreamIndex >= NUMBEROFOUTPUTS) {
            return DMO_E_INVALIDSTREAMINDEX;
        }
        if (NULL == pmt) {
            return E_POINTER;
        }
        LockIt lck(static_cast<_DERIVED_ *>(this));
        if (OutputTypeSet(ulStreamIndex))
            return MoCopyMediaType(pmt,
                                   &m_OutputInfo[ulStreamIndex].CurrentMediaType);
        else
           return DMO_E_TYPE_NOT_SET;
    }

    STDMETHODIMP GetInputSizeInfo(ULONG ulStreamIndex, ULONG *pulSize, ULONG *pcbMaxLookahead, ULONG *pulAlignment) {
        if (ulStreamIndex >= NUMBEROFINPUTS) {
            return DMO_E_INVALIDSTREAMINDEX;
        }
        if (NULL == pulSize || NULL == pulAlignment ||
            NULL == pcbMaxLookahead) {
            return E_POINTER;
        }
        LockIt lck(static_cast<_DERIVED_ *>(this));
        if (!InputTypeSet(ulStreamIndex)) {
           return DMO_E_TYPE_NOT_SET;
        }
        return INTERNAL_CALL(_DERIVED_, GetInputSizeInfo)(ulStreamIndex, pulSize, pcbMaxLookahead, pulAlignment);
    }

    STDMETHODIMP GetOutputSizeInfo(ULONG ulStreamIndex, ULONG *pulSize, ULONG *pulAlignment) {
        if (ulStreamIndex >= NUMBEROFOUTPUTS) {
            return DMO_E_INVALIDSTREAMINDEX;
        }
        if (NULL == pulSize || NULL == pulAlignment) {
            return E_POINTER;
        }
        LockIt lck(static_cast<_DERIVED_ *>(this));
        if (!m_fTypesSet || !OutputTypeSet(ulStreamIndex)) {
           return DMO_E_TYPE_NOT_SET;
        }
        return INTERNAL_CALL(_DERIVED_, GetOutputSizeInfo)(ulStreamIndex, pulSize, pulAlignment);
    }

    STDMETHODIMP SetInputType(ULONG ulStreamIndex, const DMO_MEDIA_TYPE *pmt, DWORD dwFlags) {
        if (ulStreamIndex >= NUMBEROFINPUTS) {
            return DMO_E_INVALIDSTREAMINDEX;
        }
        if (dwFlags & ~ (DMO_SET_TYPEF_CLEAR | DMO_SET_TYPEF_TEST_ONLY)) {
            return E_INVALIDARG;
        }

        LockIt lck(static_cast<_DERIVED_ *>(this));

        if (dwFlags & DMO_SET_TYPEF_CLEAR) {
            MoFreeMediaType(&m_InputInfo[ulStreamIndex].CurrentMediaType);
            m_InputInfo[ulStreamIndex].fTypeSet = FALSE;
            if (!CheckTypesSet()) {
                Flush();
                FreeStreamingResources();
            }
            return NOERROR;
        }
        if (NULL == pmt) {
            return E_POINTER;
        }
        HRESULT hr = INTERNAL_CALL(_DERIVED_, CheckInputType)(ulStreamIndex, pmt);
        if (FAILED(hr))
           return hr;

        if (dwFlags & DMO_SET_TYPEF_TEST_ONLY) {
           return NOERROR;
        }


        // actually set the type
        DMO_MEDIA_TYPE mtTemp;
        if (S_OK == MoCopyMediaType(&mtTemp, pmt)) {
            // Free any previous mediatype
            if (InputTypeSet(ulStreamIndex)) {
                MoFreeMediaType(&m_InputInfo[ulStreamIndex].CurrentMediaType);
            }
            m_InputInfo[ulStreamIndex].CurrentMediaType = mtTemp;
            m_InputInfo[ulStreamIndex].fTypeSet = TRUE;
            CheckTypesSet();
        } else {
            return E_OUTOFMEMORY;
        }

        return NOERROR;
    }

    STDMETHODIMP SetOutputType(ULONG ulStreamIndex, const DMO_MEDIA_TYPE *pmt, DWORD dwFlags) {
        if (ulStreamIndex >= NUMBEROFOUTPUTS) {
            return DMO_E_INVALIDSTREAMINDEX;
        }
        if (dwFlags & ~ (DMO_SET_TYPEF_CLEAR | DMO_SET_TYPEF_TEST_ONLY)) {
            return E_INVALIDARG;
        }

        LockIt lck(static_cast<_DERIVED_ *>(this));

        if (dwFlags & DMO_SET_TYPEF_CLEAR) {
            MoFreeMediaType(&m_OutputInfo[ulStreamIndex].CurrentMediaType);
            m_OutputInfo[ulStreamIndex].fTypeSet = FALSE;
            if (!CheckTypesSet()) {
                Flush();
                FreeStreamingResources();
            }
            return NOERROR;
        }
        if (NULL == pmt) {
            return E_POINTER;
        }
        HRESULT hr = INTERNAL_CALL(_DERIVED_, CheckOutputType)(ulStreamIndex, pmt);
        if (FAILED(hr)) {
           return hr;
        }

        if (dwFlags & DMO_SET_TYPEF_TEST_ONLY) {
           return NOERROR;
        }


        // actually set the type
        DMO_MEDIA_TYPE mtTemp;
        if (S_OK == MoCopyMediaType(&mtTemp, pmt)) {
            // Free any previous mediatype
            if (OutputTypeSet(ulStreamIndex)) {
                MoFreeMediaType(&m_OutputInfo[ulStreamIndex].CurrentMediaType);
            }
            m_OutputInfo[ulStreamIndex].CurrentMediaType = mtTemp;
            m_OutputInfo[ulStreamIndex].fTypeSet = TRUE;
            CheckTypesSet();
        } else {
            return E_OUTOFMEMORY;
        }

        return NOERROR;
    }

    STDMETHODIMP GetInputStatus(
        ULONG ulStreamIndex,
        DWORD *pdwStatus
    ) {
        if (ulStreamIndex >= NUMBEROFINPUTS) {
            return DMO_E_INVALIDSTREAMINDEX;
        }
        if (NULL == pdwStatus) {
            return E_POINTER;
        }
        *pdwStatus = 0;

        LockIt lck(static_cast<_DERIVED_ *>(this));

        if (!m_fTypesSet) {
            return DMO_E_TYPE_NOT_SET;
        }

        if (INTERNAL_CALL(_DERIVED_, AcceptingInput)(ulStreamIndex) == S_OK) {
           *pdwStatus |= DMO_INPUT_STATUSF_ACCEPT_DATA;
        }
        return NOERROR;
    }

    STDMETHODIMP GetInputMaxLatency(unsigned long ulStreamIndex, REFERENCE_TIME *prtLatency) {

        if (prtLatency == NULL) {
            return E_POINTER;
        }
        if (ulStreamIndex >= NUMBEROFINPUTS) {
            return DMO_E_INVALIDSTREAMINDEX;
        }

        LockIt lck(static_cast<_DERIVED_ *>(this));

        return INTERNAL_CALL(_DERIVED_, GetInputMaxLatency)(ulStreamIndex, prtLatency);
    }

    STDMETHODIMP SetInputMaxLatency(unsigned long ulStreamIndex, REFERENCE_TIME rtLatency) {
        if (ulStreamIndex >= NUMBEROFINPUTS) {
            return DMO_E_INVALIDSTREAMINDEX;
        }

        LockIt lck(static_cast<_DERIVED_ *>(this));

        return INTERNAL_CALL(_DERIVED_, SetInputMaxLatency)(ulStreamIndex, rtLatency);
    }

    STDMETHODIMP Discontinuity(ULONG ulStreamIndex) {
        if (ulStreamIndex >= NUMBEROFINPUTS) {
            return DMO_E_INVALIDSTREAMINDEX;
        }

        LockIt lck(static_cast<_DERIVED_ *>(this));

        if (!m_fTypesSet) {
            return DMO_E_TYPE_NOT_SET;
        }

        if (S_OK != INTERNAL_CALL(_DERIVED_, AcceptingInput)(ulStreamIndex)) {
            return DMO_E_NOTACCEPTING;
        }

        return INTERNAL_CALL(_DERIVED_, Discontinuity)(ulStreamIndex);
    }

    STDMETHODIMP Flush()
    {
        LockIt lck(static_cast<_DERIVED_ *>(this));

        if (!m_fTypesSet) {
            return S_OK;
        }
        if (m_fFlushed) {
            return S_OK;
        }
        HRESULT hr =  INTERNAL_CALL(_DERIVED_, Flush)();
        m_fFlushed = true;
        return hr;
    }

    STDMETHODIMP AllocateStreamingResources() {
        LockIt lck(static_cast<_DERIVED_ *>(this));
        if (!m_fTypesSet) {
            return DMO_E_TYPE_NOT_SET;
        }
        if (m_fResourcesAllocated) {
            return S_OK;
        }
        HRESULT hr = INTERNAL_CALL(_DERIVED_, AllocateStreamingResources)();
        if (SUCCEEDED(hr)) {
            m_fResourcesAllocated = true;
        }
        return hr;
    }

    STDMETHODIMP FreeStreamingResources()
    {
        LockIt lck(static_cast<_DERIVED_ *>(this));
        if (m_fResourcesAllocated) {
            m_fResourcesAllocated = false;
            INTERNAL_CALL(_DERIVED_, Flush)();
            return INTERNAL_CALL(_DERIVED_, FreeStreamingResources)();
        }
        return S_OK;
    }

    //
    // Processing methods - public entry points
    //
    STDMETHODIMP ProcessInput(
        DWORD ulStreamIndex,
        IMediaBuffer *pBuffer, // [in], must not be NULL
        DWORD dwFlags, // [in] - discontinuity, timestamp, etc.
        REFERENCE_TIME rtTimestamp, // [in], valid if flag set
        REFERENCE_TIME rtTimelength // [in], valid if flag set
    ) {
        if (!pBuffer) {
            return E_POINTER;
        }
        if (ulStreamIndex >= NUMBEROFINPUTS) {
            return DMO_E_INVALIDSTREAMINDEX;
        }
        if (dwFlags & ~(DMO_INPUT_DATA_BUFFERF_SYNCPOINT |
                        DMO_INPUT_DATA_BUFFERF_TIME |
                        DMO_INPUT_DATA_BUFFERF_TIMELENGTH)) {
            return E_INVALIDARG;
        }

        LockIt lck(static_cast<_DERIVED_ *>(this));

        //  Make sure all streams have media types set and resources are allocated
        HRESULT hr = AllocateStreamingResources();
        if (FAILED(hr)) {
            return hr;
        }
        if (INTERNAL_CALL(_DERIVED_, AcceptingInput)(ulStreamIndex) != S_OK) {
            return DMO_E_NOTACCEPTING;
        }

        m_fFlushed = false;

        return INTERNAL_CALL(_DERIVED_, ProcessInput)(
                                    ulStreamIndex,
                                    pBuffer,
                                    dwFlags,
                                    rtTimestamp,
                                    rtTimelength);
    }

    STDMETHODIMP ProcessOutput(
                    DWORD dwFlags,
                    DWORD ulOutputBufferCount,
                    DMO_OUTPUT_DATA_BUFFER *pOutputBuffers,
                    DWORD *pdwStatus)
    {
        if (pdwStatus == NULL) {
            return E_POINTER;
        }


        if (ulOutputBufferCount != NUMBEROFOUTPUTS || (dwFlags & ~DMO_PROCESS_OUTPUT_DISCARD_WHEN_NO_BUFFER)) {
           return E_INVALIDARG;
        }

        if (NUMBEROFOUTPUTS != 0 && pOutputBuffers == NULL) {
            return E_POINTER;
        }

        *pdwStatus = 0;

        LockIt lck(static_cast<_DERIVED_ *>(this));

        HRESULT hr = AllocateStreamingResources();
        if (FAILED(hr)) {
            return hr;
        }

        for (DWORD dw = 0; dw < NUMBEROFOUTPUTS; dw++) {
            pOutputBuffers[dw].dwStatus = 0;
        }

        hr = INTERNAL_CALL(_DERIVED_, ProcessOutput)(
                           dwFlags,
                           ulOutputBufferCount,
                           pOutputBuffers,
                           pdwStatus);

        // remember the DMO's incomplete status
        for (dw = 0; dw < NUMBEROFOUTPUTS; dw++) {
            if (pOutputBuffers[dw].dwStatus & DMO_OUTPUT_DATA_BUFFERF_INCOMPLETE) {
                m_OutputInfo[dw].fIncomplete = TRUE;
            } else {
                m_OutputInfo[dw].fIncomplete = FALSE;
            }
        }

        return hr;
    }

    STDMETHODIMP DMOLock(LONG lLock)
    {
        if (lLock) {
            static_cast<_DERIVED_ *>(this)->Lock();
        } else {
            static_cast<_DERIVED_ *>(this)->Unlock();
        }
        return S_OK;
    }
};

#endif // _dmoimpl_h_

