#include "priv.h"

// Build this file only on Win9X or NT4
#ifdef DOWNLEVEL_PLATFORM

__inline LPWSTR StrCpyNXW(LPWSTR psz1, LPCWSTR psz2, int cchMax)
{
    ASSERT(psz1);
    ASSERT(psz2);

    if (0 < cchMax)
    {
        // Leave room for the null terminator
        while (0 < --cchMax)
        {
            if ( !(*psz1++ = *psz2++) ) {
                --psz1;
                break;
            }
        }

        if (0 == cchMax)
            *psz1 = L'\0';

        ASSERT(*psz1 == 0);
    }

    return psz1;
}

LPWSTR StrCpyNW(LPWSTR psz1, LPCWSTR psz2, int cchMax)
{
    StrCpyNXW(psz1, psz2, cchMax);
    return psz1;
}

LPWSTR StrCpyW(LPWSTR psz1, LPCWSTR psz2)
{
    LPWSTR psz = psz1;

    ASSERT(psz1);
    ASSERT(psz2);

    while (*psz1++ = *psz2++)
        ;

    return psz;
}

STDAPI_(DWORD)
RegData_AtoW(
    IN    LPCVOID pvData,
    IN    DWORD   dwSize,
    IN    DWORD   dwType,
    /*INOUT*/ LPDWORD pcbData
)
{
    DWORD dwRet = NO_ERROR;

    if (REG_SZ == dwType || REG_EXPAND_SZ == dwType || REG_MULTI_SZ == dwType)
    {
        DWORD cbData = pcbData ? *pcbData : -1;
        if (pvData)
        {
            // Allocate a temporary buffer to work with
            int cch = MultiByteToWideChar(CP_ACP, 0, (LPSTR)pvData, cbData, NULL, 0);
            LPWSTR pwszT = (LPWSTR)LocalAlloc(LPTR, CbFromCchW(cch));

            if (!pwszT)
                dwRet = ERROR_NOT_ENOUGH_MEMORY;
            else
            {
                if (CbFromCchW(cch) > dwSize)
                    dwRet = ERROR_MORE_DATA;
                else
                {
                    // Convert it to the temporary buffer
                    MultiByteToWideChar(CP_ACP, 0, (LPSTR)pvData, cbData, pwszT, cch);

                    // Copy it back to the output buffer
                    StrCpyW((LPWSTR)pvData, pwszT);
                }
                LocalFree(pwszT);
            }

            // If string data, make room for unicode 
            if (pcbData)
            {
                (*pcbData) = cch * sizeof(WCHAR);
            }
        }
        else if (pcbData)
        {
            // We don't have the data so guess (actual value may be less)
            // PERF: Does this need to be exact?  For now this seem sufficient. (Stevepro)
            (*pcbData) *= sizeof(WCHAR);
        }
    }
    return dwRet;
}


/*----------------------------------------------------------
Purpose: Recursively delete the key, including all child values
         and keys.  Mimics what RegDeleteKey does in Win95.

Returns: 
Cond:    --
*/
DWORD
DeleteKeyRecursively(
    IN HKEY   hkey, 
    IN LPCSTR pszSubKey)
{
    DWORD dwRet;
    HKEY hkSubKey;

    // Open the subkey so we can enumerate any children
    dwRet = RegOpenKeyExA(hkey, pszSubKey, 0, MAXIMUM_ALLOWED, &hkSubKey);
    if (ERROR_SUCCESS == dwRet)
    {
        DWORD   dwIndex;
        CHAR    szSubKeyName[MAX_PATH + 1];
        DWORD   cchSubKeyName = ARRAYSIZE(szSubKeyName);
        CHAR    szClass[MAX_PATH];
        DWORD   cbClass = ARRAYSIZE(szClass);

        // I can't just call RegEnumKey with an ever-increasing index, because
        // I'm deleting the subkeys as I go, which alters the indices of the
        // remaining subkeys in an implementation-dependent way.  In order to
        // be safe, I have to count backwards while deleting the subkeys.

        // Find out how many subkeys there are
        dwRet = RegQueryInfoKeyA(hkSubKey,
                                 szClass,
                                 &cbClass,
                                 NULL,
                                 &dwIndex, // The # of subkeys -- all we need
                                 NULL,
                                 NULL,
                                 NULL,
                                 NULL,
                                 NULL,
                                 NULL,
                                 NULL);

        if (NO_ERROR == dwRet)
        {
            // dwIndex is now the count of subkeys, but it needs to be
            // zero-based for RegEnumKey, so I'll pre-decrement, rather
            // than post-decrement.
            while (ERROR_SUCCESS == RegEnumKeyA(hkSubKey, --dwIndex, szSubKeyName, cchSubKeyName))
            {
                DeleteKeyRecursively(hkSubKey, szSubKeyName);
            }
        }

        RegCloseKey(hkSubKey);

        dwRet = RegDeleteKeyA(hkey, pszSubKey);
    }

    return dwRet;
}

