/*******************************************************************************
// Copyright Microsoft Corporation. All Rights Reserved. 
* SPHelper.h *
*------------*
*   Description:
*       This is the header file for core helper functions implementation.
*-------------------------------------------------------------------------------
*******************************************************************************/
#ifndef SPHelper_h
#define SPHelper_h

#ifndef _INC_MALLOC
#include <malloc.h>
#endif


#ifndef __sapi_h__
#include <sapi.h>
#endif

#ifndef __sapiddk_h__
#include <sapiddk.h>
#endif

#ifndef SPError_h
#include <SPError.h>
#endif

#ifndef _INC_LIMITS
#include <limits.h>
#endif

#ifndef _INC_MMSYSTEM
#include <mmsystem.h>
#endif

#ifndef __comcat_h__
#include <comcat.h>
#endif

#ifndef _INC_MMREG
#include <mmreg.h>
#endif

#ifndef __ATLBASE_H__
#include <atlbase.h>
#endif

#include <wchar.h>
#include <tchar.h>

#include <strsafe.h>
#include <intsafe.h>

#ifndef _PREFAST_ 
#pragma warning(disable:4068) 
#endif

#ifndef REG_MUI_STRING_TRUNCATE
#define REG_MUI_STRING_TRUNCATE     0x00000001
#endif

//=== Constants ==============================================================
#define sp_countof(x) ((sizeof(x) / sizeof(*(x))))

/*** CSpDynamicString helper class
*
*/
class CSpDynamicString 
{
public:

    WCHAR *     m_psz;
    CSpDynamicString()
    {
        m_psz = NULL;
    }
    CSpDynamicString(ULONG cchReserve)
    {
        ULONGLONG ullAllocSize = (ULONGLONG) cchReserve * sizeof(WCHAR);
        if ((ullAllocSize > cchReserve) && ((ULONG) ullAllocSize == ullAllocSize))
        {
            m_psz = (WCHAR *)::CoTaskMemAlloc((ULONG)ullAllocSize);
        }
    }
    WCHAR * operator=(const CSpDynamicString& src)
    {
        if(this != &src)
        {
            _ASSERT((m_psz == NULL) || (m_psz != src.m_psz));
            ::CoTaskMemFree(m_psz);
            m_psz = src.Copy();
        }
        return m_psz;
    }
    WCHAR * operator=(const WCHAR * pSrc)
    {
        if(pSrc != m_psz)
        {
            Clear();
            if (pSrc)
            {
                size_t cbNeeded = (wcslen(pSrc) + 1) * sizeof(WCHAR);
                
                if (cbNeeded == (ULONG) cbNeeded)
                {
                    m_psz = (WCHAR *)::CoTaskMemAlloc(cbNeeded);
                    if (m_psz == NULL)
                    {
                        SetLastError(ERROR_OUTOFMEMORY);
                    }
                    else
                    {
                        memcpy(m_psz, pSrc, cbNeeded);
                    }
                }
            }
        }
        return m_psz;
    }

    WCHAR * operator=(const char * pSrc)
    {
        Clear();
        if (pSrc)
        {
            size_t cbNeeded = (::lstrlenA(pSrc) + 1) * sizeof(WCHAR);
            if ((ULONG) cbNeeded == cbNeeded)
            {
                m_psz = (WCHAR *)::CoTaskMemAlloc(cbNeeded);
                if (m_psz == NULL)
                {
                    SetLastError(ERROR_OUTOFMEMORY);
                }
                else
                {
                    ::MultiByteToWideChar(CP_ACP, 0, pSrc, -1, m_psz, (ULONG) cbNeeded/sizeof(WCHAR));
                }
            }
        }
        return m_psz;
    }

    WCHAR * operator=(REFGUID rguid)
    {
        Clear();
        ::StringFromCLSID(rguid, &m_psz);
        return m_psz;
    }


    /*explicit*/ CSpDynamicString(const WCHAR * pSrc)
    {
        m_psz = NULL;
        operator=(pSrc);
    }
    /*explicit*/ CSpDynamicString(const char * pSrc)
    {
        m_psz = NULL;
        operator=(pSrc);
    }
    /*explicit*/ CSpDynamicString(const CSpDynamicString& src)
    {
        m_psz = src.Copy();
    }
    /*explicit*/ CSpDynamicString(REFGUID rguid)
    {
        ::StringFromCLSID(rguid, &m_psz);
    }


    ~CSpDynamicString()
    {
        ::CoTaskMemFree(m_psz);
    }
    unsigned int Length() const
    {
        if (m_psz == NULL)
            return 0;
        
        size_t cch = wcslen(m_psz);

        if ((unsigned int) cch != cch)
        {
            _ASSERT((unsigned int) cch == cch);    // Too long
            SetLastError(ERROR_ARITHMETIC_OVERFLOW);
            return MAXDWORD;
        }

        return (unsigned int) cch;
    }

    operator WCHAR * () const
    {
        return m_psz;
    }
    //The assert on operator& usually indicates a bug.  If this is really
    //what is needed, however, take the address of the m_psz member explicitly.
    WCHAR ** operator&()
    {
        _ASSERT(m_psz == NULL);
        return &m_psz;
    }

    // Versions of Append that return HRESULT
    HRESULT AppendHR(const WCHAR * pszSrc, const ULONG lenSrc)
    {
        HRESULT hr = S_OK;
        if (pszSrc && (lenSrc || !m_psz))
        {
            ULONG lenMe = Length();
            ULONGLONG ullcbNew = ((ULONGLONG) lenMe + lenSrc + 1) * sizeof(WCHAR);

            if (ullcbNew > ((ULONGLONG)lenMe + lenSrc + 1) && ((ULONG) ullcbNew == ullcbNew))
            {
                WCHAR *pszNew = (WCHAR *)::CoTaskMemAlloc((ULONG)ullcbNew);
                if (pszNew)
                {
                    if (m_psz)  // Could append to an empty string so check...
                    {
                        if (lenMe)
                        {
                            memcpy(pszNew, m_psz, lenMe * sizeof(WCHAR));
                        }
                        ::CoTaskMemFree(m_psz);
                    }
                    memcpy(pszNew + lenMe, pszSrc, lenSrc * sizeof(WCHAR));
                    *(pszNew + lenMe + lenSrc) = L'\0';
                    m_psz = pszNew;
                }
                else
                {
                    hr = E_OUTOFMEMORY;
                }
            }
            else
            {
                hr = E_INVALIDARG;
            }
        }
        return hr;
    }

    HRESULT AppendHR(const WCHAR * pszSrc)
    {
        HRESULT hr = S_OK;
        if (pszSrc)
        {
            size_t lenSrc = wcslen(pszSrc);
            if ((ULONG) lenSrc != lenSrc)
                return E_OUTOFMEMORY;
            
            hr = AppendHR(pszSrc, (ULONG) lenSrc);
        }
        return hr;
    }

    // Original versions of Append that do not return HRESULTs
    WCHAR * Append(const WCHAR * pszSrc)
    {
        if(pszSrc && FAILED(AppendHR(pszSrc)))
        {
            _ASSERT(FALSE);
        }
        return m_psz;
    }

    WCHAR * Append(const WCHAR * pszSrc, const ULONG lenSrc)
    {
        if(FAILED(AppendHR(pszSrc, lenSrc)))
        {
            _ASSERT(FALSE);
        }
        return m_psz;
    }

    // Version of Append2 that returns HRESULT
    HRESULT Append2HR(const WCHAR * pszSrc1, const WCHAR * pszSrc2)
    {
        HRESULT hr = S_OK;
        size_t lenSrc1 = pszSrc1 ? wcslen(pszSrc1) : 0;
        size_t lenSrc2 = pszSrc2 ? wcslen(pszSrc2) : 0;
    
        if (lenSrc1 || lenSrc2 || !m_psz)
        {
            ULONG lenMe = Length();
            size_t cbNew = (lenMe + lenSrc1 + lenSrc2 + 1) * sizeof(WCHAR);
            if ((ULONG) cbNew != cbNew)
                return E_OUTOFMEMORY;
            
            WCHAR *pszNew = (WCHAR *)::CoTaskMemAlloc(cbNew);
            if (pszNew)
            {
                if (m_psz)  // Could append to an empty string so check...
                {
                    if (lenMe)
                    {
                        memcpy(pszNew, m_psz, lenMe * sizeof(WCHAR));
                    }
                    ::CoTaskMemFree(m_psz);
                }
                // In both of these cases, we copy the trailing NULL so that we're sure it gets
                // there (if lenSrc2 is 0 then we better copy it from pszSrc1).
                if (lenSrc1)
                {
                    memcpy(pszNew + lenMe, pszSrc1, (lenSrc1 + 1) * sizeof(WCHAR));
                }
                if (lenSrc2)
                {
                    memcpy(pszNew + lenMe + lenSrc1, pszSrc2, (lenSrc2 + 1) * sizeof(WCHAR));
                }
                m_psz = pszNew;
            }
            else
            {
                hr = E_OUTOFMEMORY;
            }
        }

        return hr;
    }

    // Original version of Append2 that does not return HRESULT
    WCHAR * Append2(const WCHAR * pszSrc1, const WCHAR * pszSrc2)
    {
        if(FAILED(Append2HR(pszSrc1, pszSrc2)))
        {
            _ASSERT(FALSE);
        }
        return m_psz;
    }

    WCHAR * Copy() const
    {
        if (m_psz)
        {
            CSpDynamicString szNew(m_psz);
            return szNew.Detach();
        }
        return NULL;
    }
    CHAR * CopyToChar() const
    {
        if (m_psz)
        {
            CHAR* psz;
            ULONG cbNeeded = ::WideCharToMultiByte(CP_ACP, 0, m_psz, -1, NULL, NULL, NULL, NULL);
            psz = (CHAR *)::CoTaskMemAlloc(cbNeeded);
            if (psz == NULL)
            {
                SetLastError(ERROR_OUTOFMEMORY);
            }
            else
            {
                ::WideCharToMultiByte(CP_ACP, 0, m_psz, -1, psz, cbNeeded/sizeof(CHAR), NULL, NULL);
            }
            return psz;
        }
        return NULL;
    }
    void Attach(WCHAR __in * pszSrc)
    {
        _ASSERT(m_psz == NULL);
        m_psz = pszSrc;
    }
    WCHAR * Detach()
    {
        WCHAR * s = m_psz;
        m_psz = NULL;
        return s;
    }
    void Clear()
    {
        ::CoTaskMemFree(m_psz);
        m_psz = NULL;
    }
    bool operator!() const
    {
        return (m_psz == NULL);
    }
    HRESULT CopyToBSTR(BSTR * pbstr)
    {
        if (m_psz)
        {
            *pbstr = ::SysAllocString(m_psz);
            if (*pbstr == NULL)
            {
                return E_OUTOFMEMORY;
            }
        }
        else
        {
            *pbstr = NULL;
        }
        return S_OK;
    }
    void TrimToSize(ULONG ulNumChars)
    {
        if (m_psz && ulNumChars < Length())
        {
            m_psz[ulNumChars] = 0;
        }
    }
    WCHAR * Compact()
    {
        if (m_psz)
        {
            ULONG cch = (ULONG) wcslen(m_psz);
            m_psz = (WCHAR *)::CoTaskMemRealloc(m_psz, (cch + 1) * sizeof(WCHAR));
        }
        return m_psz;
    }
    WCHAR * ClearAndGrowTo(ULONG cch)
    {
        if (m_psz)
        {
            Clear();
        }
        
        ULONGLONG ullcbNew = (ULONGLONG) cch * sizeof(WCHAR);
        if ((ullcbNew > cch) && ((ULONG) ullcbNew == ullcbNew))
        {
            m_psz = (WCHAR *)::CoTaskMemAlloc((ULONG)ullcbNew);
            if (m_psz)
            {
                memset(m_psz, 0, (ULONG)ullcbNew);
            }
        }
        
        return m_psz;
    }
    WCHAR * LTrim()
    {
        if (m_psz)
        {
            WCHAR * pszRead = m_psz;
            while (iswspace(*pszRead))
            {
                pszRead++;
            }
            if (pszRead != m_psz)
            {
                WCHAR * pszWrite = m_psz;
                size_t length = wcslen(pszWrite);
                StringCchCopyW(pszWrite, length +1, pszRead);
            }
        }
        return m_psz;
    }
    WCHAR * RTrim()
    {
        if (m_psz)
        {
            WCHAR * pszTail = m_psz + wcslen(m_psz);
            WCHAR * pszZeroTerm = pszTail;
            while (pszZeroTerm > m_psz && iswspace(pszZeroTerm[-1]))
            {
                pszZeroTerm--;
            }
            if (pszZeroTerm != pszTail)
            {
                *pszZeroTerm = '\0';
            }
        }
        return m_psz;        
    }
    WCHAR * TrimBoth()
    {
        RTrim();
        return LTrim();
    }
};



