659 lines
15 KiB
C++
659 lines
15 KiB
C++
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// Microsoft Windows
|
||
|
//
|
||
|
// Copyright (C) Microsoft Corporation, 1997 - 1999
|
||
|
//
|
||
|
// File: registry.cpp
|
||
|
//
|
||
|
//--------------------------------------------------------------------------
|
||
|
|
||
|
#include "pch.h"
|
||
|
#pragma hdrstop
|
||
|
|
||
|
|
||
|
#include "registry.h"
|
||
|
|
||
|
|
||
|
RegKey::RegKey(
|
||
|
void
|
||
|
) : m_hkeyRoot(NULL),
|
||
|
m_hkey(NULL)
|
||
|
{
|
||
|
m_szSubKey[0] = TEXT('\0');
|
||
|
}
|
||
|
|
||
|
|
||
|
RegKey::RegKey(
|
||
|
HKEY hkeyRoot,
|
||
|
LPCTSTR pszSubKey
|
||
|
) : m_hkeyRoot(hkeyRoot),
|
||
|
m_hkey(NULL)
|
||
|
{
|
||
|
lstrcpyn(m_szSubKey, pszSubKey, ARRAYSIZE(m_szSubKey));
|
||
|
}
|
||
|
|
||
|
|
||
|
RegKey::~RegKey(
|
||
|
void
|
||
|
)
|
||
|
{
|
||
|
Close();
|
||
|
}
|
||
|
|
||
|
|
||
|
HRESULT
|
||
|
RegKey::Open(
|
||
|
REGSAM samDesired, // Access mask (i.e. KEY_READ, KEY_WRITE etc.)
|
||
|
bool bCreate // Create key if it doesn't exist?
|
||
|
) const
|
||
|
{
|
||
|
DWORD dwResult = ERROR_SUCCESS;
|
||
|
HKEY hkeyRoot = m_hkeyRoot; // Assume we'll use m_hkeyRoot;
|
||
|
bool bCloseRootKey = false;
|
||
|
|
||
|
Close();
|
||
|
if (HKEY_CURRENT_USER == hkeyRoot)
|
||
|
{
|
||
|
//
|
||
|
// Special-case HKEY_CURRENT_USER.
|
||
|
// Since we're running from winlogon and need to open
|
||
|
// the user hive during tricky times we need to
|
||
|
// call RegOpenCurrentUser(). If it's successful all
|
||
|
// we do is replace the hkeyRoot member with the
|
||
|
// returned key. From here on out things remain
|
||
|
// unchanged.
|
||
|
//
|
||
|
if (ERROR_SUCCESS == RegOpenCurrentUser(samDesired, &hkeyRoot))
|
||
|
{
|
||
|
bCloseRootKey = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
dwResult = RegOpenKeyEx(hkeyRoot,
|
||
|
m_szSubKey,
|
||
|
0,
|
||
|
samDesired,
|
||
|
&m_hkey);
|
||
|
|
||
|
if ((ERROR_FILE_NOT_FOUND == dwResult) && bCreate)
|
||
|
{
|
||
|
DWORD dwDisposition;
|
||
|
dwResult = RegCreateKeyEx(hkeyRoot,
|
||
|
m_szSubKey,
|
||
|
0,
|
||
|
NULL,
|
||
|
0,
|
||
|
samDesired,
|
||
|
NULL,
|
||
|
&m_hkey,
|
||
|
&dwDisposition);
|
||
|
}
|
||
|
if (bCloseRootKey)
|
||
|
{
|
||
|
RegCloseKey(hkeyRoot);
|
||
|
}
|
||
|
return HRESULT_FROM_WIN32(dwResult);
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
RegKey::Attach(
|
||
|
HKEY hkey
|
||
|
)
|
||
|
{
|
||
|
Close();
|
||
|
m_szSubKey[0] = TEXT('\0');
|
||
|
m_hkeyRoot = NULL;
|
||
|
m_hkey = hkey;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
RegKey::Detach(
|
||
|
void
|
||
|
)
|
||
|
{
|
||
|
m_hkey = NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
void
|
||
|
RegKey::Close(
|
||
|
void
|
||
|
) const
|
||
|
{
|
||
|
if (NULL != m_hkey)
|
||
|
{
|
||
|
//
|
||
|
// Do this little swap so that the m_hkey member is NULL
|
||
|
// when the actual key is being closed. This lets the async
|
||
|
// change proc determine if it was signaled because of a true
|
||
|
// change or because the key was being closed.
|
||
|
//
|
||
|
HKEY hkeyTemp = m_hkey;
|
||
|
m_hkey = NULL;
|
||
|
RegCloseKey(hkeyTemp);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Determine if a particular registry value exists.
|
||
|
//
|
||
|
HRESULT
|
||
|
RegKey::ValueExists(
|
||
|
LPCTSTR pszValueName
|
||
|
) const
|
||
|
{
|
||
|
DWORD dwResult = ERROR_SUCCESS;
|
||
|
int iValue = 0;
|
||
|
TCHAR szValue[MAX_PATH];
|
||
|
DWORD cchValue;
|
||
|
while(ERROR_SUCCESS == dwResult)
|
||
|
{
|
||
|
cchValue = ARRAYSIZE(szValue);
|
||
|
dwResult = RegEnumValue(m_hkey,
|
||
|
iValue++,
|
||
|
szValue,
|
||
|
&cchValue,
|
||
|
NULL,
|
||
|
NULL,
|
||
|
NULL,
|
||
|
NULL);
|
||
|
if (ERROR_SUCCESS == dwResult && (0 == lstrcmpi(pszValueName, szValue)))
|
||
|
{
|
||
|
return S_OK;
|
||
|
}
|
||
|
}
|
||
|
if (ERROR_NO_MORE_ITEMS == dwResult)
|
||
|
{
|
||
|
return S_FALSE;
|
||
|
}
|
||
|
|
||
|
return HRESULT_FROM_WIN32(dwResult);
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// This is the basic form of GetValue. All other forms of
|
||
|
// GetValue() call into this one.
|
||
|
//
|
||
|
HRESULT
|
||
|
RegKey::GetValue(
|
||
|
LPCTSTR pszValueName,
|
||
|
DWORD dwTypeExpected,
|
||
|
LPBYTE pbData,
|
||
|
int cbData
|
||
|
) const
|
||
|
{
|
||
|
DWORD dwType;
|
||
|
DWORD dwResult = RegQueryValueEx(m_hkey,
|
||
|
pszValueName,
|
||
|
0,
|
||
|
&dwType,
|
||
|
pbData,
|
||
|
(LPDWORD)&cbData);
|
||
|
|
||
|
if (ERROR_SUCCESS == dwResult && dwType != dwTypeExpected)
|
||
|
dwResult = ERROR_INVALID_DATATYPE;
|
||
|
|
||
|
return HRESULT_FROM_WIN32(dwResult);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Get a DWORD value (REG_DWORD).
|
||
|
//
|
||
|
HRESULT
|
||
|
RegKey::GetValue(
|
||
|
LPCTSTR pszValueName,
|
||
|
DWORD *pdwDataOut
|
||
|
) const
|
||
|
{
|
||
|
return GetValue(pszValueName, REG_DWORD, (LPBYTE)pdwDataOut, sizeof(DWORD));
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Get a byte buffer value (REG_BINARY).
|
||
|
//
|
||
|
HRESULT
|
||
|
RegKey::GetValue(
|
||
|
LPCTSTR pszValueName,
|
||
|
LPBYTE pbDataOut,
|
||
|
int cbDataOut
|
||
|
) const
|
||
|
{
|
||
|
return GetValue(pszValueName, REG_BINARY, pbDataOut, cbDataOut);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Get a text string value (REG_SZ).
|
||
|
//
|
||
|
HRESULT
|
||
|
RegKey::GetValue(
|
||
|
LPCTSTR pszValueName,
|
||
|
LPTSTR pszDataOut,
|
||
|
UINT cchDataOut
|
||
|
) const
|
||
|
{
|
||
|
return GetValue(pszValueName,
|
||
|
REG_SZ,
|
||
|
(LPBYTE)pszDataOut,
|
||
|
cchDataOut * sizeof(*pszDataOut));
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Get a multi-text string value (REG_MULTI_SZ).
|
||
|
//
|
||
|
HRESULT
|
||
|
RegKey::GetValue(
|
||
|
LPCTSTR pszValueName,
|
||
|
HDPA hdpaStringsOut
|
||
|
) const
|
||
|
{
|
||
|
HRESULT hr = E_FAIL;
|
||
|
int cb = GetValueBufferSize(pszValueName);
|
||
|
if (NULL != hdpaStringsOut && 0 < cb)
|
||
|
{
|
||
|
LPTSTR psz = (LPTSTR)LocalAlloc(LPTR, cb);
|
||
|
if (NULL != psz)
|
||
|
{
|
||
|
hr = GetValue(pszValueName, REG_MULTI_SZ, (LPBYTE)psz, cb);
|
||
|
if (SUCCEEDED(hr))
|
||
|
{
|
||
|
while(psz && TEXT('\0') != *psz && SUCCEEDED(hr))
|
||
|
{
|
||
|
LPTSTR pszCopy = StrDup(psz);
|
||
|
if (NULL != pszCopy)
|
||
|
{
|
||
|
if (-1 == DPA_AppendPtr(hdpaStringsOut, pszCopy))
|
||
|
{
|
||
|
LocalFree(pszCopy);
|
||
|
hr = E_OUTOFMEMORY;
|
||
|
}
|
||
|
}
|
||
|
psz += lstrlen(psz) + 1;
|
||
|
}
|
||
|
}
|
||
|
if (FAILED(hr))
|
||
|
{
|
||
|
//
|
||
|
// Something failed. Clean up the DPA.
|
||
|
//
|
||
|
const int cStrings = DPA_GetPtrCount(hdpaStringsOut);
|
||
|
for (int i = 0; i < cStrings; i++)
|
||
|
{
|
||
|
LPTSTR pszDel = (LPTSTR)DPA_GetPtr(hdpaStringsOut, i);
|
||
|
if (NULL != pszDel)
|
||
|
{
|
||
|
LocalFree(pszDel);
|
||
|
}
|
||
|
DPA_DeletePtr(hdpaStringsOut, i);
|
||
|
}
|
||
|
}
|
||
|
LocalFree(psz);
|
||
|
}
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Return the required buffer size for a given registry value.
|
||
|
//
|
||
|
int
|
||
|
RegKey::GetValueBufferSize(
|
||
|
LPCTSTR pszValueName
|
||
|
) const
|
||
|
{
|
||
|
DWORD dwType;
|
||
|
int cbData = 0;
|
||
|
DWORD dwDummy;
|
||
|
DWORD dwResult = RegQueryValueEx(m_hkey,
|
||
|
pszValueName,
|
||
|
0,
|
||
|
&dwType,
|
||
|
(LPBYTE)&dwDummy,
|
||
|
(LPDWORD)&cbData);
|
||
|
if (ERROR_MORE_DATA != dwResult)
|
||
|
cbData = 0;
|
||
|
|
||
|
return cbData;
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// This is the basic form of SetValue. All other forms of
|
||
|
// SetValue() call into this one.
|
||
|
//
|
||
|
HRESULT
|
||
|
RegKey::SetValue(
|
||
|
LPCTSTR pszValueName,
|
||
|
DWORD dwValueType,
|
||
|
const LPBYTE pbData,
|
||
|
int cbData
|
||
|
)
|
||
|
{
|
||
|
DWORD dwResult = RegSetValueEx(m_hkey,
|
||
|
pszValueName,
|
||
|
0,
|
||
|
dwValueType,
|
||
|
pbData,
|
||
|
cbData);
|
||
|
|
||
|
return HRESULT_FROM_WIN32(dwResult);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Set a DWORD value (REG_DWORD).
|
||
|
//
|
||
|
HRESULT
|
||
|
RegKey::SetValue(
|
||
|
LPCTSTR pszValueName,
|
||
|
DWORD dwData
|
||
|
)
|
||
|
{
|
||
|
return SetValue(pszValueName, REG_DWORD, (const LPBYTE)&dwData, sizeof(dwData));
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Set a byte buffer value (REG_BINARY).
|
||
|
//
|
||
|
HRESULT
|
||
|
RegKey::SetValue(
|
||
|
LPCTSTR pszValueName,
|
||
|
const LPBYTE pbData,
|
||
|
int cbData
|
||
|
)
|
||
|
{
|
||
|
return SetValue(pszValueName, REG_BINARY, pbData, cbData);
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Set a text string value (REG_SZ).
|
||
|
//
|
||
|
HRESULT
|
||
|
RegKey::SetValue(
|
||
|
LPCTSTR pszValueName,
|
||
|
LPCTSTR pszData
|
||
|
)
|
||
|
{
|
||
|
return SetValue(pszValueName, REG_SZ, (const LPBYTE)pszData, (lstrlen(pszData) + 1) * sizeof(TCHAR));
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Set a text string value (REG_MULTI_SZ).
|
||
|
//
|
||
|
HRESULT
|
||
|
RegKey::SetValue(
|
||
|
LPCTSTR pszValueName,
|
||
|
HDPA hdpaStrings
|
||
|
)
|
||
|
{
|
||
|
HRESULT hr = E_OUTOFMEMORY;
|
||
|
LPTSTR pszDblNul = CreateDoubleNulTermList(hdpaStrings);
|
||
|
if (NULL != pszDblNul)
|
||
|
{
|
||
|
int cch = 1;
|
||
|
LPTSTR psz = pszDblNul;
|
||
|
while(*psz)
|
||
|
{
|
||
|
while(*psz++)
|
||
|
cch++;
|
||
|
psz++;
|
||
|
}
|
||
|
hr = SetValue(pszValueName, REG_MULTI_SZ, (const LPBYTE)pszDblNul, cch * sizeof(TCHAR));
|
||
|
LocalFree(pszDblNul);
|
||
|
}
|
||
|
return hr;
|
||
|
}
|
||
|
|
||
|
|
||
|
LPTSTR
|
||
|
RegKey::CreateDoubleNulTermList(
|
||
|
HDPA hdpaStrings
|
||
|
) const
|
||
|
{
|
||
|
LPTSTR pszBuf = NULL;
|
||
|
if (NULL != hdpaStrings)
|
||
|
{
|
||
|
const int cEntries = DPA_GetPtrCount(hdpaStrings);
|
||
|
int cch = 1; // Account for 2nd nul term.
|
||
|
int i;
|
||
|
for (i = 0; i < cEntries; i++)
|
||
|
{
|
||
|
LPTSTR psz = (LPTSTR)DPA_GetPtr(hdpaStrings, i);
|
||
|
if (NULL != psz)
|
||
|
{
|
||
|
cch += lstrlen(psz) + 1;
|
||
|
}
|
||
|
}
|
||
|
pszBuf = (LPTSTR)LocalAlloc(LPTR, cch * sizeof(*pszBuf));
|
||
|
if (NULL != pszBuf)
|
||
|
{
|
||
|
LPTSTR pszWrite = pszBuf;
|
||
|
|
||
|
for (i = 0; i < cEntries; i++)
|
||
|
{
|
||
|
LPTSTR psz = (LPTSTR)DPA_GetPtr(hdpaStrings, i);
|
||
|
if (NULL != psz)
|
||
|
{
|
||
|
lstrcpy(pszWrite, psz);
|
||
|
pszWrite += lstrlen(psz) + 1;
|
||
|
}
|
||
|
}
|
||
|
*pszWrite = TEXT('\0'); // Double nul term.
|
||
|
}
|
||
|
}
|
||
|
return pszBuf;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
HRESULT
|
||
|
RegKey::DeleteAllValues(
|
||
|
int *pcNotDeleted
|
||
|
)
|
||
|
{
|
||
|
HRESULT hr;
|
||
|
KEYINFO ki;
|
||
|
if (NULL != pcNotDeleted)
|
||
|
*pcNotDeleted = 0;
|
||
|
|
||
|
hr = QueryInfo(&ki, QIM_VALUENAMEMAXCHARCNT);
|
||
|
if (FAILED(hr))
|
||
|
return hr;
|
||
|
|
||
|
TCHAR szName[MAX_PATH];
|
||
|
DWORD cchValueName;
|
||
|
DWORD dwError = ERROR_SUCCESS;
|
||
|
int cNotDeleted = 0;
|
||
|
int iValue = 0;
|
||
|
while(ERROR_SUCCESS == dwError)
|
||
|
{
|
||
|
cchValueName = ARRAYSIZE(szName);
|
||
|
dwError = RegEnumValue(m_hkey,
|
||
|
iValue,
|
||
|
szName,
|
||
|
&cchValueName,
|
||
|
NULL,
|
||
|
NULL,
|
||
|
NULL,
|
||
|
NULL);
|
||
|
|
||
|
if (ERROR_SUCCESS == dwError)
|
||
|
{
|
||
|
if (FAILED(hr = DeleteValue(szName)))
|
||
|
{
|
||
|
cNotDeleted++;
|
||
|
iValue++; // Skip to next value to avoid infinite loop.
|
||
|
Trace((TEXT("Error %d deleting reg value \"%s\""),
|
||
|
HRESULT_CODE(hr), szName));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (ERROR_NO_MORE_ITEMS == dwError)
|
||
|
dwError = ERROR_SUCCESS;
|
||
|
|
||
|
if (pcNotDeleted)
|
||
|
*pcNotDeleted = cNotDeleted;
|
||
|
|
||
|
return HRESULT_FROM_WIN32(dwError);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
HRESULT
|
||
|
RegKey::DeleteValue(
|
||
|
LPCTSTR pszValue
|
||
|
)
|
||
|
{
|
||
|
LONG lRet = RegDeleteValue(m_hkey, pszValue);
|
||
|
return HRESULT_FROM_WIN32(lRet);
|
||
|
}
|
||
|
|
||
|
|
||
|
HRESULT
|
||
|
RegKey::QueryInfo(
|
||
|
RegKey::KEYINFO *pInfo,
|
||
|
DWORD fMask
|
||
|
) const
|
||
|
{
|
||
|
LONG lResult = RegQueryInfoKey(m_hkey,
|
||
|
NULL,
|
||
|
NULL,
|
||
|
NULL,
|
||
|
(QIM_SUBKEYCNT & fMask) ? &pInfo->cSubKeys : NULL,
|
||
|
(QIM_SUBKEYMAXCHARCNT & fMask) ? &pInfo->cchSubKeyMax : NULL,
|
||
|
(QIM_CLASSMAXCHARCNT & fMask) ? &pInfo->cchClassMax : NULL,
|
||
|
(QIM_VALUECNT & fMask) ? &pInfo->cValues : NULL,
|
||
|
(QIM_VALUENAMEMAXCHARCNT & fMask) ? &pInfo->cchValueNameMax : NULL,
|
||
|
(QIM_VALUEMAXBYTECNT & fMask) ? &pInfo->cbValueMax : NULL,
|
||
|
(QIM_SECURITYBYTECNT & fMask) ? &pInfo->cbSecurityDesc : NULL,
|
||
|
(QIM_LASTWRITETIME & fMask) ? &pInfo->LastWriteTime : NULL);
|
||
|
|
||
|
return HRESULT_FROM_WIN32(lResult);
|
||
|
}
|
||
|
|
||
|
|
||
|
RegKey::ValueIterator::ValueIterator(
|
||
|
RegKey& key
|
||
|
) : m_key(key),
|
||
|
m_iValue(0),
|
||
|
m_cchValueName(0)
|
||
|
{
|
||
|
KEYINFO ki;
|
||
|
if (SUCCEEDED(key.QueryInfo(&ki, QIM_VALUENAMEMAXCHARCNT)))
|
||
|
{
|
||
|
m_cchValueName = ki.cchValueNameMax + 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Returns: S_OK = More items.
|
||
|
// S_FALSE = No more items.
|
||
|
HRESULT
|
||
|
RegKey::ValueIterator::Next(
|
||
|
LPTSTR pszNameOut,
|
||
|
UINT cchNameOut,
|
||
|
LPDWORD pdwType,
|
||
|
LPBYTE pbData,
|
||
|
LPDWORD pcbData
|
||
|
)
|
||
|
{
|
||
|
HRESULT hr = S_OK;
|
||
|
DWORD cchName = cchNameOut;
|
||
|
LONG lResult = RegEnumValue(m_key,
|
||
|
m_iValue,
|
||
|
pszNameOut,
|
||
|
&cchName,
|
||
|
NULL,
|
||
|
pdwType,
|
||
|
pbData,
|
||
|
pcbData);
|
||
|
|
||
|
switch(lResult)
|
||
|
{
|
||
|
case ERROR_SUCCESS:
|
||
|
m_iValue++;
|
||
|
break;
|
||
|
case ERROR_NO_MORE_ITEMS:
|
||
|
hr = S_FALSE;
|
||
|
break;
|
||
|
default:
|
||
|
Trace((TEXT("Error %d enumerating reg value \"%s\""),
|
||
|
lResult, m_key.SubKeyName()));
|
||
|
hr = HRESULT_FROM_WIN32(lResult);
|
||
|
break;
|
||
|
}
|
||
|
return hr;
|
||
|
};
|
||
|
|
||
|
|
||
|
RegKeyCU::RegKeyCU(
|
||
|
REGSAM samDesired
|
||
|
) : m_hkey(NULL)
|
||
|
{
|
||
|
RegOpenCurrentUser(samDesired, &m_hkey);
|
||
|
}
|
||
|
|
||
|
RegKeyCU::~RegKeyCU(
|
||
|
void
|
||
|
)
|
||
|
{
|
||
|
if (NULL != m_hkey)
|
||
|
RegCloseKey(m_hkey);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
//
|
||
|
// Version of RegEnumValue that expands environment variables
|
||
|
// in all string values.
|
||
|
//
|
||
|
LONG
|
||
|
_RegEnumValueExp(
|
||
|
HKEY hKey,
|
||
|
DWORD dwIndex,
|
||
|
LPTSTR lpValueName,
|
||
|
LPDWORD lpcbValueName,
|
||
|
LPDWORD lpReserved,
|
||
|
LPDWORD lpType,
|
||
|
LPBYTE lpData,
|
||
|
LPDWORD lpcbData
|
||
|
)
|
||
|
{
|
||
|
DWORD cchNameDest = lpcbValueName ? *lpcbValueName / sizeof(TCHAR) : 0;
|
||
|
DWORD cchDataDest = lpcbData ? *lpcbData / sizeof(TCHAR) : 0;
|
||
|
DWORD dwType;
|
||
|
if (NULL == lpType)
|
||
|
lpType = &dwType;
|
||
|
|
||
|
LONG lResult = RegEnumValue(hKey,
|
||
|
dwIndex,
|
||
|
lpValueName,
|
||
|
lpcbValueName,
|
||
|
lpReserved,
|
||
|
lpType,
|
||
|
lpData,
|
||
|
lpcbData);
|
||
|
|
||
|
if (ERROR_SUCCESS == lResult)
|
||
|
{
|
||
|
HRESULT hr = ExpandStringInPlace(lpValueName, cchNameDest);
|
||
|
|
||
|
if ((NULL != lpData) && (REG_SZ == *lpType || REG_EXPAND_SZ == *lpType))
|
||
|
{
|
||
|
hr = ExpandStringInPlace((LPTSTR)lpData, cchDataDest);
|
||
|
}
|
||
|
lResult = HRESULT_CODE(hr);
|
||
|
}
|
||
|
return lResult;
|
||
|
}
|
||
|
|
||
|
|