/*----------------------------------------------------------
Purpose: Recursively delete the key, including all child values
         and keys.

Returns: 
Cond:    --
*/
STDAPI_(DWORD)
SHDeleteKeyA(
    IN HKEY   hkey, 
    IN LPCSTR pszSubKey)
{
    DWORD dwRet;

    if (g_bRunOnNT)
    {
        dwRet = DeleteKeyRecursively(hkey, pszSubKey);
    }
    else
    {
        // On Win95, RegDeleteKey does what we want
        dwRet = RegDeleteKeyA(hkey, pszSubKey);
    }

    return dwRet;
}


/*----------------------------------------------------------
Purpose: Recursively delete the key, including all child values
         and keys.

Returns: 
Cond:    --
*/
STDAPI_(DWORD)
SHDeleteKeyW(
    IN HKEY    hkey, 
    IN LPCWSTR pwszSubKey)
{
    DWORD dwRet;
    CHAR sz[MAX_PATH];

    WideCharToMultiByte(CP_ACP, 0, pwszSubKey, -1, sz, SIZECHARS(sz), NULL, NULL);

    if (g_bRunOnNT)
    {
        dwRet = DeleteKeyRecursively(hkey, sz);
    }
    else
    {
        // On Win95, RegDeleteKey does what we want
        dwRet = RegDeleteKeyA(hkey, sz);
    }

    return dwRet;
}


/*----------------------------------------------------------
Purpose: Behaves just like RegQueryValueEx, except if the
         data type is REG_EXPAND_SZ, then this goes ahead
         and expands out the string.  *pdwType will always
         be massaged to REG_SZ if this happens.

Returns: 
Cond:    --
*/
STDAPI_(DWORD)
SHQueryValueExA(
    IN     HKEY    hkey,
    IN     LPCSTR  pszValue,
    IN     LPDWORD lpReserved,
    OUT    LPDWORD pdwType,
    OUT    LPVOID  pvData,
    IN OUT LPDWORD pcbData)
{
    DWORD dwRet;
    DWORD cbSize;
    DWORD dwType;
    LPSTR lpsz;

    // Trying to get back data

    if (pcbData)
        cbSize = *pcbData;     // Size of output buffer
        
    dwRet = RegQueryValueExA(hkey, pszValue, lpReserved, &dwType,
                             pvData, &cbSize);

    // Normally, we'd be done with this.  But do some extra work
    // if this is an expandable string (something that has system
    // variables in it), or if we need to pad the buffer.

    if (NO_ERROR == dwRet)
    {
        // Note: on Win95, RegSetValueEx will always write the 
        // full string out, including the null terminator.  On NT,
        // it won't unless the write length was specified.  
        // Hence, we have the following check.

        // Pad the buffer, in case the string didn't have a null
        // terminator when it was stored?
        if (REG_SZ == dwType)
        {
            // Yes
            if (pvData && cbSize < *pcbData) 
            {
                LPSTR lpszData = pvData;
                lpszData[cbSize] = '\0';
            }
        }
        // Expand the string?
        else if (REG_EXPAND_SZ == dwType)
        {
            // Yes

            // Use a temporary buffer to expand
            if (pvData)
            {
                lpsz = (LPSTR)LocalAlloc(LPTR, *pcbData);    
                if ( !lpsz )
                    return ERROR_OUTOFMEMORY;

                cbSize = ExpandEnvironmentStringsA(pvData, lpsz, *pcbData);

                // NT screws up the cbSize returned...
                if (cbSize > 0)
                    cbSize = lstrlenA(lpsz) + 1;
                if (cbSize > 0 && cbSize <= *pcbData) 
                    lstrcpynA(pvData, lpsz, *pcbData);
                else
                    dwRet = GetLastError();
            }
            else
            {
                //
                // Find out the length of the expanded string
                // we have to call in and actually get the data to do this
                //
                CHAR szBuff[1];

                lpsz = (LPSTR)LocalAlloc(LPTR, cbSize);
                if (!lpsz)
                    return ERROR_OUTOFMEMORY;

                dwRet = RegQueryValueExA(hkey, pszValue, lpReserved, NULL,
                                         (LPBYTE)lpsz, &cbSize);

                if (NO_ERROR == dwRet)
                {
                    //  dummy buffer required...
                    DWORD cbExpand = ExpandEnvironmentStringsA(lpsz, szBuff, ARRAYSIZE(szBuff));
                    cbSize = max(cbExpand, cbSize);


                }

            }

            LocalFree(lpsz);

            // Massage dwType so that callers always see REG_SZ
            dwType = REG_SZ;
        }
    }

    if (pdwType)
        *pdwType = dwType;

    if (pcbData)
        *pcbData = cbSize;

    return dwRet;
}