//
//  Simple inline function converts a ulong to a hex string.
//
inline void SpHexFromUlong(__out_ecount (9) WCHAR * psz, ULONG ul)
{
    // If for some reason we cannot convert a number, set it to 0

#ifndef UNDER_CE
    if (_ultow_s(ul, psz, 9, 16))
    {
        psz[0] = L'0';
        psz[1] = 0;
    }
#else
    if (psz != NULL)
    {
        psz[0] = L'0';
        psz[1] = 0;
        _ultow(ul, psz, 16);
    }
#endif
}


inline HRESULT SpULongFromHex(const WCHAR *psz, unsigned *pResult)
{
    HRESULT hr = S_OK;
    *pResult = 0;
    size_t cLen = wcslen (psz);

    if (cLen > 8)
    {
        hr = E_FAIL;
    }

    if (SUCCEEDED(hr))
    {
        // Convert the token to its numeral form in a WCHAR
        bool fFirst = true;

        for (size_t i = 0; i < cLen; i++)
        {
            int k = tolower (*psz);
            if (k >= L'a')
            {
                k = 10 + k - L'a';
            }
            else
            {
                if (k <= L'9')
                {
                    k -= L'0';
                }
                else
                {
                    // character in the range ':' .. '@'
                    k = -1;
                }
            }

            if (k < 0 || k > 15)
            {
                hr = E_FAIL;
                break;
            }
            if (fFirst)
                fFirst = false;
            else
                *pResult <<= 4;

            *pResult += (unsigned) k;
            psz++;
        }
    }

    return hr;
} 

//=== Token helpers

inline HRESULT SpGetTokenFromId(
    const WCHAR * pszTokenId, 
    ISpObjectToken ** ppToken,
    BOOL fCreateIfNotExist = FALSE)
{
    HRESULT hr;
    
    CComPtr<ISpObjectToken> cpToken;
    hr = cpToken.CoCreateInstance(CLSID_SpObjectToken);
    
    if (SUCCEEDED(hr))
    {
        hr = cpToken->SetId(NULL, pszTokenId, fCreateIfNotExist);
    }
    
    if (SUCCEEDED(hr))
    {
        *ppToken = cpToken.Detach();
    }
    
    return hr;
}

inline HRESULT SpGetCategoryFromId(
    const WCHAR * pszCategoryId,
    ISpObjectTokenCategory ** ppCategory,
    BOOL fCreateIfNotExist = FALSE)
{
    HRESULT hr;
    
    CComPtr<ISpObjectTokenCategory> cpTokenCategory;
    hr = cpTokenCategory.CoCreateInstance(CLSID_SpObjectTokenCategory);
    
    if (SUCCEEDED(hr))
    {
        hr = cpTokenCategory->SetId(pszCategoryId, fCreateIfNotExist);
    }
    
    if (SUCCEEDED(hr))
    {
        *ppCategory = cpTokenCategory.Detach();
    }
    
    return hr;
}

inline HRESULT SpGetDefaultTokenIdFromCategoryId(
    const WCHAR * pszCategoryId,
    __deref_out WCHAR ** ppszTokenId)
{
    HRESULT hr;

    CComPtr<ISpObjectTokenCategory> cpCategory;
    hr = SpGetCategoryFromId(pszCategoryId, &cpCategory);
    
    if (SUCCEEDED(hr))
    {
        hr = cpCategory->GetDefaultTokenId(ppszTokenId);
    }

    return hr;
}

inline HRESULT SpSetDefaultTokenIdForCategoryId(
    const WCHAR * pszCategoryId,
    const WCHAR * pszTokenId)
{
    HRESULT hr;

    CComPtr<ISpObjectTokenCategory> cpCategory;
    hr = SpGetCategoryFromId(pszCategoryId, &cpCategory);
    
    if (SUCCEEDED(hr))
    {
        hr = cpCategory->SetDefaultTokenId(pszTokenId);
    }

    return hr;
}

inline HRESULT SpGetDefaultTokenFromCategoryId(
    const WCHAR * pszCategoryId,
    ISpObjectToken ** ppToken,
    BOOL fCreateCategoryIfNotExist = TRUE)
{
    HRESULT hr;

    CComPtr<ISpObjectTokenCategory> cpCategory;
    hr = SpGetCategoryFromId(pszCategoryId, &cpCategory, fCreateCategoryIfNotExist);

    if (SUCCEEDED(hr))
    {
        WCHAR * pszTokenId;
        hr = cpCategory->GetDefaultTokenId(&pszTokenId);
        if (SUCCEEDED(hr))
        {
            hr = SpGetTokenFromId(pszTokenId, ppToken);
            ::CoTaskMemFree(pszTokenId);
        }
    }

    return hr;
}

inline HRESULT SpSetDefaultTokenForCategoryId(
    const WCHAR * pszCategoryId,
    ISpObjectToken * pToken)
{
    HRESULT hr;

    WCHAR * pszTokenId;
    hr = pToken->GetId(&pszTokenId);

    if (SUCCEEDED(hr))
    {
        hr = SpSetDefaultTokenIdForCategoryId(pszCategoryId, pszTokenId);
        ::CoTaskMemFree(pszTokenId);
    }

    return hr;
}

inline HRESULT SpSetCommonTokenData(
    ISpObjectToken * pToken,
    const CLSID * pclsid,
    const WCHAR * pszLangIndependentName,
    LANGID langid,
    const WCHAR * pszLangDependentName,
    ISpDataKey ** ppDataKeyAttribs)
{
    HRESULT hr = S_OK;
    
    // Set the new token's CLSID (if specified)
    if (SUCCEEDED(hr) && pclsid)
    {
        CSpDynamicString dstrClsid;
        hr = StringFromCLSID(*pclsid, &dstrClsid);
    
        if (SUCCEEDED(hr))
        {
            hr = pToken->SetStringValue(SPTOKENVALUE_CLSID, dstrClsid);
        }
    }

    // Set the token's lang independent name
    if (SUCCEEDED(hr) && pszLangIndependentName)
    {
        hr = pToken->SetStringValue(NULL, pszLangIndependentName);
    }

    // Set the token's lang dependent name
    if (SUCCEEDED(hr) && pszLangDependentName)
    {
        WCHAR szLangId[10];
        StringCbPrintfW (szLangId, sizeof (szLangId), L"%x", langid);

        hr = pToken->SetStringValue(szLangId, pszLangDependentName);
    }

    // Open the attributes key if requested
    if (SUCCEEDED(hr) && ppDataKeyAttribs)
    {
        hr = pToken->CreateKey(L"Attributes", ppDataKeyAttribs);
    }

    return hr;
}

inline HRESULT SpCreateNewToken(
    const WCHAR * pszTokenId,
    ISpObjectToken ** ppToken)
{
    HRESULT hr;

    // Forcefully create the token
    hr = SpGetTokenFromId(pszTokenId, ppToken, TRUE);
    
    return hr;
}

inline HRESULT SPCoCreateGuid(GUID *pGuid)
{
    HRESULT hr = S_OK;

    hr = CoCreateGuid(pGuid);
    return hr;
}

inline HRESULT SpCreateNewToken(
    const WCHAR * pszCategoryId,
    const WCHAR * pszTokenKeyName,
    ISpObjectToken ** ppToken)
{
    HRESULT hr;

    // Forcefully create the category
    CComPtr<ISpObjectTokenCategory> cpCategory;
    hr = SpGetCategoryFromId(pszCategoryId, &cpCategory, TRUE);

    // Come up with a token key name if one wasn't specified
    CSpDynamicString dstrTokenKeyName;
    if (SUCCEEDED(hr))
    {
        if (pszTokenKeyName == NULL)
        {
            GUID guidTokenKeyName;
            hr = SPCoCreateGuid(&guidTokenKeyName);

            if (SUCCEEDED(hr))
            {
                hr = StringFromCLSID(guidTokenKeyName, &dstrTokenKeyName);
            }

            if (SUCCEEDED(hr))
            {
                pszTokenKeyName = dstrTokenKeyName;
            }
        }
    }

    // Build the token id
    CSpDynamicString dstrTokenId;
    if (SUCCEEDED(hr))
    {
        dstrTokenId = pszCategoryId;
        dstrTokenId.Append2(L"\\Tokens\\", pszTokenKeyName);
    }

    // Forcefully create the token
    if (SUCCEEDED(hr))
    {
        hr = SpGetTokenFromId(dstrTokenId, ppToken, TRUE);
    }
    
    return hr;
}

inline HRESULT SpCreateNewTokenEx(
    const WCHAR * pszCategoryId,
    const WCHAR * pszTokenKeyName,
    const CLSID * pclsid,
    const WCHAR * pszLangIndependentName,
    LANGID langid,
    const WCHAR * pszLangDependentName,
    ISpObjectToken ** ppToken,
    ISpDataKey ** ppDataKeyAttribs)
{
    HRESULT hr;

    // Create the new token
    hr = SpCreateNewToken(pszCategoryId, pszTokenKeyName, ppToken);

    // Now set the extra data
    if (SUCCEEDED(hr))
    {
        hr = SpSetCommonTokenData(
                    *ppToken, 
                    pclsid, 
                    pszLangIndependentName, 
                    langid, 
                    pszLangDependentName, 
                    ppDataKeyAttribs);
    }
    
    return hr;
}

inline HRESULT SpCreateNewTokenEx(
    const WCHAR * pszTokenId,
    const CLSID * pclsid,
    const WCHAR * pszLangIndependentName,
    LANGID langid,
    const WCHAR * pszLangDependentName,
    ISpObjectToken ** ppToken,
    ISpDataKey ** ppDataKeyAttribs)
{
    HRESULT hr;

    // Create the new token
    hr = SpCreateNewToken(pszTokenId, ppToken);

    // Now set the extra data
    if (SUCCEEDED(hr))
    {
        hr = SpSetCommonTokenData(
                    *ppToken, 
                    pclsid, 
                    pszLangIndependentName, 
                    langid, 
                    pszLangDependentName, 
                    ppDataKeyAttribs);
    }
    
    return hr;
}

inline HRESULT SpEnumTokens(
    const WCHAR * pszCategoryId, 
    const WCHAR * pszReqAttribs, 
    const WCHAR * pszOptAttribs, 
    IEnumSpObjectTokens ** ppEnum)
{
    HRESULT hr = S_OK;
    
    CComPtr<ISpObjectTokenCategory> cpCategory;
    hr = SpGetCategoryFromId(pszCategoryId, &cpCategory);
    
    if (SUCCEEDED(hr))
    {
        hr = cpCategory->EnumTokens(
                    pszReqAttribs,
                    pszOptAttribs,
                    ppEnum);
    }
    
    return hr;
}

inline HRESULT SpFindBestToken(
    const WCHAR * pszCategoryId, 
    const WCHAR * pszReqAttribs, 
    const WCHAR * pszOptAttribs, 
    ISpObjectToken **ppObjectToken)
{
    HRESULT hr = S_OK;
    
    const WCHAR *pszVendorPreferred = L"VendorPreferred";
    const ULONG ulLenVendorPreferred = (ULONG) wcslen(pszVendorPreferred);

    // append VendorPreferred to the end of pszOptAttribs to force this preference
    ULONG ulLen;
    if (pszOptAttribs)
    {
        hr = ULongAdd((ULONG)wcslen(pszOptAttribs), ulLenVendorPreferred, &ulLen);
        if (SUCCEEDED(hr))
        {
            hr = ULongAdd(ulLen, 1 + 1, &ulLen); // including 1 char here for null terminator
        }
    }
    else
    {
        hr = ULongAdd(ulLenVendorPreferred, 1, &ulLen); // including 1 char here for null terminator
    }
    if (FAILED(hr))
    {
        hr = E_INVALIDARG;
    }
    else
    {
        WCHAR *pszOptAttribsVendorPref = new WCHAR[ulLen];
        if (pszOptAttribsVendorPref)
        {
            if (pszOptAttribs)
            {
                StringCchCopyW (pszOptAttribsVendorPref, ulLen, pszOptAttribs);
                StringCchCatW (pszOptAttribsVendorPref, ulLen, L";");
                StringCchCatW (pszOptAttribsVendorPref, ulLen, pszVendorPreferred);
            }
            else
            {
                StringCchCopyW (pszOptAttribsVendorPref, ulLen, pszVendorPreferred);
            }
        }
        else
        {
            hr = E_OUTOFMEMORY;
        }

        CComPtr<IEnumSpObjectTokens> cpEnum;
        if (SUCCEEDED(hr))
        {
            hr = SpEnumTokens(pszCategoryId, pszReqAttribs, pszOptAttribsVendorPref, &cpEnum);
        }

        delete[] pszOptAttribsVendorPref;

        if (SUCCEEDED(hr))
        {
            hr = cpEnum->Next(1, ppObjectToken, NULL);
            if (hr == S_FALSE)
            {
                *ppObjectToken = NULL;
                hr = SPERR_NOT_FOUND;
            }
        }
    }

    return hr;
}

