665 lines
16 KiB
C++
665 lines
16 KiB
C++
// MyInfoCtl.cpp : Implementation of CMyInfoCtl
|
|
#include "stdafx.h"
|
|
#include "MyInfo.h"
|
|
#include "MyInfCtl.h"
|
|
#include "WCParser.h"
|
|
#include <stdio.h>
|
|
#include <pudebug.h>
|
|
|
|
class CMyInfoLock
|
|
{
|
|
public:
|
|
CMyInfoLock()
|
|
{
|
|
CMyInfoCtl::Lock();
|
|
}
|
|
~CMyInfoLock()
|
|
{
|
|
CMyInfoCtl::Unlock();
|
|
}
|
|
};
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CMyInfoCtl
|
|
|
|
static long gCMyInfoCtlCount = 0;
|
|
|
|
// Initialize will handle one-time class initialization, it should be called in DllMain
|
|
bool
|
|
CMyInfoCtl::Initialize()
|
|
{
|
|
::InitializeCriticalSection( &s_cs );
|
|
SET_CRITICAL_SECTION_SPIN_COUNT(&s_cs, IIS_DEFAULT_CS_SPIN_COUNT);
|
|
return true;
|
|
}
|
|
|
|
// Uninitialize will handle one-time class unitialization, it should be called in DllMain
|
|
bool
|
|
CMyInfoCtl::Uninitialize()
|
|
{
|
|
if ( s_pInfoBase )
|
|
{
|
|
delete s_pInfoBase;
|
|
}
|
|
::DeleteCriticalSection( &s_cs );
|
|
return true;
|
|
}
|
|
|
|
// lock the shared critical section
|
|
void
|
|
CMyInfoCtl::Lock()
|
|
{
|
|
::EnterCriticalSection( &s_cs );
|
|
}
|
|
|
|
// unlock the shared critical section
|
|
void
|
|
CMyInfoCtl::Unlock()
|
|
{
|
|
::LeaveCriticalSection( &s_cs );
|
|
}
|
|
|
|
// Constructor for CMyInfoCtl
|
|
// The myInfoTop parameter is set to true when the object is the toplevel CMyInfoCtl
|
|
// (created by the Class factory), and false for the objects that make up the rest
|
|
// of the tree.
|
|
CMyInfoCtl::CMyInfoCtl(bool myInfoTop)
|
|
{
|
|
long numInstances = ::InterlockedIncrement(&gCMyInfoCtlCount);
|
|
ATLTRACE( _T("CMyInfoCtl(): %ld instances\n"), numInstances );
|
|
m_bIsMyInfoTop = myInfoTop;
|
|
if(m_bIsMyInfoTop)
|
|
m_cRef = 1;
|
|
else
|
|
m_cRef = 0;
|
|
}
|
|
|
|
CMyInfoCtl::~CMyInfoCtl(void)
|
|
{
|
|
if(this == s_pInfoBase) // If we are deleting the top, first save it
|
|
{
|
|
CMyInfoLock lock;
|
|
|
|
CMyInfoCtl::SaveFile();
|
|
s_pInfoBase = NULL;
|
|
}
|
|
|
|
long numInstances = ::InterlockedDecrement(&gCMyInfoCtlCount);
|
|
ATLTRACE( _T("~CMyInfoCtl(): %ld instances\n"), numInstances );
|
|
}
|
|
|
|
CWDNode * CMyInfoCtl::WDClone()
|
|
{
|
|
CMyInfoCtl *newObject = new CMyInfoCtl(false);
|
|
newObject->AddRef();
|
|
|
|
return newObject;
|
|
}
|
|
|
|
STDMETHODIMP CMyInfoCtl::QueryInterface(REFIID iid, void ** ppv)
|
|
{
|
|
*ppv = NULL;
|
|
if(iid == IID_IUnknown || iid == IID_IDispatch || iid == IID_IMyInfoCtl)
|
|
{
|
|
*ppv = static_cast<IMyInfoCtl *>(this);
|
|
}
|
|
else if (iid == IID_IMarshal) {
|
|
*ppv = m_pUnkMarshaler;
|
|
}
|
|
else
|
|
return ResultFromScode(E_NOINTERFACE);
|
|
|
|
if (*ppv != NULL)
|
|
((LPUNKNOWN)*ppv)->AddRef();
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CMyInfoCtl::AddRef(void)
|
|
{
|
|
ULONG rc = ::InterlockedIncrement( &m_cRef );
|
|
// ATLTRACE( _T("CMyInfoCtl::AddRef: %d\n"), rc );
|
|
if(rc == 1)
|
|
NodeAddRef();
|
|
return rc;
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CMyInfoCtl::Release(void)
|
|
{
|
|
ULONG rc = ::InterlockedDecrement( &m_cRef );
|
|
// ATLTRACE( _T("CMyInfoCtl::Release: %d\n"), rc );
|
|
if(rc == 0)
|
|
{
|
|
NodeReleaseRef();
|
|
return 0;
|
|
}
|
|
return m_cRef;
|
|
}
|
|
|
|
|
|
STDMETHODIMP CMyInfoCtl::GetIDsOfNames(REFIID /*riid*/, LPOLESTR* rgszNames,
|
|
UINT cNames, LCID lcid, DISPID* rgdispid)
|
|
{
|
|
CMyInfoLock lock;
|
|
// Make sure the file is loaded. This routine does nothing if its already loaded.
|
|
LoadFile();
|
|
|
|
CMyInfoCtl *theNode;
|
|
|
|
if(m_bIsMyInfoTop)
|
|
theNode = s_pInfoBase;
|
|
else
|
|
theNode = this;
|
|
|
|
ITypeInfo* pInfo;
|
|
HRESULT hRes = GetTI(lcid, &pInfo);
|
|
if (pInfo != NULL)
|
|
{
|
|
OLECHAR *namestr = rgszNames[0];
|
|
char name[256];
|
|
|
|
if(!wcsicmp(namestr, L"OnStartPage") ||
|
|
!wcsicmp(namestr, L"OnEndPage"))
|
|
{
|
|
*rgdispid = 0;
|
|
hRes = DISP_E_UNKNOWNNAME;
|
|
}
|
|
else
|
|
{
|
|
for(int i = 0; namestr[i] != 0; i++)
|
|
name[i] = (char) namestr[i];
|
|
name[i] = 0;
|
|
|
|
CMyInfoCtl *subNode = static_cast<CMyInfoCtl *>(theNode->GetChild(name, 0));
|
|
|
|
if(subNode == NULL)
|
|
{
|
|
subNode = new CMyInfoCtl(false);
|
|
theNode->AddChild(name, subNode);
|
|
DirtyMyInfo();
|
|
}
|
|
|
|
// The collection member referenced is in subNode. Calculate
|
|
// a dispid to return by finding out its index inside its parent (theNode).
|
|
// The indexes are 0 based, while the dispid needs to be 1 based, so add 1.
|
|
*rgdispid = (DISPID) theNode->GetChildNumber(subNode) + 1;
|
|
hRes = S_OK;
|
|
}
|
|
pInfo->Release();
|
|
}
|
|
return hRes;
|
|
}
|
|
|
|
HRESULT CMyInfoCtl::PutVariant(CWDNode *theNode, VARIANT *data)
|
|
{
|
|
CSimpleUTFString value;
|
|
|
|
if(data->vt != VT_BSTR)
|
|
{
|
|
VARIANTARG dest;
|
|
|
|
VariantInit(&dest);
|
|
|
|
HRESULT result = VariantChangeType(&dest, data, 0, VT_BSTR);
|
|
|
|
if(result != S_OK)
|
|
return DISP_E_TYPEMISMATCH;
|
|
|
|
value.Copy(dest.bstrVal, SysStringLen(dest.bstrVal));
|
|
}
|
|
else
|
|
value.Copy(data->bstrVal, SysStringLen(data->bstrVal));
|
|
|
|
if(theNode->NumValues() == 0)
|
|
theNode->AddValue(&value, false);
|
|
else
|
|
theNode->ReplaceValue(0, &value, false);
|
|
DirtyMyInfo();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CMyInfoCtl::Invoke(DISPID dispidMember, REFIID /*riid*/,
|
|
LCID lcid, WORD wFlags, DISPPARAMS* pdispparams, VARIANT* pvarResult,
|
|
EXCEPINFO* pexcepinfo, UINT* puArgErr)
|
|
{
|
|
CMyInfoLock lock;
|
|
|
|
CWDNode *theNode;
|
|
CMyInfoCtl *subNode = NULL;
|
|
|
|
if(m_bIsMyInfoTop)
|
|
theNode = s_pInfoBase;
|
|
else
|
|
theNode = this;
|
|
|
|
if(dispidMember > 0)
|
|
{
|
|
subNode = static_cast<CMyInfoCtl *>(theNode->GetChildIndex(dispidMember - 1));
|
|
}
|
|
|
|
SetErrorInfo(0, NULL);
|
|
ITypeInfo* pInfo;
|
|
HRESULT hRes = GetTI(lcid, &pInfo);
|
|
if (pInfo != NULL)
|
|
{
|
|
short extraArgs = 0;
|
|
|
|
if(wFlags & (DISPATCH_PROPERTYPUT | DISPATCH_PROPERTYPUTREF))
|
|
extraArgs++; // Skip the value to store in the beginning
|
|
|
|
if(dispidMember == DISPID_NEWENUM)
|
|
{
|
|
// An enumerator for this collection has been requested
|
|
|
|
CMyInfoEnum *theEnum = NULL;
|
|
|
|
theEnum = CMyInfoEnum::Create(theNode);
|
|
|
|
if(theEnum == NULL)
|
|
{
|
|
// Error
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
IUnknown *theEnumUnknown = NULL;
|
|
theEnum->QueryInterface(IID_IUnknown, (void **) &theEnumUnknown);
|
|
|
|
V_VT(pvarResult) = VT_UNKNOWN;
|
|
V_UNKNOWN(pvarResult) = theEnumUnknown;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
if(dispidMember != DISPID_VALUE || pdispparams->cArgs > (unsigned short) extraArgs)
|
|
{
|
|
bool lastArgName = false;
|
|
if(dispidMember != DISPID_VALUE)
|
|
{
|
|
theNode = subNode;
|
|
lastArgName = true;
|
|
}
|
|
// Note: The following would NOT work if argNum were not signed!
|
|
// Since pdispparams->cArgs is unsigned we cast it to a signed int first
|
|
for(int argNum = ((int) pdispparams->cArgs) - 1; argNum >= extraArgs; argNum--)
|
|
{
|
|
// Collection reference
|
|
// VARTYPE theType = pdispparams->rgvarg[argNum].vt;
|
|
VARIANTARG dest;
|
|
|
|
VariantInit(&dest);
|
|
|
|
hRes = VariantChangeType(&dest, &(pdispparams->rgvarg[argNum]), 0, VT_I4);
|
|
|
|
if(hRes != S_OK)
|
|
{
|
|
hRes = VariantChangeType(&dest, &(pdispparams->rgvarg[argNum]), 0, VT_BSTR);
|
|
if(hRes != S_OK)
|
|
{
|
|
// Error;
|
|
theNode = NULL;
|
|
break; // for loop
|
|
}
|
|
else
|
|
{
|
|
// String
|
|
OLECHAR *namestr = dest.bstrVal;
|
|
char name[256];
|
|
|
|
// BOYDM - Convert the wide character string down to ansi taking
|
|
// into account any dbcs conversions and special character conversions.
|
|
int i = WideCharToMultiByte(
|
|
CP_ACP, // code page
|
|
0, // performance and mapping flags
|
|
namestr, // address of wide-character string
|
|
-1, // number of characters in string -> -1 means null terminated
|
|
name, // address of buffer for new string
|
|
256, // size of buffer
|
|
NULL, // address of default for unmappable characters
|
|
NULL // address of flag set when default char. used
|
|
);
|
|
// for(int i = 0; i < 255 && namestr[i] != 0; i++)
|
|
// name[i] = (char) namestr[i];
|
|
// name[i] = 0;
|
|
|
|
CWDNode *oldNode = theNode;
|
|
theNode = static_cast<CMyInfoCtl *>(theNode->GetChild(name, 0));
|
|
if(!theNode)
|
|
{
|
|
theNode = new CMyInfoCtl(false);
|
|
|
|
oldNode->AddChild(name, theNode);
|
|
DirtyMyInfo();
|
|
}
|
|
lastArgName = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(lastArgName)
|
|
{
|
|
// If the previous item is a name then we retrieve the
|
|
// nth item with that name
|
|
CWDNode *subNode = NULL;
|
|
subNode = static_cast<CMyInfoCtl *>(theNode->GetSibling(dest.lVal));
|
|
if(subNode == NULL && (wFlags & (DISPATCH_PROPERTYPUT | DISPATCH_PROPERTYPUTREF)))
|
|
{
|
|
while(theNode->GetParent()->GetChild(theNode->GetMyName(), dest.lVal) == NULL)
|
|
{
|
|
subNode = new CMyInfoCtl(false);
|
|
theNode->GetParent()->AddChild(theNode->GetMyName(), subNode);
|
|
DirtyMyInfo();
|
|
}
|
|
}
|
|
theNode = subNode;
|
|
}
|
|
else
|
|
{
|
|
theNode = theNode->GetChildIndex(dest.lVal);
|
|
}
|
|
lastArgName = false;
|
|
}
|
|
if(theNode == NULL)
|
|
{
|
|
return DISP_E_MEMBERNOTFOUND;
|
|
}
|
|
}
|
|
|
|
if(wFlags & (DISPATCH_PROPERTYPUT | DISPATCH_PROPERTYPUTREF))
|
|
{
|
|
hRes = PutVariant(theNode, &pdispparams->rgvarg[0]);
|
|
}
|
|
else
|
|
{
|
|
CMyInfoCtl *theInfo = static_cast<CMyInfoCtl *> (theNode);
|
|
IDispatch *theInfoDispatch = NULL;
|
|
theInfo->QueryInterface(IID_IDispatch, (void **) &theInfoDispatch);
|
|
|
|
V_VT(pvarResult) = VT_DISPATCH;
|
|
V_DISPATCH(pvarResult) = theInfoDispatch;
|
|
}
|
|
}
|
|
else if(wFlags & (DISPATCH_PROPERTYPUT | DISPATCH_PROPERTYPUTREF))
|
|
{
|
|
if(dispidMember == DISPID_VALUE)
|
|
{
|
|
hRes = PutVariant(this, &pdispparams->rgvarg[0]);
|
|
}
|
|
else
|
|
{
|
|
hRes = PutVariant(subNode, &pdispparams->rgvarg[0]);
|
|
}
|
|
}
|
|
else if(wFlags & DISPATCH_PROPERTYGET)
|
|
{
|
|
if(dispidMember == DISPID_VALUE && pdispparams->cArgs == 0)
|
|
{
|
|
CSimpleUTFString *theStr = theNode->GetValue(0);
|
|
|
|
if(theStr == NULL)
|
|
{
|
|
VariantInit(pvarResult);
|
|
}
|
|
else
|
|
{
|
|
V_VT(pvarResult) = VT_BSTR;
|
|
V_BSTR(pvarResult) = SysAllocStringLen((OLECHAR *) theStr->getData(), theStr->Length());
|
|
}
|
|
}
|
|
}
|
|
|
|
pInfo->Release();
|
|
}
|
|
return hRes;
|
|
}
|
|
|
|
void CMyInfoCtl::SaveFile(void)
|
|
{
|
|
// Notes:
|
|
// This function does not require any additional syncronization since
|
|
// the use of the CreateFile with no sharing permission effectively
|
|
// prevents multiple access to the same file. The behavior
|
|
// of a failed CreateFile call is important. We should
|
|
// not set any of the flags for whether the file has been written or not.
|
|
|
|
HANDLE hFile;
|
|
|
|
_TCHAR fileNameBuffer[MAX_PATH];
|
|
|
|
GetSystemDirectory(fileNameBuffer, MAX_PATH-16);
|
|
_tcscat(fileNameBuffer, _T("\\inetsrv\\Data\\MyInfo.xml"));
|
|
|
|
hFile = CreateFile( fileNameBuffer, // open MyInfo.xml
|
|
GENERIC_WRITE, // open for writing
|
|
0, // don't share
|
|
NULL, // no security
|
|
CREATE_ALWAYS, // overwrite existing file
|
|
FILE_ATTRIBUTE_NORMAL, // normal file
|
|
NULL); // no attr. template
|
|
|
|
if (hFile == INVALID_HANDLE_VALUE)
|
|
{
|
|
// ErrorHandler("Could not open file."); // process error
|
|
return;
|
|
}
|
|
|
|
CMyInfoCtl *clone = new CMyInfoCtl(false);
|
|
clone->AddRef();
|
|
|
|
CWDParser *theParser = new CWDParser(clone);
|
|
theParser->StartOutput(s_pInfoBase);
|
|
|
|
while(true)
|
|
{
|
|
char buffer[1026];
|
|
unsigned long size = 1024;
|
|
|
|
bool more = theParser->Output(buffer, &size);
|
|
if(size > 0)
|
|
{
|
|
DWORD bytesWritten;
|
|
WriteFile( hFile, buffer, size,
|
|
&bytesWritten, NULL);
|
|
}
|
|
if(!more)
|
|
break;
|
|
}
|
|
|
|
CloseHandle(hFile);
|
|
clone->Release();
|
|
delete theParser;
|
|
|
|
s_bInfoDirty = false;
|
|
s_dwLastSaveTime = GetTickCount();
|
|
}
|
|
|
|
void CMyInfoCtl::DirtyMyInfo(void)
|
|
{
|
|
s_bInfoDirty = true;
|
|
|
|
if(s_pInfoBase != NULL && s_dwLastSaveTime + 15 * 1000 < GetTickCount())
|
|
SaveFile();
|
|
}
|
|
|
|
void CMyInfoCtl::LoadFile(void)
|
|
{
|
|
// Once we have already loaded the file, s_pInfoBase is set to point to the properties
|
|
if(s_pInfoBase != NULL)
|
|
return; // Only load the file once at startup
|
|
|
|
CWCParser *theParser;
|
|
|
|
CMyInfoCtl *clone = new CMyInfoCtl(false);
|
|
clone->AddRef();
|
|
|
|
theParser = new CWDParser(clone);
|
|
|
|
CWDNode *theNode = theParser->StartParse();
|
|
s_pInfoBase = static_cast<CMyInfoCtl *> (theNode);
|
|
|
|
HANDLE hFile;
|
|
|
|
// Calculate the file location- Always put it in Windows\System32\inetsrv\MyInfo.xml
|
|
_TCHAR fileNameBuffer[MAX_PATH];
|
|
|
|
GetSystemDirectory(fileNameBuffer, MAX_PATH-16);
|
|
_tcscat(fileNameBuffer, _T("\\inetsrv\\Data\\MyInfo.xml"));
|
|
|
|
hFile = CreateFile( fileNameBuffer, // open MyInfo.xml
|
|
GENERIC_READ, // open for reading
|
|
0, // don't share
|
|
NULL, // no security
|
|
OPEN_EXISTING, // overwrite existing
|
|
// file
|
|
FILE_ATTRIBUTE_NORMAL, // normal file
|
|
NULL); // no attr. template
|
|
|
|
if (hFile != INVALID_HANDLE_VALUE)
|
|
{
|
|
while(true)
|
|
{
|
|
DWORD bytesRead;
|
|
unsigned char buffer[256];
|
|
|
|
if(!ReadFile( hFile,
|
|
buffer,
|
|
256,
|
|
&bytesRead,
|
|
NULL) || bytesRead == 0)
|
|
{ // Done
|
|
break;
|
|
}
|
|
theParser->Parse(buffer, bytesRead);
|
|
}
|
|
CloseHandle(hFile);
|
|
}
|
|
|
|
theParser->EndParse();
|
|
clone->Release();
|
|
delete theParser;
|
|
|
|
s_bInfoDirty = false;
|
|
s_dwLastSaveTime = GetTickCount();
|
|
}
|
|
|
|
CMyInfoCtl* CMyInfoCtl::s_pInfoBase = NULL;
|
|
bool CMyInfoCtl::s_bInfoDirty = false;
|
|
DWORD CMyInfoCtl::s_dwLastSaveTime = 0;
|
|
CRITICAL_SECTION CMyInfoCtl::s_cs;
|
|
|
|
// Implementation of CMyInfoEnum. This lets you enumerate over the contents of the collection.
|
|
|
|
STDMETHODIMP CMyInfoEnum::QueryInterface(REFIID iid, void ** ppv)
|
|
{
|
|
*ppv = NULL;
|
|
if(iid == IID_IUnknown || iid == IID_IEnumVARIANT)
|
|
*ppv = this;
|
|
else
|
|
return ResultFromScode(E_NOINTERFACE);
|
|
AddRef();
|
|
return NOERROR;
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CMyInfoEnum::AddRef(void)
|
|
{
|
|
return ::InterlockedIncrement(&m_cRef);
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CMyInfoEnum::Release(void)
|
|
{
|
|
ULONG rc = ::InterlockedDecrement(&m_cRef);
|
|
if(rc == 0)
|
|
{
|
|
delete this;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static long gCMyInfoEnumCount = 0;
|
|
|
|
CMyInfoEnum::CMyInfoEnum(CWDNode *theInfo)
|
|
{
|
|
_Module.Lock();
|
|
// ATLTRACE( _T("CMyInfo: new enumeration\n") );
|
|
::InterlockedIncrement(&gCMyInfoEnumCount);
|
|
m_Info = theInfo;
|
|
m_Index = 0;
|
|
m_cRef = 0;
|
|
}
|
|
|
|
CMyInfoEnum::~CMyInfoEnum(void)
|
|
{
|
|
// ATLTRACE( _T("CMyInfo: enumeration destroyed\n") );
|
|
::InterlockedDecrement(&gCMyInfoEnumCount);
|
|
_Module.Unlock();
|
|
}
|
|
|
|
STDMETHODIMP CMyInfoEnum::Next(ULONG cElements, VARIANT * pvar, ULONG * pcElementFetched)
|
|
{
|
|
CMyInfoLock lock;
|
|
|
|
if(m_Index >= m_Info->NumChildren())
|
|
{
|
|
if(pcElementFetched)
|
|
*pcElementFetched = 0;
|
|
return ResultFromScode(S_FALSE);
|
|
}
|
|
CWDNode *theNode = NULL;
|
|
|
|
theNode = m_Info->GetChildIndex(m_Index);
|
|
|
|
if ( pcElementFetched )
|
|
{
|
|
*pcElementFetched = 0;
|
|
}
|
|
for ( ULONG i = 0; ( i < cElements ) && ( m_Index < m_Info->NumChildren() ); i++ )
|
|
{
|
|
if(pcElementFetched)
|
|
{
|
|
(*pcElementFetched)++;
|
|
}
|
|
|
|
CMyInfoCtl *theInfo = static_cast<CMyInfoCtl *> (theNode);
|
|
IDispatch *theInfoDispatch = static_cast<IDispatch *>(theInfo);
|
|
theInfoDispatch->AddRef();
|
|
pvar[i].vt = VT_DISPATCH;
|
|
pvar[i].pdispVal = theInfoDispatch;
|
|
Skip(1);
|
|
}
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
STDMETHODIMP CMyInfoEnum::Skip(ULONG cElements)
|
|
{
|
|
m_Index += cElements;
|
|
|
|
CMyInfoLock lock;
|
|
|
|
if(m_Index >= m_Info->NumChildren())
|
|
{
|
|
m_Index = m_Info->NumChildren();
|
|
return ResultFromScode(S_FALSE);
|
|
}
|
|
else
|
|
{
|
|
return NOERROR;
|
|
}
|
|
}
|
|
|
|
STDMETHODIMP CMyInfoEnum::Reset()
|
|
{
|
|
m_Index = 0;
|
|
return NOERROR;
|
|
}
|
|
|
|
STDMETHODIMP CMyInfoEnum::Clone(IEnumVARIANT **ppenum)
|
|
{
|
|
CMyInfoEnum *newEnum = CMyInfoEnum::Create(m_Info);
|
|
if (newEnum == NULL)
|
|
return E_OUTOFMEMORY;
|
|
|
|
newEnum->m_Index = m_Index;
|
|
*ppenum = newEnum;
|
|
return NOERROR;
|
|
}
|