#ifdef UNICODE
/*----------------------------------------------------------
Purpose: Behaves just like RegQueryValueEx, except if the
         data type is REG_EXPAND_SZ, then this goes ahead
         and expands out the string.  *pdwType will always
         be massaged to REG_SZ if this happens.

Returns: 
Cond:    --
*/
STDAPI_(DWORD)
SHQueryValueExW(
    IN     HKEY    hkey,
    IN     LPCWSTR pwszValue,
    IN     LPDWORD lpReserved,
    OUT    LPDWORD pdwType,
    OUT    LPVOID  pvData,
    IN OUT LPDWORD pcbData)
{
    DWORD dwRet;
    DWORD cbSize;
    DWORD dwType;
    LPWSTR lpsz;

    if ( !g_bRunOnNT )
    {
        CHAR szValue[MAX_PATH];
        LPSTR pszValue = NULL;
        DWORD dwOriginalSize = 0;

        if (pcbData)
            dwOriginalSize = *pcbData;

        if (pwszValue)
        {
            WideCharToMultiByte(CP_ACP, 0, pwszValue, -1, szValue, SIZECHARS(szValue), NULL, NULL);
            pszValue = szValue;
        }

        if (!pdwType)
            pdwType = &dwType;

        dwRet = SHQueryValueExA(hkey, pszValue, lpReserved, pdwType, pvData, pcbData);

        if (NO_ERROR == dwRet)
            dwRet = RegData_AtoW(pvData, dwOriginalSize, *pdwType, pcbData);   // Thunk data from ANSI->UNICODE if needed.
    }
    else
    {
        // Running on NT
        // Trying to get back data

        if (pcbData)
            cbSize = *pcbData;     // Size of output buffer
            
        dwRet = RegQueryValueExW(hkey, pwszValue, lpReserved, &dwType,
                                 pvData, &cbSize);

        // Normally, we'd be done with this.  But do some extra work
        // if this is an expandable string (something that has system
        // variables in it), or if we need to pad the buffer.

        if (NO_ERROR == dwRet)
        {
            // Note: on Win95, RegSetValueEx will always write the 
            // full string out, including the null terminator.  On NT,
            // it won't unless the write length was specified.  
            // Hence, we have the following check.

            // Pad the buffer, in case the string didn't have a null
            // terminator when it was stored?
            if (REG_SZ == dwType)
            {
                // Yes
                if (pvData && cbSize + sizeof(WCHAR) <= *pcbData) 
                {
                    LPWSTR lpszData = pvData;
                    lpszData[cbSize / sizeof(WCHAR)] = '\0';
                }
            }
            // Expand the string?
            else if (REG_EXPAND_SZ == dwType)
            {
                if (pvData)
                {
                    // Yes

                    // Use a temporary buffer to expand
                    lpsz = (LPWSTR)LocalAlloc(LPTR, *pcbData);    
                    if (!lpsz)
                        return ERROR_OUTOFMEMORY;

                    cbSize = CbFromCchW(ExpandEnvironmentStringsW(pvData, lpsz, *pcbData / sizeof(WCHAR)));
                    if (cbSize > 0 && cbSize <= *pcbData) 
                        StrCpyNW(pvData, lpsz, *pcbData / sizeof(WCHAR));
                    else
                        dwRet = GetLastError();
                }
                else
                {
                    //
                    // Find out the length of the expanded string
                    // we have to call in and actually get the data to do this
                    //
                    WCHAR szBuff[1];

                    // Find out the length of the expanded string
                    //
                    lpsz = (LPWSTR)LocalAlloc(LPTR, cbSize);
                    if (!lpsz)
                        return ERROR_OUTOFMEMORY;

                    dwRet = RegQueryValueExW(hkey, pwszValue, lpReserved, NULL,
                                             (LPBYTE)lpsz, &cbSize);

                    if (NO_ERROR == dwRet)
                    {
                        DWORD cbExpand = CbFromCchW(ExpandEnvironmentStringsW(lpsz, szBuff, 
                                                                      ARRAYSIZE(szBuff)));
                        cbSize = max(cbExpand, cbSize);
                    }
                }

                LocalFree(lpsz);

                // Massage dwType so that callers always see REG_SZ
                dwType = REG_SZ;
            }
        }

        if (pdwType)
            *pdwType = dwType;

        if (pcbData)
            *pcbData = cbSize;
    }

    return dwRet;
}
#endif //UNICODE