template<class T>
HRESULT SpCreateObjectFromToken(ISpObjectToken * pToken, T ** ppObject,
                       IUnknown * pUnkOuter = NULL, DWORD dwClsCtxt = CLSCTX_ALL)
{
    HRESULT hr;

    hr = pToken->CreateInstance(pUnkOuter, dwClsCtxt, __uuidof(T), (void **)ppObject);
    
    return hr;
}

template<class T>
HRESULT SpCreateObjectFromTokenId(const WCHAR * pszTokenId, T ** ppObject,
                       IUnknown * pUnkOuter = NULL, DWORD dwClsCtxt = CLSCTX_ALL)
{
    
    ISpObjectToken * pToken;
    HRESULT hr = SpGetTokenFromId(pszTokenId, &pToken);
    if (SUCCEEDED(hr))
    {
        hr = SpCreateObjectFromToken(pToken, ppObject, pUnkOuter, dwClsCtxt);
        pToken->Release();
    }

    return hr;
}

template<class T>
HRESULT SpCreateDefaultObjectFromCategoryId(const WCHAR * pszCategoryId, T ** ppObject,
                       IUnknown * pUnkOuter = NULL, DWORD dwClsCtxt = CLSCTX_ALL)
{
   
    ISpObjectToken * pToken;
    HRESULT hr = SpGetDefaultTokenFromCategoryId(pszCategoryId, &pToken);
    if (SUCCEEDED(hr))
    {
        hr = SpCreateObjectFromToken(pToken, ppObject, pUnkOuter, dwClsCtxt);
        pToken->Release();
    }

    return hr;
}

template<class T>
HRESULT SpCreateBestObject(
    const WCHAR * pszCategoryId, 
    const WCHAR * pszReqAttribs, 
    const WCHAR * pszOptAttribs, 
    T ** ppObject,
    IUnknown * pUnkOuter = NULL, 
    DWORD dwClsCtxt = CLSCTX_ALL)
{
    HRESULT hr;
    
    CComPtr<ISpObjectToken> cpToken;
    hr = SpFindBestToken(pszCategoryId, pszReqAttribs, pszOptAttribs, &cpToken);

    if (SUCCEEDED(hr))
    {
        hr = SpCreateObjectFromToken(cpToken, ppObject, pUnkOuter, dwClsCtxt);
    }

    return hr;
}

inline HRESULT SpCreatePhoneConverter(
    LANGID LangID,
    const WCHAR * pszReqAttribs,
    const WCHAR * pszOptAttribs,
    ISpPhoneConverter ** ppPhoneConverter)
{
    HRESULT hr = SPERR_NOT_FOUND;

    // If not IPA or UPS
    if (LangID != 0)
    {
        CSpDynamicString dstrReqAttribs;
        if (pszReqAttribs)
        {
            dstrReqAttribs = pszReqAttribs;
            dstrReqAttribs.Append(L";");
        }

        WCHAR szLang[MAX_PATH];

        SpHexFromUlong(szLang, LangID);

        WCHAR szLangCondition[MAX_PATH];
        StringCchCopyW(szLangCondition, MAX_PATH, L"Language=");
        StringCchCatW(szLangCondition, MAX_PATH, szLang);

        dstrReqAttribs.Append(szLangCondition);

        hr = SpCreateBestObject(SPCAT_PHONECONVERTERS, dstrReqAttribs, pszOptAttribs, ppPhoneConverter);
    }

#if _SAPI_VER >= 0x053
    // If we cannot find a phone converter, use the Universal Phone Converter as default
    if (hr == SPERR_NOT_FOUND)
    {
        hr =  CoCreateInstance(CLSID_SpPhoneConverter, NULL, CLSCTX_ALL, IID_ISpPhoneConverter, (LPVOID*)ppPhoneConverter);

        if (SUCCEEDED(hr))
        {
            ISpPhoneticAlphabetSelection * pAlphabetSelection = NULL;
            hr = (*ppPhoneConverter)->QueryInterface(IID_ISpPhoneticAlphabetSelection, (LPVOID*) &pAlphabetSelection);
            if (SUCCEEDED(hr))
            {
                hr = pAlphabetSelection->SetAlphabetToUPS(TRUE);
                pAlphabetSelection->Release();
            }
            else
            {
                // We cannot query for the new interface, reset the error code
                hr = SPERR_NOT_FOUND;
            }

            if (FAILED(hr))
            {
                (*ppPhoneConverter)->Release();
                *ppPhoneConverter = NULL;
            }
        }
    }
#endif
    return hr;
}

inline HRESULT SpGetLanguageFromToken(ISpObjectToken * pToken, LANGID * plangid)
{
    HRESULT hr = S_OK;
    CComPtr<ISpDataKey> cpDataKeyAttribs;
    hr = pToken->OpenKey(SPTOKENKEY_ATTRIBUTES, &cpDataKeyAttribs);

    CSpDynamicString dstrLanguage;
    if (SUCCEEDED(hr))
    {
        hr = cpDataKeyAttribs->GetStringValue(L"Language", &dstrLanguage);
    }

    if (SUCCEEDED(hr))
    {
        const WCHAR *pszLangId = dstrLanguage;
        WCHAR achHexNumber [5]; // LangIds cannot be more than 4 characters longs

        // check if the langid is in the format "409;9" - extract the "409" in this case
        const WCHAR *pSemiColon = wcschr (pszLangId, L';');
        if (pSemiColon)
        {
            size_t cLen = pSemiColon - dstrLanguage;
            if (cLen <= 4)
            {
                StringCbCopyNW (achHexNumber, sizeof (achHexNumber), dstrLanguage, cLen * sizeof (achHexNumber [0]));
                pszLangId = achHexNumber;
            }
        }
        unsigned langid;
        if (FAILED (SpULongFromHex(pszLangId, &langid)))
        {
            hr = SPERR_INVALID_TOKEN_ID;
        }
        else
        {
            *plangid = (LANGID) langid;
        }
    }

    return hr;
}

inline HRESULT SpGetLanguageFromVoiceToken(ISpObjectToken * pToken, LANGID * plangid)
{
    return SpGetLanguageFromToken(pToken, plangid);
}


/****************************************************************************
* SpHrFromWin32 *
*---------------*
*   Description:
*       This inline function works around a basic problem with the macro
*   HRESULT_FROM_WIN32.  The macro forces the expresion in ( ) to be evaluated
*   two times.  By using this inline function, the expression will only be
*   evaluated once.
*
*   Returns:
*       HRESULT of converted Win32 error code
*
*****************************************************************************/

inline HRESULT SpHrFromWin32(DWORD dwErr)
{
    return HRESULT_FROM_WIN32(dwErr);
}


/****************************************************************************
* SpHrFromLastWin32Error *
*------------------------*
*   Description:
*       This simple inline function is used to return a converted HRESULT
*   from the Win32 function ::GetLastError.  Note that using HRESULT_FROM_WIN32
*   will evaluate the error code twice so we don't want to use:
*
*       HRESULT_FROM_WIN32(::GetLastError()) 
*
*   since that will call GetLastError twice.
*
*   Returns:
*       HRESULT for ::GetLastError(). If the HRESULT is a success code, this 
*       function will return E_FAIL to guarantee an error return code.
*
*****************************************************************************/

inline HRESULT SpHrFromLastWin32Error()
{
    HRESULT hr = SpHrFromWin32(::GetLastError());
    return FAILED(hr) ? hr : E_FAIL;
}


/****************************************************************************
* SpGetUserDefaultUILanguage *
*----------------------------*
*   Description:
*       Now that we only support XP & Above, this is a straight call to
*       GetUserDefaultUILanguage
*
*   Returns:
*       Default UI language
*
*****************************************************************************/

inline LANGID SpGetUserDefaultUILanguage(void) 
{
    return GetUserDefaultUILanguage() ;
}

inline HRESULT SpGetDescription(ISpObjectToken * pObjToken, __deref_out WCHAR ** ppszDescription, LANGID Language = SpGetUserDefaultUILanguage())
{
    WCHAR szLangId[10];
    HRESULT hr = S_OK;

#if _SAPI_VER >= 0x053
    WCHAR* pRegKeyPath = 0;
    WCHAR* pszTemp = 0;
    HKEY   Handle = NULL;

    // Windows Vista does not encourage localized strings in the registry
    // When running on Windows Vista query the localized engine name from a resource dll
    OSVERSIONINFO ver;
    ver.dwOSVersionInfoSize = sizeof( ver );

    if (ppszDescription == NULL)
    {
        return E_POINTER;
    }
    *ppszDescription = NULL;

    if( ( ::GetVersionEx( &ver ) == TRUE ) && ( ver.dwMajorVersion >= 6 ) )
    {
        // If we reach this code we are running under Windows Vista
        HMODULE hmodAdvapi32Dll = NULL;
        typedef HRESULT (WINAPI* LPFN_RegLoadMUIStringW)(HKEY, LPCWSTR, LPWSTR, DWORD, LPDWORD, DWORD, LPCWSTR);
        LPFN_RegLoadMUIStringW pfnRegLoadMUIStringW = NULL;
                
        // Delay bind with RegLoadMUIStringW since this function is not supported on previous versions of advapi32.dll
        // RegLoadMUIStringW is supported only on advapi32.dll that ships with Windows Vista  and above
        // Calling RegLoadMUIStringW directly makes the loader try to resolve the function reference at load time which breaks,
        // hence we manually load advapi32.dll, query for the function pointer and invoke it.
        hmodAdvapi32Dll = ::LoadLibrary(TEXT("advapi32.dll"));
        if(hmodAdvapi32Dll)
        {
#ifdef UNDER_CE
            pfnRegLoadMUIStringW = (LPFN_RegLoadMUIStringW) ::GetProcAddress(hmodAdvapi32Dll, L"RegLoadMUIStringW");
#else
            pfnRegLoadMUIStringW = (LPFN_RegLoadMUIStringW) ::GetProcAddress(hmodAdvapi32Dll, "RegLoadMUIStringW");
#endif
            if (!pfnRegLoadMUIStringW)
            {
                // This should not happen in Vista
                _ASSERT (pfnRegLoadMUIStringW);
                hr = TYPE_E_DLLFUNCTIONNOTFOUND;
            }
        }
        else
        {
            hr = HRESULT_FROM_WIN32(ERROR_DLL_NOT_FOUND);
        }
        
        if (SUCCEEDED(hr))
        {
            hr = pObjToken->GetId(&pszTemp);
        }

        if (SUCCEEDED(hr))
        {
            LONG   lErrorCode = ERROR_SUCCESS;

            pRegKeyPath = wcschr(pszTemp, L'\\');   // Find the first occurance of '\\' in the absolute registry key path
            if(pRegKeyPath)
            {
                *pRegKeyPath = L'\0';
                pRegKeyPath++;                         // pRegKeyPath now points to the path to the recognizer token under the HKLM or HKCR hive
                *ppszDescription = 0;

                // Open the registry key for read and get the handle
                if (wcsncmp(pszTemp, L"HKEY_LOCAL_MACHINE", MAX_PATH) == 0)
                {
                    lErrorCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE, pRegKeyPath, 0, KEY_QUERY_VALUE, &Handle);
                }
                else if (wcsncmp(pszTemp, L"HKEY_CURRENT_USER", MAX_PATH) == 0)
                {
                    lErrorCode = RegOpenKeyExW(HKEY_CURRENT_USER, pRegKeyPath, 0, KEY_QUERY_VALUE, &Handle);
                }
                else
                {
                    lErrorCode = ERROR_BAD_ARGUMENTS;
                }
                
                // Use MUI RegLoadMUIStringW API to load the localized string
                if(ERROR_SUCCESS == lErrorCode)
                {
                    *ppszDescription = (WCHAR*) CoTaskMemAlloc(MAX_PATH * sizeof(WCHAR)); // This should be enough memory to allocate the localized Engine Name
                    lErrorCode = (*pfnRegLoadMUIStringW) (Handle, SR_LOCALIZED_DESCRIPTION, *ppszDescription, MAX_PATH * sizeof(WCHAR), NULL, REG_MUI_STRING_TRUNCATE, NULL);
                }
            }
            else
            {
                // pRegKeyPath should never be 0 if we are querying for relative hkey path
                lErrorCode = ERROR_BAD_ARGUMENTS;
            }

            hr = HRESULT_FROM_WIN32(lErrorCode);
        }

        // Close registry key handle
        if(Handle)
        {
            RegCloseKey(Handle);
        }
        // Free memory allocated to locals
        if(pszTemp)
        {
            CoTaskMemFree(pszTemp);
        }
        if (hmodAdvapi32Dll)
        {
            ::FreeLibrary(hmodAdvapi32Dll);
        }
    }
    else
    {
        hr = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
    }

    _ASSERT(FAILED(hr) || *ppszDescription != NULL);

    // If running on OSes released before Windows Vista query the localized string from the registry
    // If RegLoadMUIStringW failed to retrieved the localized Engine name retrieve the localized string from the fallback (Default) attribute
#else
    hr = E_FAIL;
#endif // _SAPI_VER >= 0x053
    if (FAILED(hr))
    {
        // Free memory allocated above if necessary
        if (*ppszDescription != NULL)
        {
            CoTaskMemFree(*ppszDescription);
            *ppszDescription = NULL;
        }

        SpHexFromUlong(szLangId, Language);
        hr = pObjToken->GetStringValue(szLangId, ppszDescription);
        if (hr == SPERR_NOT_FOUND)
        {
            hr = pObjToken->GetStringValue(NULL, ppszDescription);
        }
    }

    return hr;
}


inline HRESULT SpSetDescription(ISpObjectToken * pObjToken, const WCHAR * pszDescription, LANGID Language = SpGetUserDefaultUILanguage(), BOOL fSetLangIndependentId = TRUE)
{
    WCHAR szLangId[10];
    SpHexFromUlong(szLangId, Language);
    HRESULT hr = pObjToken->SetStringValue(szLangId, pszDescription);
    if (SUCCEEDED(hr) && fSetLangIndependentId)
    {
        hr = pObjToken->SetStringValue(NULL, pszDescription);
    }
    return hr;
}

/****************************************************************************
* SpConvertStreamFormatEnum *
*---------------------------*
*   Description:
*       This method converts the specified stream format into a wave format
*   structure.
*
*****************************************************************************/
inline HRESULT SpConvertStreamFormatEnum(SPSTREAMFORMAT eFormat, GUID * pFormatId, WAVEFORMATEX ** ppCoMemWaveFormatEx)
{
    HRESULT hr = S_OK;

    if(pFormatId==NULL || ppCoMemWaveFormatEx==NULL)
    {
        return E_INVALIDARG;
    }

    const GUID * pFmtGuid = &GUID_NULL;     // Assume failure case
    if( eFormat >= SPSF_8kHz8BitMono && eFormat <= SPSF_48kHz16BitStereo )
    {
        WAVEFORMATEX * pwfex = (WAVEFORMATEX *)::CoTaskMemAlloc(sizeof(WAVEFORMATEX));
        *ppCoMemWaveFormatEx = pwfex;
        if (pwfex)
        {
            DWORD dwIndex = eFormat - SPSF_8kHz8BitMono;
            BOOL bIsStereo = dwIndex & 0x1;
            BOOL bIs16 = dwIndex & 0x2;
            DWORD dwKHZ = (dwIndex & 0x3c) >> 2;
            static const DWORD adwKHZ[] = { 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 };
            pwfex->wFormatTag = WAVE_FORMAT_PCM;
            pwfex->nChannels = pwfex->nBlockAlign = (WORD)(bIsStereo ? 2 : 1);
            pwfex->nSamplesPerSec = (dwKHZ < sizeof(adwKHZ)/sizeof(adwKHZ[0])) ? adwKHZ[dwKHZ] : adwKHZ[0];
            pwfex->wBitsPerSample = 8;
            if (bIs16)
            {
                pwfex->wBitsPerSample *= 2;
                pwfex->nBlockAlign *= 2;
            }
                pwfex->nAvgBytesPerSec = pwfex->nSamplesPerSec * pwfex->nBlockAlign;
            pwfex->cbSize = 0;
            pFmtGuid = &SPDFID_WaveFormatEx;
        }
        else
        {
            hr = E_OUTOFMEMORY;
        }
    }
    else if( eFormat == SPSF_TrueSpeech_8kHz1BitMono )
    {
        int NumBytes = sizeof( WAVEFORMATEX ) + 32;
        WAVEFORMATEX * pwfex = (WAVEFORMATEX *)::CoTaskMemAlloc( NumBytes );
        *ppCoMemWaveFormatEx = pwfex;
        if( pwfex )
        {
            memset( pwfex, 0, NumBytes );
            pwfex->wFormatTag      = WAVE_FORMAT_DSPGROUP_TRUESPEECH;
            pwfex->nChannels       = 1;
            pwfex->nSamplesPerSec  = 8000;
            pwfex->nAvgBytesPerSec = 1067;
            pwfex->nBlockAlign     = 32;
            pwfex->wBitsPerSample  = 1;
            pwfex->cbSize          = 32;
            BYTE* pExtra = ((BYTE*)pwfex) + sizeof( WAVEFORMATEX );
            pExtra[0] = 1;
            pExtra[2] = 0xF0;
            pFmtGuid = &SPDFID_WaveFormatEx;
        }
        else
        {
            hr = E_OUTOFMEMORY;
        }
    }
    else if( (eFormat >= SPSF_CCITT_ALaw_8kHzMono    ) &&
             (eFormat <= SPSF_CCITT_ALaw_44kHzStereo ) )
    {
        WAVEFORMATEX * pwfex = (WAVEFORMATEX *)::CoTaskMemAlloc( sizeof(WAVEFORMATEX) );
        *ppCoMemWaveFormatEx = pwfex;
        if( pwfex )
        {
            memset( pwfex, 0, sizeof(WAVEFORMATEX) );
            DWORD dwIndex = eFormat - SPSF_CCITT_ALaw_8kHzMono;
            DWORD dwKHZ = dwIndex / 2;
            static const DWORD adwKHZ[] = { 8000, 11025, 22050, 44100 };
            BOOL bIsStereo    = dwIndex & 0x1;
            pwfex->wFormatTag = WAVE_FORMAT_ALAW;
            pwfex->nChannels  = pwfex->nBlockAlign = (WORD)(bIsStereo ? 2 : 1);
            pwfex->nSamplesPerSec = (dwKHZ < sizeof(adwKHZ)/sizeof(adwKHZ[0])) ? adwKHZ[dwKHZ] : adwKHZ[0];
            pwfex->wBitsPerSample  = 8;
            pwfex->nAvgBytesPerSec = pwfex->nSamplesPerSec * pwfex->nBlockAlign;
            pwfex->cbSize          = 0;
            pFmtGuid = &SPDFID_WaveFormatEx;
        }
        else
        {
            hr = E_OUTOFMEMORY;
        }
    }
    else if( (eFormat >= SPSF_CCITT_uLaw_8kHzMono    ) &&
             (eFormat <= SPSF_CCITT_uLaw_44kHzStereo ) )
    {
        WAVEFORMATEX * pwfex = (WAVEFORMATEX *)::CoTaskMemAlloc( sizeof(WAVEFORMATEX) );
        *ppCoMemWaveFormatEx = pwfex;
        if( pwfex )
        {
            memset( pwfex, 0, sizeof(WAVEFORMATEX) );
            DWORD dwIndex = eFormat - SPSF_CCITT_uLaw_8kHzMono;
            DWORD dwKHZ = dwIndex / 2;
            static const DWORD adwKHZ[] = { 8000, 11025, 22050, 44100 };
            BOOL bIsStereo    = dwIndex & 0x1;
            pwfex->wFormatTag = WAVE_FORMAT_MULAW;
            pwfex->nChannels  = pwfex->nBlockAlign = (WORD)(bIsStereo ? 2 : 1);
            pwfex->nSamplesPerSec = (dwKHZ < sizeof(adwKHZ)/sizeof(adwKHZ[0])) ? adwKHZ[dwKHZ] : adwKHZ[0];
            pwfex->wBitsPerSample  = 8;
            pwfex->nAvgBytesPerSec = pwfex->nSamplesPerSec * pwfex->nBlockAlign;
            pwfex->cbSize          = 0;
            pFmtGuid = &SPDFID_WaveFormatEx;
        }
        else
        {
            hr = E_OUTOFMEMORY;
        }
    }
    else if( (eFormat >= SPSF_ADPCM_8kHzMono    ) &&
             (eFormat <= SPSF_ADPCM_44kHzStereo ) )
    {
        int NumBytes = sizeof( WAVEFORMATEX ) + 32;
        WAVEFORMATEX * pwfex = (WAVEFORMATEX *)::CoTaskMemAlloc( NumBytes );
        *ppCoMemWaveFormatEx = pwfex;
        if( pwfex )
        {
            //--- Some of these values seem odd. We used what the codec told us.
            static const DWORD adwKHZ[] = { 8000, 11025, 22050, 44100 };
            static const DWORD BytesPerSec[] = { 4096, 8192, 5644, 11289, 11155, 22311, 22179, 44359 };
            static const DWORD BlockAlign[]  = { 256, 256, 512, 1024 };
            static const BYTE Extra811[32] =
            {
                0xF4, 0x01, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00,
                0x00, 0x02, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00,
                0xC0, 0x00, 0x40, 0x00, 0xF0, 0x00, 0x00, 0x00,
                0xCC, 0x01, 0x30, 0xFF, 0x88, 0x01, 0x18, 0xFF
            };

            static const BYTE Extra22[32] =
            {
                0xF4, 0x03, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00,
                0x00, 0x02, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00,
                0xC0, 0x00, 0x40, 0x00, 0xF0, 0x00, 0x00, 0x00,
                0xCC, 0x01, 0x30, 0xFF, 0x88, 0x01, 0x18, 0xFF
            };

            static const BYTE Extra44[32] =
            {
                0xF4, 0x07, 0x07, 0x00, 0x00, 0x01, 0x00, 0x00,
                0x00, 0x02, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00,
                0xC0, 0x00, 0x40, 0x00, 0xF0, 0x00, 0x00, 0x00,
                0xCC, 0x01, 0x30, 0xFF, 0x88, 0x01, 0x18, 0xFF
            };

            static const BYTE* Extra[4] = { Extra811, Extra811, Extra22, Extra44 };
            memset( pwfex, 0, NumBytes );
            DWORD dwIndex  = eFormat - SPSF_ADPCM_8kHzMono;
            DWORD dwKHZ    = dwIndex / 2;
            BOOL bIsStereo = dwIndex & 0x1;
            pwfex->wFormatTag      = WAVE_FORMAT_ADPCM;
            pwfex->nChannels       = (WORD)(bIsStereo ? 2 : 1);
            pwfex->nSamplesPerSec =  (dwKHZ < sizeof(adwKHZ)/sizeof(adwKHZ[0])) ? adwKHZ[dwKHZ] : adwKHZ[0];
            pwfex->nAvgBytesPerSec = (dwIndex < sizeof(BytesPerSec)/sizeof(BytesPerSec[0])) ? BytesPerSec[dwIndex] : BytesPerSec[0];
            pwfex->nBlockAlign     = (WORD)(((dwKHZ < sizeof(BlockAlign)/sizeof(BlockAlign[0])) ? BlockAlign[dwKHZ] : BlockAlign[0]) * pwfex->nChannels);
            pwfex->wBitsPerSample  = 4;
            pwfex->cbSize          = 32;
            BYTE* pExtra = ((BYTE*)pwfex) + sizeof( WAVEFORMATEX );
            memcpy( pExtra, (dwKHZ < sizeof(Extra)/sizeof(Extra[0])) ? Extra[dwKHZ] : Extra[0], 32 );
            pFmtGuid = &SPDFID_WaveFormatEx;
        }
        else
        {
            hr = E_OUTOFMEMORY;
        }
    }
    else if( (eFormat >= SPSF_GSM610_8kHzMono    ) &&
             (eFormat <= SPSF_GSM610_44kHzMono ) )
    {
        int NumBytes = sizeof( WAVEFORMATEX ) + 2;
        WAVEFORMATEX * pwfex = (WAVEFORMATEX *)::CoTaskMemAlloc( NumBytes );
        *ppCoMemWaveFormatEx = pwfex;
        if( pwfex )
        {
            //--- Some of these values seem odd. We used what the codec told us.
            static const DWORD adwKHZ[] = { 8000, 11025, 22050, 44100 };
            static const DWORD BytesPerSec[] = { 1625, 2239, 4478, 8957 };
            memset( pwfex, 0, NumBytes );
            DWORD dwIndex          = eFormat - SPSF_GSM610_8kHzMono;
            pwfex->wFormatTag      = WAVE_FORMAT_GSM610;
            pwfex->nChannels       = 1;
            pwfex->nSamplesPerSec = (dwIndex < sizeof(adwKHZ)/sizeof(adwKHZ[0])) ? adwKHZ[dwIndex] : adwKHZ[0];
            pwfex->nAvgBytesPerSec = BytesPerSec[dwIndex];
            pwfex->nBlockAlign     = 65;
            pwfex->wBitsPerSample  = 0;
            pwfex->cbSize          = 2;
            BYTE* pExtra = ((BYTE*)pwfex) + sizeof( WAVEFORMATEX );
            pExtra[0] = 0x40;
            pExtra[1] = 0x01;
            pFmtGuid = &SPDFID_WaveFormatEx;
        }
        else
        {
            hr = E_OUTOFMEMORY;
        }
    }
    else
    {
        *ppCoMemWaveFormatEx = NULL;
        switch (eFormat)
        {
        case SPSF_NoAssignedFormat:
            break;
        case SPSF_Text:
            pFmtGuid = &SPDFID_Text;
            break;
        default:
            hr = E_INVALIDARG;
            break;
        }
    }
    *pFormatId = *pFmtGuid;
    return hr;
}