/*
 *  @doc    INTERNAL
 *
 *  @func   int | SHAnsiToUnicodeNativeCP |
 *
 *          Convert an ANSI string to a UNICODE string via the
 *          specified Windows code page.  If the source string is too large
 *          for the destination buffer, then as many characters as
 *          possible are copied.
 *
 *          The resulting output string is always null-terminated.
 *
 *  @parm   UINT | uiCP |
 *
 *          The code page in which to perform the conversion.
 *          This must be a Windows code page.
 *
 *  @parm   LPCSTR | pszSrc |
 *
 *          Source buffer containing ANSI string to be converted.
 *
 *  @parm   int | cchSrc |
 *
 *          Source buffer length, including terminating null.
 *
 *  @parm   LPWSTR | pwszDst |
 *
 *          Destination buffer to receive converted UNICODE string.
 *
 *  @parm   int | cwchBuf |
 *
 *          Size of the destination buffer in <t WCHAR>s.
 *
 *  @returns
 *
 *          On success, the number of characters copied to the output
 *          buffer is returned, including the terminating null.
 */

int
SHAnsiToUnicodeNativeCP(UINT uiCP,
                        LPCSTR pszSrc, int cchSrc,
                        LPWSTR pwszDst, int cwchBuf)
{
    int cwchRc = 0;             /* Assume failure */

    /*
     *  Checks the caller should've made.
     */
    ASSERT(IS_VALID_STRING_PTRA(pszSrc, -1));
    ASSERT(cchSrc == lstrlenA(pszSrc) + 1);
    ASSERT(IS_VALID_WRITE_BUFFER(pwszDst, WCHAR, cwchBuf));
    ASSERT(pszSrc != NULL);
    ASSERT(uiCP != 1200 && uiCP != 65000 && uiCP != 50000 && uiCP != 65001);
    ASSERT(pwszDst);
    ASSERT(cwchBuf);

    cwchRc = MultiByteToWideChar(uiCP, 0, pszSrc, cchSrc, pwszDst, cwchBuf);
    if (cwchRc) {
        /*
         *  The output buffer was big enough; no double-buffering
         *  needed.
         */
    } else if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
        /*
         *  The output buffer wasn't big enough.  Need to double-buffer.
         */

        int cwchNeeded = MultiByteToWideChar(uiCP, 0, pszSrc, cchSrc,
                                             NULL, 0);

        ASSERT(cwchRc == 0);        /* In case we fail later */
        if (cwchNeeded) {
            LPWSTR pwsz = (LPWSTR)LocalAlloc(LMEM_FIXED,
                                             cwchNeeded * SIZEOF(WCHAR));
            if (pwsz) {
                cwchRc = MultiByteToWideChar(uiCP, 0, pszSrc, cchSrc,
                                             pwsz, cwchNeeded);
                if (cwchRc) {
                    StrCpyNW(pwszDst, pwsz, cwchBuf);
                    cwchRc = cwchBuf;
                }
                LocalFree(pwsz);
            }
        }
    } else {
        /* Possibly unsupported code page */
        ASSERT(!"Unexpected error in MultiByteToWideChar");
    }

    return cwchRc;
}