class CSpStreamFormat
{
public:
    GUID            m_guidFormatId;
    WAVEFORMATEX  * m_pCoMemWaveFormatEx; 


    static HRESULT CoMemCopyWFEX(const WAVEFORMATEX * pSrc, WAVEFORMATEX ** ppCoMemWFEX)
    {
        if (pSrc == NULL ||
            pSrc->nAvgBytesPerSec == 0 ||
            pSrc->nBlockAlign == 0 ||
            pSrc->nChannels == 0) // There are other fields like wBitsPerSample but these can be zero in some formats.
        {
            return E_INVALIDARG;
        }

        ULONG cb = sizeof(WAVEFORMATEX);
        if (pSrc->wFormatTag != WAVE_FORMAT_PCM)
        {
            // Add the extra data size in but ignore for WAVE_FORMAT_PCM {accoring to MSDN this should be ignored}.
            cb += pSrc->cbSize;
            if (cb < pSrc->cbSize)
            {
                return E_INVALIDARG;
            }
        }

        *ppCoMemWFEX = (WAVEFORMATEX *)::CoTaskMemAlloc(cb);
        if (*ppCoMemWFEX)
        {
            memcpy(*ppCoMemWFEX, pSrc, cb);
            if ((*ppCoMemWFEX)->wFormatTag == WAVE_FORMAT_PCM)
            {
                (*ppCoMemWFEX)->cbSize = 0; // Always set cbSize to zero for WAVE_FORMAT_PCM.
            }
            return S_OK;
        }
        else
        {
            return E_OUTOFMEMORY;
        }
    }


    CSpStreamFormat()
    {
        m_guidFormatId = GUID_NULL;
        m_pCoMemWaveFormatEx = NULL;
    }

    CSpStreamFormat(SPSTREAMFORMAT eFormat, HRESULT * phr)
    {
        *phr = SpConvertStreamFormatEnum(eFormat, &m_guidFormatId, &m_pCoMemWaveFormatEx);
    }

    CSpStreamFormat(const WAVEFORMATEX * pWaveFormatEx, HRESULT * phr)
    {
        _ASSERT(pWaveFormatEx);
        *phr = CoMemCopyWFEX(pWaveFormatEx, &m_pCoMemWaveFormatEx);
        m_guidFormatId = SUCCEEDED(*phr) ? SPDFID_WaveFormatEx : GUID_NULL;
    }

    ~CSpStreamFormat()
    {
        ::CoTaskMemFree(m_pCoMemWaveFormatEx);
    }

    void Clear()
    {
        ::CoTaskMemFree(m_pCoMemWaveFormatEx);
        m_pCoMemWaveFormatEx = NULL;
        memset(&m_guidFormatId, 0, sizeof(m_guidFormatId));
    }

    const GUID & FormatId() const 
    {
        return m_guidFormatId;
    }

    const WAVEFORMATEX * WaveFormatExPtr() const
    {
        return m_pCoMemWaveFormatEx;
    }


    HRESULT AssignFormat(SPSTREAMFORMAT eFormat)
    {
        ::CoTaskMemFree(m_pCoMemWaveFormatEx);    
        return SpConvertStreamFormatEnum(eFormat, &m_guidFormatId, &m_pCoMemWaveFormatEx);
    }

    HRESULT AssignFormat(ISpStreamFormat * pStream)
    {
        ::CoTaskMemFree(m_pCoMemWaveFormatEx);
        m_pCoMemWaveFormatEx = NULL;
        HRESULT hr = pStream->GetFormat(&m_guidFormatId, &m_pCoMemWaveFormatEx);
        if (SUCCEEDED(hr) && m_pCoMemWaveFormatEx)
        {
            if (m_pCoMemWaveFormatEx->wFormatTag == WAVE_FORMAT_PCM)
            {
                m_pCoMemWaveFormatEx->cbSize = 0; // Always set cbSize to zero for WAVE_FORMAT_PCM.
            }
            if (m_pCoMemWaveFormatEx->nAvgBytesPerSec == 0 ||
                m_pCoMemWaveFormatEx->nBlockAlign == 0 ||
                m_pCoMemWaveFormatEx->nChannels == 0)
            {
                Clear();
                hr = E_INVALIDARG;
            }
        }
        return hr;
    }

    HRESULT AssignFormat(const WAVEFORMATEX * pWaveFormatEx)
    {
        if (pWaveFormatEx->nBlockAlign == 0)
        {
            return E_INVALIDARG;
        }
        ::CoTaskMemFree(m_pCoMemWaveFormatEx);
        HRESULT hr = CoMemCopyWFEX(pWaveFormatEx, &m_pCoMemWaveFormatEx);
        m_guidFormatId = SUCCEEDED(hr) ? SPDFID_WaveFormatEx : GUID_NULL;
        return hr;
    }

    HRESULT AssignFormat(REFGUID rguidFormatId, const WAVEFORMATEX * pWaveFormatEx)
    {
        HRESULT hr = S_OK;

        m_guidFormatId = rguidFormatId;
        ::CoTaskMemFree(m_pCoMemWaveFormatEx);
        m_pCoMemWaveFormatEx = NULL;

        if (rguidFormatId == SPDFID_WaveFormatEx)
        {
            if (pWaveFormatEx == NULL)
            {
                hr = E_INVALIDARG;
            }
            else 
            {
                hr = CoMemCopyWFEX(pWaveFormatEx, &m_pCoMemWaveFormatEx);
            }

            if (FAILED(hr))
            {
                m_guidFormatId = GUID_NULL;
            }
        }

        return hr;
    }


    BOOL IsEqual(REFGUID rguidFormatId, const WAVEFORMATEX * pwfex) const
    {
        if (rguidFormatId == m_guidFormatId)
        {
            if (m_pCoMemWaveFormatEx)
            {
                if (pwfex &&
                    pwfex->cbSize == m_pCoMemWaveFormatEx->cbSize &&
                    memcmp(m_pCoMemWaveFormatEx, pwfex, sizeof(WAVEFORMATEX) + pwfex->cbSize) == 0)
                {
                    return TRUE;
                }
            }
            else
            {
                return (pwfex == NULL);
            }
        }
        return FALSE;
    }



    HRESULT ParamValidateAssignFormat(REFGUID rguidFormatId, const WAVEFORMATEX * pWaveFormatEx, BOOL fRequireWaveFormat = FALSE)
    {
        if ((pWaveFormatEx && (rguidFormatId != SPDFID_WaveFormatEx)) ||
            (fRequireWaveFormat && pWaveFormatEx == NULL))
        {
            return E_INVALIDARG;
        }
        return AssignFormat(rguidFormatId, pWaveFormatEx);
    }

    SPSTREAMFORMAT ComputeFormatEnum()
    {
        if (m_guidFormatId == GUID_NULL)
        {
            return SPSF_NoAssignedFormat;
        }
        if (m_guidFormatId == SPDFID_Text)
        {
            return SPSF_Text;
        }
        if ((m_guidFormatId != SPDFID_WaveFormatEx) || (m_pCoMemWaveFormatEx == NULL))
        {
            return SPSF_NonStandardFormat;
        }
        //
        //  It is a WAVEFORMATEX.  Now determine which type it is and convert.
        //
        DWORD dwIndex = 0;
        switch (m_pCoMemWaveFormatEx->wFormatTag)
        {
          case WAVE_FORMAT_PCM:
          {
            switch (m_pCoMemWaveFormatEx->nChannels)
            {
              case 1:
                break;
              case 2:
                dwIndex |= 1;
                break;
              default:
                return SPSF_ExtendedAudioFormat;
            }

            switch (m_pCoMemWaveFormatEx->wBitsPerSample)
            {
              case 8:
                break;
              case 16:
                dwIndex |= 2;
                break;
              default:
                return SPSF_ExtendedAudioFormat;
            }

            switch (m_pCoMemWaveFormatEx->nSamplesPerSec)
            {
              case 48000:
                dwIndex += 4;   // Fall through
              case 44100:
                dwIndex += 4;   // Fall through
              case 32000:
                dwIndex += 4;   // Fall through
              case 24000:
                dwIndex += 4;   // Fall through
              case 22050:
                dwIndex += 4;   // Fall through
              case 16000:
                dwIndex += 4;   // Fall through
              case 12000:
                dwIndex += 4;   // Fall through
              case 11025:
                dwIndex += 4;   // Fall through
              case 8000:
                break;
              default:
                return SPSF_ExtendedAudioFormat;
            }

            return static_cast<SPSTREAMFORMAT>(SPSF_8kHz8BitMono + dwIndex);
          }

          case WAVE_FORMAT_DSPGROUP_TRUESPEECH:
          {
            return SPSF_TrueSpeech_8kHz1BitMono;
          }

          case WAVE_FORMAT_ALAW: // fall through
          case WAVE_FORMAT_MULAW:
          case WAVE_FORMAT_ADPCM:
          {
            switch (m_pCoMemWaveFormatEx->nChannels)
            {
              case 1:
                break;
              case 2:
                dwIndex |= 1;
                break;
              default:
                return SPSF_ExtendedAudioFormat;
            }

            if(m_pCoMemWaveFormatEx->wFormatTag == WAVE_FORMAT_ADPCM)
            {
                if(m_pCoMemWaveFormatEx->wBitsPerSample != 4)
                {
                    return SPSF_ExtendedAudioFormat;
                }
            }
            else if(m_pCoMemWaveFormatEx->wBitsPerSample != 8)
            {
                return SPSF_ExtendedAudioFormat;
            }

            switch (m_pCoMemWaveFormatEx->nSamplesPerSec)
            {
              case 44100:
                dwIndex += 2;   // Fall through
              case 22050:
                dwIndex += 2;   // Fall through
              case 11025:
                dwIndex += 2;   // Fall through
              case 8000:
                break;
              default:
                return SPSF_ExtendedAudioFormat;
            }

            switch( m_pCoMemWaveFormatEx->wFormatTag )
            {
              case WAVE_FORMAT_ALAW:
                return static_cast<SPSTREAMFORMAT>(SPSF_CCITT_ALaw_8kHzMono + dwIndex);
              case WAVE_FORMAT_MULAW:
                return static_cast<SPSTREAMFORMAT>(SPSF_CCITT_uLaw_8kHzMono + dwIndex);
              case WAVE_FORMAT_ADPCM:
                return static_cast<SPSTREAMFORMAT>(SPSF_ADPCM_8kHzMono + dwIndex);
            }
          }

          case WAVE_FORMAT_GSM610:
          {
            if( m_pCoMemWaveFormatEx->nChannels != 1 )
            {
                return SPSF_ExtendedAudioFormat;
            }

            switch (m_pCoMemWaveFormatEx->nSamplesPerSec)
            {
              case 44100:
                dwIndex = 3;
                break;
              case 22050:
                dwIndex = 2;
                break;
              case 11025:
                dwIndex = 1;
                break;
              case 8000:
                dwIndex = 0;
                break;
              default:
                return SPSF_ExtendedAudioFormat;
            }

            return static_cast<SPSTREAMFORMAT>(SPSF_GSM610_8kHzMono + dwIndex);
          }

          default:
            return SPSF_ExtendedAudioFormat;
            break;
        }
    }

    void DetachTo(CSpStreamFormat & Other)
    {
        ::CoTaskMemFree(Other.m_pCoMemWaveFormatEx);
        Other.m_guidFormatId = m_guidFormatId;
        Other.m_pCoMemWaveFormatEx = m_pCoMemWaveFormatEx;
        m_pCoMemWaveFormatEx = NULL;
        memset(&m_guidFormatId, 0, sizeof(m_guidFormatId));
    }

    void DetachTo(GUID * pFormatId, WAVEFORMATEX ** ppCoMemWaveFormatEx)
    {
        *pFormatId = m_guidFormatId;
        *ppCoMemWaveFormatEx = m_pCoMemWaveFormatEx;
        m_pCoMemWaveFormatEx = NULL;
        memset(&m_guidFormatId, 0, sizeof(m_guidFormatId));
    }

    HRESULT CopyTo(GUID * pFormatId, WAVEFORMATEX ** ppCoMemWFEX) const
    {
        HRESULT hr = S_OK;
        *pFormatId = m_guidFormatId;
        if (m_pCoMemWaveFormatEx)
        {
            hr = CoMemCopyWFEX(m_pCoMemWaveFormatEx, ppCoMemWFEX);
            if (FAILED(hr))
            {
                memset(pFormatId, 0, sizeof(*pFormatId));
            }
        }
        else
        {
            *ppCoMemWFEX = NULL;
        }
        return hr;
    }

    HRESULT CopyTo(CSpStreamFormat & Other) const
    {
        ::CoTaskMemFree(Other.m_pCoMemWaveFormatEx);
        return CopyTo(&Other.m_guidFormatId, &Other.m_pCoMemWaveFormatEx);
    }
    
    HRESULT AssignFormat(const CSpStreamFormat & Src)
    {
        return Src.CopyTo(*this);
    }


    HRESULT ParamValidateCopyTo(GUID * pFormatId, WAVEFORMATEX ** ppCoMemWFEX) const
    {
        if ((pFormatId == NULL) || (ppCoMemWFEX == NULL))
        {
            return E_POINTER;
        }
        return CopyTo(pFormatId, ppCoMemWFEX);
    }

    BOOL operator==(const CSpStreamFormat & Other) const
    {
        return IsEqual(Other.m_guidFormatId, Other.m_pCoMemWaveFormatEx);
    }
    BOOL operator!=(const CSpStreamFormat & Other) const
    {
        return !IsEqual(Other.m_guidFormatId, Other.m_pCoMemWaveFormatEx);
    }

    ULONG SerializeSize() const
    {
        ULONG cb = sizeof(ULONG) + sizeof(m_guidFormatId);
        if (m_pCoMemWaveFormatEx)
        {
            if (m_pCoMemWaveFormatEx->cbSize != 0 && m_pCoMemWaveFormatEx->wFormatTag == WAVE_FORMAT_PCM)
            {
                _ASSERT(!"PCM wave format");
                SetLastError(ERROR_INVALID_STATE);
                return 0;
            }
            cb += sizeof(WAVEFORMATEX) + m_pCoMemWaveFormatEx->cbSize + 3;  // Add 3 to round up
            cb -= cb % 4;                                                   // Round to DWORD
        }
        return cb;
    }

    ULONG Serialize(BYTE * pBuffer) const
    {
        ULONG cb = SerializeSize();
        *((UNALIGNED ULONG *)pBuffer) = cb;
        pBuffer += sizeof(ULONG);
        *((UNALIGNED GUID *)pBuffer) = m_guidFormatId;
        if (m_pCoMemWaveFormatEx)
        {
            pBuffer += sizeof(m_guidFormatId);
            memcpy(pBuffer, m_pCoMemWaveFormatEx, sizeof(WAVEFORMATEX) + m_pCoMemWaveFormatEx->cbSize);
        }
        return cb;
    }

    HRESULT Deserialize(const BYTE * pBuffer, ULONG * pcbUsed)
    {
        HRESULT hr = S_OK;

        // check pointer to pBuffer for size value
        if(pBuffer == NULL || pcbUsed == NULL)
        {
            return E_INVALIDARG;
        }

        *pcbUsed = *((UNALIGNED ULONG *)pBuffer);
 
        // check complete pBuffer from start
        if(*pcbUsed < sizeof(GUID) + sizeof(ULONG))
        {
            return E_INVALIDARG;
        }

        pBuffer += sizeof(ULONG);

        ::CoTaskMemFree(m_pCoMemWaveFormatEx);
        m_pCoMemWaveFormatEx = NULL;

        memcpy(&m_guidFormatId, pBuffer, sizeof(GUID));
        if (*pcbUsed > sizeof(GUID) + sizeof(ULONG))
        {
            pBuffer += sizeof(m_guidFormatId);
            hr = CoMemCopyWFEX((const WAVEFORMATEX *)pBuffer, &m_pCoMemWaveFormatEx);
            if (FAILED(hr))
            {
                m_guidFormatId = GUID_NULL;
            }
        }
        return hr;
    }

};



// Return the default codepage given a LCID.
// Note some of the newer locales do not have associated Windows codepages.  For these, we return UTF-8.

inline UINT SpCodePageFromLcid(LCID lcid)
{
    WCHAR achCodePage[6];

    return (0 != GetLocaleInfoW(lcid, LOCALE_IDEFAULTANSICODEPAGE, achCodePage, sizeof(achCodePage) / sizeof(*achCodePage))) ? _wtoi(achCodePage) : 65001;
}


inline HRESULT SPBindToFile( LPCWSTR pFileName, SPFILEMODE eMode, ISpStream ** ppStream,
                            const GUID * pFormatId = NULL, const WAVEFORMATEX * pWaveFormatEx = NULL,
                            ULONGLONG ullEventInterest = SPFEI_ALL_EVENTS)
{
    HRESULT hr = ::CoCreateInstance(CLSID_SpStream, NULL, CLSCTX_ALL, __uuidof(*ppStream), (void **)ppStream);
    if (SUCCEEDED(hr))
    {
        hr = (*ppStream)->BindToFile(pFileName, eMode, pFormatId, pWaveFormatEx, ullEventInterest);
        if (FAILED(hr))
        {
            (*ppStream)->Release();
            *ppStream = NULL;
        }
    }
    return hr;
} /* SPBindToFile */

inline HRESULT SPBindToFile( const CHAR * pFileName, SPFILEMODE eMode, ISpStream** ppStream, 
                             const GUID * pFormatId = NULL, const WAVEFORMATEX * pWaveFormatEx = NULL,
                             ULONGLONG ullEventInterest = SPFEI_ALL_EVENTS)
{
    WCHAR szWcharFileName[MAX_PATH];
    ::MultiByteToWideChar(CP_ACP, 0, pFileName, -1, szWcharFileName, sp_countof(szWcharFileName));
    return SPBindToFile(szWcharFileName, eMode, ppStream, pFormatId, pWaveFormatEx, ullEventInterest);
}

/****************************************************************************
* CheckStringSizeBytes *
*--------------*
*   Description:
*       Checks a string is not longer that the supplied size {IN BYTES}, including
*       the terminating char. If pcb is non-null then it is set to the size of 
*       the string {IN BYTES}, including the terminating char.
*
*****************************************************************************/
inline HRESULT CheckStringSizeBytes(WCHAR const * psz, size_t cbMax, size_t* pcb)
{
    HRESULT hr = S_OK;
    size_t cchMax = cbMax / sizeof(WCHAR);

    while (cchMax && (*psz != L'\0'))
    {
        psz++;
        cchMax--;
    }

    if (cchMax == 0)
    {
        // the string is longer than cchMax
        hr = E_INVALIDARG;
    }

    if (SUCCEEDED(hr) && pcb)
    {
        *pcb = cbMax - (cchMax - 1) * sizeof(WCHAR);
    }

    return hr;
}

/****************************************************************************
* SpClearEvent *
*--------------*
*   Description:
*       Helper function that can be used by clients that do not use the CSpEvent
*   class.
*
*   Returns:
*
*****************************************************************************/

inline void SpClearEvent(SPEVENT * pe)
{
    if( pe->elParamType != SPEI_UNDEFINED)
    {
        if( pe->elParamType == SPET_LPARAM_IS_POINTER ||
            pe->elParamType == SPET_LPARAM_IS_STRING)
        {
            ::CoTaskMemFree((void *)pe->lParam);
        }
        else if (pe->elParamType == SPET_LPARAM_IS_TOKEN ||
               pe->elParamType == SPET_LPARAM_IS_OBJECT)
        {
            ((IUnknown*)pe->lParam)->Release();
        }
    }
    memset(pe, 0, sizeof(*pe));
}

/****************************************************************************
* SpInitEvent *
*-------------*
*   Description:
*
*   Returns:
*
*****************************************************************************/

inline void SpInitEvent(SPEVENT * pe)
{
    memset(pe, 0, sizeof(*pe));
}

/****************************************************************************
* SpEventSerializeSize *
*----------------------*
*   Description:
*       Computes the required size of a buffer to serialize an event.  The caller
*   must specify which type of serialized event is desired -- either SPSERIALIZEDEVENT
*   or SPSERIALIZEDEVENT64.    
*
*   Returns:
*       Size in bytes required to seriailze the event.
*
****************************************************************************/

template <class T>
inline ULONG SpEventSerializeSize(const SPEVENT * pEvent)
{
    ULONG ulSize = sizeof(T);

    if( ( pEvent->elParamType == SPET_LPARAM_IS_POINTER ) && pEvent->lParam )
    {
        ulSize += ULONG(pEvent->wParam);
    }
    else if ((pEvent->elParamType == SPET_LPARAM_IS_STRING) && pEvent->lParam != NULL)
    {
        // Would be better to check for overflow of string length.
        ulSize += ((ULONG) wcslen((WCHAR*)pEvent->lParam) + 1) * sizeof( WCHAR );
    }
    else if( pEvent->elParamType == SPET_LPARAM_IS_TOKEN )
    {
        CSpDynamicString dstrObjectId;
        if( ((ISpObjectToken*)(pEvent->lParam))->GetId( &dstrObjectId ) == S_OK )
        {
            ulSize += (dstrObjectId.Length() + 1) * sizeof( WCHAR );
        }
        else
        {
            return 0;
        }
    }
    // Round up to nearest DWORD
    ulSize += 3;
    ulSize -= ulSize % 4;
    return ulSize;
}

/****************************************************************************
* SpSerializedEventSize *
*-----------------------*
*   Description:
*       Returns the size, in bytes, used by a serialized event.  The caller can
*   pass a pointer to either a SPSERIAILZEDEVENT or SPSERIALIZEDEVENT64 structure.
*
*   Returns:
*       Number of bytes used by serizlied event
*
*****************************************************************************/

template <class T>
inline ULONG SpSerializedEventSize(const T * pSerEvent)
{
    ULONG ulSize = sizeof(T);

    if( ( pSerEvent->elParamType == SPET_LPARAM_IS_POINTER ) && pSerEvent->SerializedlParam )
    {
        ulSize += ULONG(pSerEvent->SerializedwParam);
    }
    else if ((pSerEvent->elParamType == SPET_LPARAM_IS_STRING || pSerEvent->elParamType == SPET_LPARAM_IS_TOKEN) &&
             pSerEvent->SerializedlParam != NULL)
    {
        ulSize += ((ULONG)wcslen((WCHAR*)(pSerEvent + 1)) + 1) * sizeof( WCHAR );
    }
    // Round up to nearest DWORD
    ulSize += 3;
    ulSize -= ulSize % 4;
    return ulSize;
}


/*** CSpEvent helper class
*
*/
class CSpEvent : public SPEVENT
{
public:
    CSpEvent()
    {
        SpInitEvent(this);
    }
    ~CSpEvent()
    {
        SpClearEvent(this);
    }
    // If you need to take the address of a CSpEvent that is not const, use the AddrOf() method
    // which will do debug checking of parameters.  If you encounter this problem when calling
    // GetEvents from an event source, you may want to use the GetFrom() method of this class.
    const SPEVENT * operator&()
        {
                return this;
        }
    CSpEvent * AddrOf()
    {
        // Note:  This method does not ASSERT since we assume the caller knows what they are doing.
        return this;
    }
    void Clear()
    {
        SpClearEvent(this);
    }
    HRESULT CopyTo(SPEVENT * pDestEvent) const
    {
        memcpy(pDestEvent, this, sizeof(*pDestEvent));
        if ((elParamType == SPET_LPARAM_IS_POINTER) && lParam)
        {
            pDestEvent->lParam = (LPARAM)::CoTaskMemAlloc(wParam);
            if (pDestEvent->lParam)
            {
                memcpy((void *)pDestEvent->lParam, (void *)lParam, wParam);
            }
            else
            {
                pDestEvent->eEventId = SPEI_UNDEFINED;
                return E_OUTOFMEMORY;
            }
        }
        else if (elParamType == SPET_LPARAM_IS_STRING && lParam != NULL)
        {
            size_t cLen = wcslen((WCHAR*)lParam) + 1;
            pDestEvent->lParam = (LPARAM)::CoTaskMemAlloc(cLen * sizeof(WCHAR));
            if (pDestEvent->lParam)
            {
                StringCchCopyW ((WCHAR*)pDestEvent->lParam, cLen, (WCHAR*)lParam);
            }
            else
            {
                pDestEvent->eEventId = SPEI_UNDEFINED;
                return E_OUTOFMEMORY;
            }
        }
        else if (elParamType == SPET_LPARAM_IS_TOKEN ||
               elParamType == SPET_LPARAM_IS_OBJECT)
        {
            ((IUnknown*)lParam)->AddRef();
        }
        return S_OK;
    }