int
SHAnsiToUnicodeCP_ACP(LPCSTR pszSrc, LPWSTR pwszDst, int cwchBuf)
{
    int cwchRc = 0;             /* Assume failure */

    ASSERT(IS_VALID_STRING_PTRA(pszSrc, -1));
    ASSERT(IS_VALID_WRITE_BUFFER(pwszDst, WCHAR, cwchBuf));

    /*
     *  Sanity check - NULL source string is treated as a null string.
     */
    if (pszSrc == NULL) {
        pszSrc = "";
    }

    /*
     *  Sanity check - Output buffer must be non-NULL and must be of
     *  nonzero size.
     */
    if (pwszDst && cwchBuf) {

        int cchSrc;

        pwszDst[0] = 0;         /* In case of error */

        cchSrc = lstrlenA(pszSrc) + 1;

        cwchRc = SHAnsiToUnicodeNativeCP(CP_ACP, pszSrc, cchSrc, pwszDst, cwchBuf);
    }

    return cwchRc;
}

/*
 *  @doc    EXTERNAL
 *
 *  @func   int | SHAnsiToUnicode |
 *
 *          Convert an ANSI string to a UNICODE string via the
 *          <c CP_ACP> code page.  If the source string is too large
 *          for the destination buffer, then as many characters as
 *          possible are copied.
 *
 *          The resulting output string is always null-terminated.
 *
 *  @parm   LPCSTR | pszSrc |
 *
 *          Source buffer containing ANSI string to be converted.
 *
 *  @parm   LPWSTR | pwszDst |
 *
 *          Destination buffer to receive converted UNICODE string.
 *
 *  @parm   int | cwchBuf |
 *
 *          Size of the destination buffer in <t WCHAR>s.
 *
 *  @returns
 *
 *          On success, the number of characters copied to the output
 *          buffer is returned, including the terminating null.
 *
 */

int
SHAnsiToUnicode(LPCSTR pszSrc, LPWSTR pwszDst, int cwchBuf)
{
    return SHAnsiToUnicodeCP_ACP(pszSrc, pwszDst, cwchBuf);
}

// dupe a string using the task allocator for returing from a COM interface
// These functions use SHAlloc, so they cannot go into shlwapi.

STDAPI SHStrDupA(LPCSTR psz, WCHAR **ppwsz)
{
    DWORD cch = MultiByteToWideChar(CP_ACP, 0, psz, -1, NULL, 0);
    *ppwsz = (WCHAR *)CoTaskMemAlloc((cch + 1) * SIZEOF(WCHAR));
    if (*ppwsz)
    {
        MultiByteToWideChar(CP_ACP, 0, psz, -1, *ppwsz, cch);
        return S_OK;
    }
    return E_OUTOFMEMORY;
}

// dupe a string using the task allocator for returing from a COM interface

STDAPI SHStrDupW(LPCWSTR psz, WCHAR **ppwsz)
{
    *ppwsz = (WCHAR *)CoTaskMemAlloc((lstrlenW(psz) + 1) * SIZEOF(WCHAR));
    if (*ppwsz)
    {
        StrCpyW(*ppwsz, psz);
        return S_OK;
    }
    return E_OUTOFMEMORY;
}

// Modifies:
//      szRoot
//
// Returns:
//      TRUE if a drive root was found
//      FALSE otherwise
//
STDAPI_(BOOL)
PathStripToRoot(
    LPTSTR szRoot)
{
    while(!PathIsRoot(szRoot))
    {
        if (!PathRemoveFileSpec(szRoot))
        {
            // If we didn't strip anything off,
            // must be current drive
            return(FALSE);
        }
    }

    return(TRUE);
}

//
// SHStringFromGUIDA
//
// converts GUID into (...) form without leading identifier; returns
// amount of data copied to lpsz if successful; 0 if buffer too small.
//

// An endian-dependant map of what bytes go where in the GUID
// text representation.
//
// Do NOT use the TEXT() macro in GuidMap... they're intended to be bytes
//

static const BYTE c_rgbGuidMap[] = { 3, 2, 1, 0, '-', 5, 4, '-', 7, 6, '-',
                                     8, 9, '-', 10, 11, 12, 13, 14, 15 };