    HRESULT GetFrom(ISpEventSource * pEventSrc)
    {
        SpClearEvent(this);
        return pEventSrc->GetEvents(1, this, NULL);
    }
    HRESULT CopyFrom(const SPEVENT * pSrcEvent)
    {
        SpClearEvent(this);
        return static_cast<const CSpEvent *>(pSrcEvent)->CopyTo(this);
    }
    void Detach(SPEVENT * pDestEvent = NULL)
    {
        if (pDestEvent)
        {
            memcpy(pDestEvent, this, sizeof(*pDestEvent));
        }
        memset(this, 0, sizeof(*this));
    }

    template <class T>
    ULONG SerializeSize() const
    {
        return SpEventSerializeSize<T>(this);
    }

    // Call this method with either SPSERIALIZEDEVENT or SPSERIALIZEDEVENT64
    template <class T>
    HRESULT Serialize(T * pSerEvent) const
    {
        if (elParamType == SPET_LPARAM_IS_OBJECT)
        {
            _ASSERT(elParamType != SPET_LPARAM_IS_OBJECT);
            return E_UNEXPECTED;
        }

        HRESULT hr = S_OK;

        pSerEvent->eEventId = this->eEventId;
        pSerEvent->elParamType = this->elParamType;
        pSerEvent->ulStreamNum = this->ulStreamNum;
        pSerEvent->ullAudioStreamOffset = this->ullAudioStreamOffset;
        pSerEvent->SerializedwParam = static_cast<ULONG>(this->wParam);
        pSerEvent->SerializedlParam = static_cast<LONG>(this->lParam);
        if (lParam)
        {
            switch(elParamType)
            {
            case SPET_LPARAM_IS_POINTER:
                memcpy(pSerEvent + 1, (void *)lParam, wParam);
                pSerEvent->SerializedlParam = sizeof(T);
                break;

            case SPET_LPARAM_IS_STRING:
                StringCchCopyW ((WCHAR *)(pSerEvent + 1), wcslen ((WCHAR*) lParam) + 1, (WCHAR*)lParam);
                pSerEvent->SerializedlParam = sizeof(T);
                break;

            case SPET_LPARAM_IS_TOKEN:
                {
                    CSpDynamicString dstrObjectId;
                    hr = ((ISpObjectToken*)lParam)->GetId( &dstrObjectId );
                    if( SUCCEEDED( hr ) )
                    {
                        pSerEvent->SerializedwParam = (dstrObjectId.Length() + 1) * sizeof( WCHAR );;
                        memcpy( pSerEvent + 1, (void *)dstrObjectId.m_psz, static_cast<ULONG>(pSerEvent->SerializedwParam) );
                    }
                    pSerEvent->SerializedlParam = sizeof(T);
                }
                break;

            default:
                break;
            }
        }
        return hr;
    }

    template <class T>
    HRESULT Serialize(T ** ppCoMemSerEvent, ULONG * pcbSerEvent) const 
    {
        *pcbSerEvent = SpEventSerializeSize<T>(this);
        if (*pcbSerEvent == 0)
        {
            return E_FAIL;
        }
        *ppCoMemSerEvent = (T *)::CoTaskMemAlloc(*pcbSerEvent);
        if (*ppCoMemSerEvent)
        {
            return Serialize(*ppCoMemSerEvent);
        }
        else
        {
            *pcbSerEvent = 0;
            return E_OUTOFMEMORY;
        }
    }


    // Call this method with either SPSERIALIZEDEVENT or SPSERIALIZEDEVENT64
    template <class T>
    HRESULT Deserialize(const T * pSerEvent, ULONG * pcbUsed = NULL, ULONG cbMaxLength = 0xFFFF)
    {
        Clear();
        HRESULT hr = S_OK;
        const UNALIGNED T * pTemp = pSerEvent;
        if (cbMaxLength < sizeof(*pTemp))
        {
            return E_INVALIDARG;
        }
        size_t cbExtraSize = cbMaxLength - sizeof(*pTemp);

        this->eEventId = pTemp->eEventId;
        this->elParamType = pTemp->elParamType;
        this->ulStreamNum = pTemp->ulStreamNum;
        this->ullAudioStreamOffset = pTemp->ullAudioStreamOffset;
        this->wParam = static_cast<WPARAM>(pTemp->SerializedwParam);
        this->lParam = static_cast<LPARAM>(pTemp->SerializedlParam);


        if (pTemp->SerializedlParam)
        {
            size_t cbAlloc = 0;
            switch (pTemp->elParamType)
            {
            case SPET_LPARAM_IS_POINTER:
                cbAlloc = static_cast<ULONG>(wParam);
                if (cbAlloc > cbExtraSize)
                {
                    hr = E_INVALIDARG;
                }
                break;

            case SPET_LPARAM_IS_STRING:
                hr = CheckStringSizeBytes((WCHAR *)(pTemp + 1), cbExtraSize, &cbAlloc);
                break;

            case SPET_LPARAM_IS_TOKEN:
                {
                    hr = CheckStringSizeBytes((WCHAR *)(pTemp + 1), cbExtraSize, NULL);
                    if (SUCCEEDED(hr))
                    {
                        hr = SpGetTokenFromId( (const WCHAR*)(pTemp + 1), (ISpObjectToken **)&lParam );
                        wParam = 0;
                    }
                }
                break;
            case SPET_LPARAM_IS_UNDEFINED:
                break;
            case SPET_LPARAM_IS_OBJECT:
                hr = E_INVALIDARG;
                break;
            default:
                hr = E_INVALIDARG;
                break;
            }

            if (SUCCEEDED(hr) && cbAlloc)
            {
                void * pvBuff = ::CoTaskMemAlloc(cbAlloc);
                this->lParam = (LPARAM)pvBuff;
                if (pvBuff)
                {
                    memcpy(pvBuff, pTemp + 1, cbAlloc);
                }
                else
                {
                    hr = E_OUTOFMEMORY;
                }
            }
        }

        if( SUCCEEDED( hr ) && pcbUsed )
        {
            *pcbUsed = SpEventSerializeSize<T>(this);
            if(*pcbUsed == 0)
            {
                hr = E_FAIL;
            }
        }

        // Reset the data structure on failure.  Otherwise, the destructor may AV.
        if (FAILED(hr))
        {
            memset(this, 0, sizeof(*this));
        }

        return hr;
    }

    //
    //  Helpers for access to events.  Performs run-time checks in debug and casts
    //  data to the appropriate types
    //
    SPPHONEID Phoneme() const 
    {
        _ASSERT(eEventId == SPEI_PHONEME);
        return (SPPHONEID)LOWORD(lParam);
    }
    SPVISEMES Viseme() const 
    {
        _ASSERT(eEventId == SPEI_VISEME);
        return (SPVISEMES)LOWORD(lParam);
    }
    ULONG InputWordPos() const
    {
        _ASSERT(eEventId == SPEI_WORD_BOUNDARY);
        return ULONG(lParam);
    }
    ULONG InputWordLen() const 
    {
        _ASSERT(eEventId == SPEI_WORD_BOUNDARY);
        return ULONG(wParam);
    }
    ULONG InputSentPos() const
    {
        _ASSERT(eEventId == SPEI_SENTENCE_BOUNDARY);
        return ULONG(lParam);
    }
    ULONG InputSentLen() const 
    {
        _ASSERT(eEventId == SPEI_SENTENCE_BOUNDARY);
        return ULONG(wParam);
    }
    ISpObjectToken * ObjectToken() const
    {
        _ASSERT(elParamType == SPET_LPARAM_IS_TOKEN);
        return (ISpObjectToken *)lParam;
    }
    ISpObjectToken * VoiceToken() const     // More explicit check than ObjectToken()
    {
        _ASSERT(eEventId == SPEI_VOICE_CHANGE);
        return ObjectToken();
    }
    BOOL PersistVoiceChange() const
    {
        _ASSERT(eEventId == SPEI_VOICE_CHANGE);
        return (BOOL)wParam;
    }
    IUnknown * Object() const
    {
        _ASSERT(elParamType == SPET_LPARAM_IS_OBJECT);
        return (IUnknown*)lParam;
    }
    ISpRecoResult * RecoResult() const
    {
        return (ISpRecoResult *)Object();
    }

#if _SAPI_VER >= 0x053
    ULONG RetainedAudioSize() const
    {
        _ASSERT(eEventId == SPEI_SR_RETAINEDAUDIO);
        return ULONG(wParam);
    }

    ISpStreamFormat * RetainedAudioStream() const
    {
        _ASSERT(eEventId == SPEI_SR_RETAINEDAUDIO);
        return (ISpStreamFormat *)Object();
    }
#endif

    BOOL IsPaused()
    {
        return (BOOL)(wParam & SPREF_AutoPause);
    }
    BOOL IsEmulated()
    {
        return (BOOL)(wParam & SPREF_Emulated);
    }

#if _SAPI_VER >= 0x053
    BOOL IsSMLTimeout()
    {
        return (BOOL)(wParam & SPREF_SMLTimeout);
    }
#endif

    const WCHAR * String() const
    {
        _ASSERT(elParamType == SPET_LPARAM_IS_STRING);
        return (const WCHAR*)lParam;
    }
    const WCHAR * BookmarkName() const
    {
        _ASSERT(eEventId == SPEI_TTS_BOOKMARK);
        return String();
    }
    const WCHAR * RequestTypeOfUI() const
    {
        _ASSERT(eEventId == SPEI_REQUEST_UI);
        return String();
    }
    SPRECOSTATE RecoState() const
    {
        _ASSERT(eEventId == SPEI_RECO_STATE_CHANGE);
        return static_cast<SPRECOSTATE>(wParam);
    }
    const WCHAR * PropertyName() const
    {
        _ASSERT((eEventId == SPEI_PROPERTY_NUM_CHANGE && elParamType == SPET_LPARAM_IS_STRING) ||
                     (eEventId == SPEI_PROPERTY_STRING_CHANGE && elParamType == SPET_LPARAM_IS_POINTER));
        // Note: Don't use String() method here since in the case of string attributes, the elParamType
        // field specifies LPARAM_IS_POINTER, but the attribute name IS the first string in this buffer
        return (const WCHAR*)lParam;
    }
    const LONG PropertyNumValue() const 
    {
        _ASSERT(eEventId == SPEI_PROPERTY_NUM_CHANGE);
        return static_cast<LONG>(wParam);
    }
    const WCHAR * PropertyStringValue() const
    {
        // Search for the first NULL and return pointer to the char past it.
        _ASSERT(eEventId == SPEI_PROPERTY_STRING_CHANGE);
        const WCHAR * psz = (const WCHAR *)lParam;
        for (; *psz; psz++) {}
        return psz + 1;
    }
    SPINTERFERENCE Interference() const
    {
        _ASSERT(eEventId == SPEI_INTERFERENCE);
        return static_cast<SPINTERFERENCE>(lParam);
    }
    HRESULT EndStreamResult() const
    {
        _ASSERT(eEventId == SPEI_END_SR_STREAM);
        return static_cast<HRESULT>(lParam);
    }
    BOOL InputStreamReleased() const
    {
        _ASSERT(eEventId == SPEI_END_SR_STREAM);
        return (wParam & SPESF_STREAM_RELEASED) ? TRUE : FALSE;
    }
};

class CSpPhrasePtr
{
public:
    SPPHRASE    *   m_pPhrase;
    CSpPhrasePtr() : m_pPhrase(NULL) {}
    CSpPhrasePtr(ISpPhrase * pPhraseObj, HRESULT * phr)
    {
        *phr = pPhraseObj->GetPhrase(&m_pPhrase);
    }
    ~CSpPhrasePtr()
    {
        ::CoTaskMemFree(m_pPhrase);
    }
        //The assert on operator& usually indicates a bug.  If this is really
        //what is needed, however, take the address of the m_pPhrase member explicitly.
        SPPHRASE ** operator&()
        {
            _ASSERT(m_pPhrase == NULL);
            return &m_pPhrase;
        }
    operator SPPHRASE *() const
    {
        return m_pPhrase;
    }
        SPPHRASE & operator*() const
        {
                _ASSERT(m_pPhrase);
                return *m_pPhrase;
        }
    SPPHRASE * operator->() const
    {
        return m_pPhrase;
    }
        bool operator!() const
        {
                return (m_pPhrase == NULL);
        }
    void Clear()
    {
        if (m_pPhrase)
        {
            ::CoTaskMemFree(m_pPhrase);
            m_pPhrase = NULL;
        }
    }
    HRESULT GetFrom(ISpPhrase * pPhraseObj)
    {
        Clear();
        return pPhraseObj->GetPhrase(&m_pPhrase);
    }
};


template <class T>
class CSpCoTaskMemPtr
{
public:
    T       * m_pT;
    CSpCoTaskMemPtr() : m_pT(NULL) {}
    CSpCoTaskMemPtr(void * pv) : m_pT((T *)pv) {}
    CSpCoTaskMemPtr(ULONG cElements, HRESULT * phr)
    {
        m_pT = (T *)::CoTaskMemAlloc(cElements * sizeof(T));
        *phr = m_pT ? S_OK : E_OUTOFMEMORY;
    }
    ~CSpCoTaskMemPtr()
    {
        ::CoTaskMemFree(m_pT);
    }
    void Clear()
    {
        if (m_pT)
        {
            ::CoTaskMemFree(m_pT);
            m_pT = NULL;
        }
    }
    HRESULT Alloc(ULONG cArrayElements = 1)
    {
        T * pNewT = (T *)::CoTaskMemRealloc(m_pT, sizeof(T) * cArrayElements);
        if (pNewT == NULL)
        {
            // Keep the old behavior, the pointer becomes NULL if we fail above
            Clear();
            return E_OUTOFMEMORY;
        }
        else
        {
            m_pT = pNewT;
            return S_OK;
        }
    }
    void Attach(void * pv)
    {
        Clear();
        m_pT = (T *)pv;
    }
    T * Detatch()
    {
        T * pT = m_pT;
        m_pT = NULL;
        return pT;
    }
        //The assert on operator& usually indicates a bug.  If this is really
        //what is needed, however, take the address of the m_pT member explicitly.
        T ** operator&()
        {
            _ASSERT(m_pT == NULL);
            return &m_pT;
        }
    T * operator->()
    {
        _ASSERT(m_pT != NULL);
        return m_pT;
    }
    operator T *()
    {
        return m_pT;
    }
        bool operator!() const
        {
                return (m_pT == NULL);
        }
};

/**** Helper function used to create a new phrase object from an array of
    test words. Each word in the string is converted to a phrase element.
    This is useful to create a phrase to pass to the EmulateRecognition method.
    The method can convert standard words as well as words with the
    "/display_text/lexical_form/pronounciation;" word format.
    You can also specify the DisplayAttributes for each element if desired. 
    If prgDispAttribs is NULL then the DisplayAttribs for each element default to 
    SPAF_ONE_TRAILING_SPACE. ****/
inline HRESULT CreatePhraseFromWordArray(const WCHAR ** ppWords, ULONG cWords,
                             SPDISPLYATTRIBUTES * prgDispAttribs,
                             ISpPhraseBuilder **ppResultPhrase,
                             LANGID LangId = 0,
                             CComPtr<ISpPhoneConverter> cpPhoneConv = NULL,
                             BOOL fNoSpecialCharacters = FALSE)
{
    HRESULT hr = S_OK;

    if ( cWords == 0 || ppWords == NULL )
    {
        return E_INVALIDARG;
    }

    size_t   cTotalChars = 0;
    ULONG    i;
    WCHAR** pStringPtrArray = (WCHAR**)::CoTaskMemAlloc( cWords * sizeof(WCHAR *));
    if ( !pStringPtrArray )
    {
        return E_OUTOFMEMORY;
    }
    for (i = 0; i < cWords; i++)
    {
        cTotalChars += wcslen(ppWords[i])+1;
    }

    if ((ULONG) cTotalChars != cTotalChars)
    {
        ::CoTaskMemFree(pStringPtrArray);
        return E_OUTOFMEMORY;
    }

    CSpDynamicString dsText((ULONG) cTotalChars);
    if(dsText.m_psz == NULL)
    {
        ::CoTaskMemFree(pStringPtrArray);
        return E_OUTOFMEMORY;
    }
    CSpDynamicString dsPhoneId((ULONG) cTotalChars);
    if(dsPhoneId.m_psz == NULL)
    {
        ::CoTaskMemFree(pStringPtrArray);
        return E_OUTOFMEMORY;
    }
    SPPHONEID* pphoneId = dsPhoneId;

    SPPHRASE Phrase;
    memset(&Phrase, 0, sizeof(Phrase));
#ifdef SP_SPPHRASESIZE_500
        Phrase.cbSize = SP_SPPHRASESIZE_500;
#else
        Phrase.cbSize = sizeof(Phrase);
#endif

    if(LangId == 0)
    {
        LangId = SpGetUserDefaultUILanguage();
    }

    SPPHRASEELEMENT *pPhraseElement = new SPPHRASEELEMENT[cWords];
    if(pPhraseElement == NULL)
    {
        ::CoTaskMemFree(pStringPtrArray);
        return E_OUTOFMEMORY;
    }
    memset(pPhraseElement, 0, sizeof(SPPHRASEELEMENT) * cWords);
    
    WCHAR * pText = dsText;
    for (i = 0; SUCCEEDED(hr) && i < cWords; i++)
    {
        WCHAR *p = pText;
        pStringPtrArray[i] = pText;

        if (ppWords[i][0] == L'/' && !fNoSpecialCharacters)
        {
            const WCHAR *pszOriginalText = ppWords[i] + 1;
            
            //This is a compound word
            WCHAR* pszFirstPart = p;
            WCHAR* pszSecondPart = NULL;
            WCHAR* pszThirdPart = NULL;

            // Find the second separator slash.
            while (*pszOriginalText)
            {
                if (*pszOriginalText == L'\\')
                {
                    pszOriginalText++;
                }
                else if (*pszOriginalText == L';' || *pszOriginalText == L'/')
                {
                    break;
                }
                *p++ = *pszOriginalText++;
            }

            if (*pszOriginalText == L'/')
            {
                // We stopped at the second '/'
                *p = L'\0';
                pszSecondPart = ++p;
                pszOriginalText++;
                while (*pszOriginalText)
                {
                    if (*pszOriginalText == L'\\')
                    {
                        pszOriginalText++;
                    }
                    else if (*pszOriginalText == L';' || *pszOriginalText == L'/')
                    {
                        break;
                    }
                    *p++ = *pszOriginalText++;
                }
                if (*pszOriginalText == L'/')
                {
                    // We stopped at the third '/'
                    *p = L'\0';
                    pszThirdPart = ++p;
                    pszOriginalText++;
                }
                
            }

            WCHAR *pBound = (WCHAR*) pszOriginalText + wcslen(pszOriginalText);
            while (pszOriginalText < pBound && *pszOriginalText != L';')
            {
                *p++ = *pszOriginalText++;
            }

            *p = L'\0';
            pText = p + 1;


            pPhraseElement[i].pszDisplayText = pszFirstPart;
            pPhraseElement[i].pszLexicalForm = pszSecondPart ? pszSecondPart : pszFirstPart;

            if (pszThirdPart && *pszThirdPart != L'\0')
            {
                if(cpPhoneConv == NULL)
                {
                    hr = SpCreatePhoneConverter(LangId, NULL, NULL, &cpPhoneConv);
                    if(FAILED(hr))
                    {
                        break;
                    }
                }

                hr = cpPhoneConv->PhoneToId(pszThirdPart, pphoneId);
                if (SUCCEEDED(hr))
                {
                    pPhraseElement[i].pszPronunciation = pphoneId;
                    pphoneId += wcslen(pphoneId) + 1;
                }
            }
        }
        else
        {
            //It is the simple format, only have one form, use it for everything.
            StringCchCopyW ( pText, cTotalChars - (pText - dsText), ppWords[i] );
            pText += wcslen( p ) + 1;

            pPhraseElement[i].pszDisplayText = NULL;
            pPhraseElement[i].pszLexicalForm = p;
            pPhraseElement[i].pszPronunciation = NULL;
        }

        pPhraseElement[i].bDisplayAttributes = (BYTE)(prgDispAttribs ? prgDispAttribs[i] : SPAF_ONE_TRAILING_SPACE);
        pPhraseElement[i].RequiredConfidence = SP_NORMAL_CONFIDENCE;
        pPhraseElement[i].ActualConfidence =  SP_NORMAL_CONFIDENCE;
        pPhraseElement[i].SREngineConfidence = 1.0f; // Emulated results give confidence of 1.0
    }

    Phrase.Rule.ulCountOfElements = cWords;
    Phrase.Rule.SREngineConfidence = 1.0f;
    Phrase.pElements = pPhraseElement;
    Phrase.LangID = LangId;

    CComPtr<ISpPhraseBuilder> cpPhrase;
    if (SUCCEEDED(hr))
    {
        hr = cpPhrase.CoCreateInstance(CLSID_SpPhraseBuilder);
    }

    if (SUCCEEDED(hr))
    {
        hr = cpPhrase->InitFromPhrase(&Phrase);
    }
    if (SUCCEEDED(hr))
    {
        *ppResultPhrase = cpPhrase.Detach();
    }

    delete[] pPhraseElement;
    ::CoTaskMemFree(pStringPtrArray);

    return hr;
}

/**** Helper function used to create a new phrase object from a 
    test string. Each word in the string is converted to a phrase element.
    This is useful to create a phrase to pass to the EmulateRecognition method.
    The method can convert standard words as well as words with the
    "/display_text/lexical_form/pronounciation;" word format.
    If the emulation needs to match word sequence data (textbuffer) then
    the corresponding words need to be bracketed with '[' and ']' so they
    can be put into a single phrase element
    ****/
inline HRESULT CreatePhraseFromText(const WCHAR *pszOriginalText,
                             ISpPhraseBuilder **ppResultPhrase,
                             LANGID LangId = 0,
                             CComPtr<ISpPhoneConverter> cpPhoneConv = NULL,
                             BOOL fNoSpecialCharacters = FALSE)
{
    HRESULT hr = S_OK;

    //We first trim the input text
    CSpDynamicString dsText((ULONG)wcslen(pszOriginalText) + 1);
    if(dsText.m_psz == NULL)
    {
        return E_OUTOFMEMORY;
    }

    ULONG cWords = 0;

    // Set first array pointer (if *p).
    WCHAR *p = dsText;
    while (*pszOriginalText != L'\0')
    {
        // Skip leading white spaces
        while (iswspace(*pszOriginalText))
        {
            *p++ = L'\0';
            pszOriginalText++;
        }

        // Skip over word
        if (*pszOriginalText != L'\0')
        {
            cWords++;
            if ((*pszOriginalText == L'/') && !fNoSpecialCharacters)
            {
                // Skip all non-semicolon characters and unescape escaped characters
                while (*pszOriginalText != L'\0')
                {
                    if (*pszOriginalText == L'\\')
                    {
                        *p++ = *pszOriginalText++;
                    }
                    else if (*pszOriginalText == L';')
                    {
                        break;
                    }
                    *p++ = *pszOriginalText++;
                }
                if (*pszOriginalText == L';')
                {
                    *p++ = *pszOriginalText++;
                }
            }
            else
            {
                // Skip all non-whitespace characters
                while ((*pszOriginalText != L'\0') && !iswspace(*pszOriginalText))
                {
                    *p++ = *pszOriginalText++;
                }
            }
        }
    }
    *p = L'\0';

    WCHAR** pStringPtrArray = (WCHAR**)::CoTaskMemAlloc( cWords * sizeof(WCHAR *));
    if ( !pStringPtrArray )
    {
        hr = E_OUTOFMEMORY;
    }

    if ( SUCCEEDED( hr ) )
    {
        p = dsText;
        for (ULONG i=0; i<cWords; i++)
        {
            while (*p == L'\0') p++;
            pStringPtrArray[i] = p;
            p += wcslen(p);
        }

        hr = CreatePhraseFromWordArray((const WCHAR **)pStringPtrArray, cWords, NULL, ppResultPhrase, LangId, cpPhoneConv, fNoSpecialCharacters);

        ::CoTaskMemFree(pStringPtrArray);
    }
    return hr;
}

#endif /* This must be the last line in the file */