static const CHAR c_szDigitsA[] = "0123456789ABCDEF";
static const WCHAR c_szDigitsW[] = TEXTW("0123456789ABCDEF");

STDAPI_(int) 
SHStringFromGUIDA(
    UNALIGNED REFGUID rguid, 
    LPSTR   psz, 
    int     cchMax)
{
    int i;
    const BYTE * pBytes = (const BYTE *) rguid;

    if (cchMax < GUIDSTR_MAX)
        return 0;

#ifdef BIG_ENDIAN
    // This is the slow, but portable version
    wnsprintf(psz, cchMax,"{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}",
            rguid->Data1, rguid->Data2, rguid->Data3,
            rguid->Data4[0], rguid->Data4[1],
            rguid->Data4[2], rguid->Data4[3],
            rguid->Data4[4], rguid->Data4[5],
            rguid->Data4[6], rguid->Data4[7]);
#else
    // The following algorithm is faster than the wsprintf.
    *psz++ = '{';

    for (i = 0; i < SIZEOF(c_rgbGuidMap); i++)
    {
        if (c_rgbGuidMap[i] == '-')      // don't TEXT() this line
        {
            *psz++ = '-';
        }
        else
        {
            // Convert a byte-value into a character representation
            *psz++ = c_szDigitsA[ (pBytes[c_rgbGuidMap[i]] & 0xF0) >> 4 ];
            *psz++ = c_szDigitsA[ (pBytes[c_rgbGuidMap[i]] & 0x0F) ];
        }
    }
    *psz++ = '}';
    *psz   = '\0';
#endif /* !BIG_ENDIAN */

    return GUIDSTR_MAX;
}

STDAPI_(int) 
SHStringFromGUIDW(
    UNALIGNED REFGUID rguid, 
    LPWSTR  psz, 
    int     cchMax)
{
    int i;
    const BYTE * pBytes = (const BYTE *) rguid;

    if (cchMax < GUIDSTR_MAX)
        return 0;

#ifdef BIG_ENDIAN
    // This is the slow, but portable version
    wnsprintfW(psz, cchMax, L"{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}",
            rguid->Data1, rguid->Data2, rguid->Data3,
            rguid->Data4[0], rguid->Data4[1],
            rguid->Data4[2], rguid->Data4[3],
            rguid->Data4[4], rguid->Data4[5],
            rguid->Data4[6], rguid->Data4[7]);
#else
    // The following algorithm is faster than the wsprintf.
    *psz++ = TEXTW('{');

    for (i = 0; i < SIZEOF(c_rgbGuidMap); i++)
    {
        if (c_rgbGuidMap[i] == '-')      // don't TEXT() this line
        {
            *psz++ = TEXTW('-');
        }
        else
        {
            // Convert a byte-value into a character representation
            *psz++ = c_szDigitsW[ (pBytes[c_rgbGuidMap[i]] & 0xF0) >> 4 ];
            *psz++ = c_szDigitsW[ (pBytes[c_rgbGuidMap[i]] & 0x0F) ];
        }
    }
    *psz++ = TEXTW('}');
    *psz   = TEXTW('\0');
#endif /* !BIG_ENDIAN */

    return GUIDSTR_MAX;
}

//
//  Why do we use the unsafe version?
//
//  -   Unsafe is much faster.
//
//  -   The safe version isn't safe after all and serves only to mask
//      existing bugs.  The situation the safe version "saves" is if
//      two threads both try to atomicrelease the same object.  This
//      means that at the same moment, both threads think the object
//      is alive.  Change the timing slightly, and now one thread
//      atomicreleases the object before the other one, so the other
//      thread is now using an object after the first thread already
//      atomicreleased it.  Bug.
//
STDAPI_(void) IUnknown_AtomicRelease(void **ppunk)
{
#if 1 // Unsafe
    if (ppunk && *ppunk) {
        IUnknown* punk = *(IUnknown**)ppunk;
        *ppunk = NULL;
        punk->lpVtbl->Release(punk);
    }
#else // Safe
    if (ppunk) {
        IUnknown* punk = (IUnknown *)InterlockedExchangePointer(ppunk, NULL);
        if (punk) {
            punk->Release();
        }
    }
#endif
}

#endif //DOWNLEVEL_PLATFORM