Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

995 lines
23 KiB

/***************************************************************************
*
* Copyright (C) 2001-2002 Microsoft Corporation. All Rights Reserved.
*
* File: dp8simdllmain.cpp
*
* Content: DP8SIM DLL entry points.
*
* History:
* Date By Reason
* ======== ======== =========
* 04/23/01 VanceO Created.
*
***************************************************************************/
#include "dp8simi.h"
//=============================================================================
// External globals
//=============================================================================
volatile LONG g_lOutstandingInterfaceCount = 0; // number of outstanding interfaces
HINSTANCE g_hDLLInstance = NULL; // handle to this DLL instance
DNCRITICAL_SECTION g_csGlobalsLock; // lock protecting all of the following globals
CBilink g_blDP8SimSPObjs; // bilink of all the DP8SimSP interface objects
CBilink g_blDP8SimControlObjs; // bilink of all the DP8SimControl interface objects
UINT g_uiRandShr3 = 0; // global holding value for Shr3 random number sequence generator
UINT g_uiRandCong = 0; // global holding value for congruential random number sequence generator
//=============================================================================
// Supported SPs table
//=============================================================================
typedef struct _SUPPORTEDSP
{
const CLSID * pclsidFakeSP; // pointer to class ID for fake SP
const CLSID * pclsidRealSP; // pointer to class ID for real SP
const WCHAR * pwszVerIndProgID; // version independent prog ID for fake SP COM object, must match the sub key for pwszServiceProviderKey
const WCHAR * pwszProgID; // prog ID for fake SP COM object
const WCHAR * pwszDesc; // description for fake SP COM object
const WCHAR * pwszServiceProviderKey; // service provider key string, sub key must match pwszVerIndProgID
UINT uiFriendlyNameResourceID; // ID of fake SP's name string resource
} SUPPORTEDSP;
const SUPPORTEDSP c_aSupportedSPs[] =
{
{
(&CLSID_NETWORKSIMULATOR_DP8SP_TCPIP),
(&CLSID_DP8SP_TCPIP),
L"DP8SimTCPIP",
L"DP8SimTCPIP.1",
L"DirectPlay8 Network Simulator TCP/IP Service Provider",
DPN_REG_LOCAL_SP_SUBKEY L"\\DP8SimTCPIP",
IDS_FRIENDLYNAME_TCPIP,
},
};
#define NUM_SUPPORTED_SPS (sizeof(c_aSupportedSPs) / sizeof(c_aSupportedSPs[0]))
//=============================================================================
// Defines
//=============================================================================
#define MAX_RESOURCE_STRING_LENGTH _MAX_PATH
//=============================================================================
// Macros
//=============================================================================
// 3-shift register generator
//
// Original comments:
// SHR3 is a 3-shift-register generator with period 2^32-1. It uses
// y(n)=y(n-1)(I+L^17)(I+R^13)(I+L^5), with the y's viewed as binary vectors,
// L the 32x32 binary matrix that shifts a vector left 1, and R its transpose.
// SHR3 seems to pass all except those related to the binary rank test, since
// 32 successive values, as binary vectors, must be linearly independent,
// while 32 successive truly random 32-bit integers, viewed as binary vectors,
// will be linearly independent only about 29% of the time.
#define RANDALG_SHR3() (g_uiRandShr3 = g_uiRandShr3 ^ (g_uiRandShr3 << 17), g_uiRandShr3 = g_uiRandShr3 ^ (g_uiRandShr3 >> 13), g_uiRandShr3 = g_uiRandShr3 ^ (g_uiRandShr3 << 5))
// Congruential generator
//
// Original comments:
// CONG is a congruential generator with the widely used 69069 multiplier:
// x(n)=69069x(n-1)+1234567. It has period 2^32. The leading half of its 32
// bits seem to pass tests, but bits in the last half are too regular.
#define RANDALG_CONG() (g_uiRandCong = 69069UL * g_uiRandCong + 1234567UL)
//=============================================================================
// Local prototypes
//=============================================================================
BOOL InitializeProcessGlobals(void);
void CleanupProcessGlobals(void);
HRESULT LoadAndAllocString(UINT uiResourceID, WCHAR ** pwszString);
#undef DPF_MODNAME
#define DPF_MODNAME "DllMain"
//=============================================================================
// DllMain
//-----------------------------------------------------------------------------
//
// Description: DLL entry point.
//
// Arguments:
// HINSTANCE hDllInst - Handle to this DLL module.
// DWORD dwReason - Reason for calling this function.
// LPVOID lpvReserved - Reserved.
//
// Returns: TRUE if all goes well, FALSE otherwise.
//=============================================================================
BOOL WINAPI DllMain(HINSTANCE hDllInst,
DWORD dwReason,
LPVOID lpvReserved)
{
switch (dwReason)
{
case DLL_PROCESS_ATTACH:
{
DPFX(DPFPREP, 2, "====> ENTER: DLLMAIN(%p): Process Attach: %08lx, tid=%08lx",
DllMain, GetCurrentProcessId(), GetCurrentThreadId());
DNASSERT(g_hDLLInstance == NULL);
g_hDLLInstance = hDllInst;
//
// Attempt to initialize the OS abstraction layer.
//
if (! DNOSIndirectionInit(0))
{
DPFX(DPFPREP, 0, "Failed to initialize OS indirection layer!");
return FALSE;
}
//
// Attempt to initialize COM.
//
if (FAILED(COM_Init()))
{
DPFX(DPFPREP, 0, "Failed to initialize COM indirection layer!");
DNOSIndirectionDeinit();
return FALSE;
}
//
// Attempt to initialize process-global items.
//
if (! InitializeProcessGlobals())
{
DPFX(DPFPREP, 0, "Failed to initialize globals!");
COM_Free();
DNOSIndirectionDeinit();
return FALSE;
}
//
// We don't need thread attach/detach messages.
//
DisableThreadLibraryCalls(hDllInst);
return TRUE;
break;
}
case DLL_PROCESS_DETACH:
{
DPFX(DPFPREP, 2, "====> EXIT: DLLMAIN(%p): Process Detach %08lx, tid=%08lx",
DllMain, GetCurrentProcessId(), GetCurrentThreadId());
DNASSERT(g_hDLLInstance != NULL);
g_hDLLInstance = NULL;
CleanupProcessGlobals();
COM_Free();
DNOSIndirectionDeinit();
return TRUE;
break;
}
default:
{
DNASSERT(FALSE);
break;
}
}
return FALSE;
} // DllMain
#undef DPF_MODNAME
#define DPF_MODNAME "DllRegisterServer"
//=============================================================================
// DllRegisterServer
//-----------------------------------------------------------------------------
//
// Description: Registers the DP8Sim COM object.
//
// Arguments: None.
//
// Returns: HRESULT
// S_OK - Successfully registered DP8Sim.
// E_FAIL - Failed registering DP8Sim.
//=============================================================================
HRESULT WINAPI DllRegisterServer(void)
{
HRESULT hr;
CRegistry RegObject;
DWORD dwLength;
DWORD dwSimulatedSP;
char szLocalPath[_MAX_PATH + 1];
WCHAR wszLocalPath[_MAX_PATH + 1];
WCHAR * pwszFriendlyName = NULL;
//
// Retrieve the location of this DLL.
//
dwLength = GetModuleFileNameA(g_hDLLInstance, szLocalPath, _MAX_PATH);
if (dwLength == 0)
{
DPFX(DPFPREP, 0, "Couldn't read local path!");
hr = E_FAIL;
goto Failure;
}
//
// Include NULL termination.
//
szLocalPath[dwLength] = '\0';
dwLength++;
//
// Convert it to Unicode.
//
hr = STR_AnsiToWide(szLocalPath, dwLength, wszLocalPath, &dwLength);
if (hr != S_OK)
{
DPFX(DPFPREP, 0, "Could not convert ANSI path to Unicode!");
goto Failure;
}
//
// Register the control COM object CLSID.
//
if (! RegObject.Register(L"DP8SimControl.1",
L"DirectPlay8 Network Simulator Control Object",
wszLocalPath,
&CLSID_DP8SimControl,
L"DP8SimControl"))
{
DPFX(DPFPREP, 0, "Could not register DP8SimControl object!");
hr = E_FAIL;
goto Failure;
}
//
// Register all of the simulated SPs.
//
for(dwSimulatedSP = 0; dwSimulatedSP < NUM_SUPPORTED_SPS; dwSimulatedSP++)
{
if (! RegObject.Register(c_aSupportedSPs[dwSimulatedSP].pwszProgID,
c_aSupportedSPs[dwSimulatedSP].pwszDesc,
wszLocalPath,
c_aSupportedSPs[dwSimulatedSP].pclsidFakeSP,
c_aSupportedSPs[dwSimulatedSP].pwszVerIndProgID))
{
DPFX(DPFPREP, 0, "Could not register simulated SP %u object!",
dwSimulatedSP);
hr = E_FAIL;
goto Failure;
}
hr = LoadAndAllocString(c_aSupportedSPs[dwSimulatedSP].uiFriendlyNameResourceID,
&pwszFriendlyName);
if (FAILED(hr))
{
DPFX(DPFPREP, 0, "Could not load friendly name string (err = 0x%lx)!", hr);
goto Failure;
}
if (! RegObject.Open(HKEY_LOCAL_MACHINE, c_aSupportedSPs[dwSimulatedSP].pwszServiceProviderKey, FALSE, TRUE))
{
DPFX(DPFPREP, 0, "Could not open service provider key!");
hr = E_FAIL;
goto Failure;
}
RegObject.WriteString(DPN_REG_KEYNAME_FRIENDLY_NAME, pwszFriendlyName);
RegObject.WriteGUID(DPN_REG_KEYNAME_GUID, *(c_aSupportedSPs[dwSimulatedSP].pclsidFakeSP));
RegObject.Close();
DNFree(pwszFriendlyName);
pwszFriendlyName = NULL;
}
hr = S_OK;
Exit:
return hr;
Failure:
if (pwszFriendlyName != NULL)
{
DNFree(pwszFriendlyName);
pwszFriendlyName = NULL;
}
goto Exit;
} // DllRegisterServer
#undef DPF_MODNAME
#define DPF_MODNAME "DllUnregisterServer"
//=============================================================================
// DllUnregisterServer
//-----------------------------------------------------------------------------
//
// Description: Unregisters the DP8Sim COM object.
//
// Arguments: None.
//
// Returns: HRESULT
// S_OK - Successfully unregistered DP8Sim.
// E_FAIL - Failed unregistering DP8Sim.
//=============================================================================
STDAPI DllUnregisterServer(void)
{
HRESULT hr;
CRegistry RegObject;
DWORD dwSimulatedSP;
//
// Unregister the control class.
//
if (! RegObject.UnRegister(&CLSID_DP8SimControl))
{
DPFX(DPFPREP, 0, "Failed to unregister DP8Sim control object!");
hr = E_FAIL;
goto Failure;
}
//
// Unregister all of the simulated SPs.
//
if (! RegObject.Open(HKEY_LOCAL_MACHINE, DPN_REG_LOCAL_SP_SUBKEY, FALSE, FALSE, FALSE))
{
DPFX(DPFPREP, 0, "Could not open HKEY_LOCAL_MACHINE!");
hr = E_FAIL;
goto Failure;
}
for(dwSimulatedSP = 0; dwSimulatedSP < NUM_SUPPORTED_SPS; dwSimulatedSP++)
{
if (! RegObject.UnRegister(c_aSupportedSPs[dwSimulatedSP].pclsidFakeSP))
{
DPFX(DPFPREP, 0, "Could not unregister simulated SP %u object!",
dwSimulatedSP);
hr = E_FAIL;
goto Failure;
}
if (! RegObject.DeleteSubKey(c_aSupportedSPs[dwSimulatedSP].pwszVerIndProgID))
{
DPFX(DPFPREP, 0, "Could not delete simulated SP %u's key!",
dwSimulatedSP);
hr = E_FAIL;
goto Failure;
}
}
RegObject.Close();
hr = S_OK;
Exit:
return hr;
Failure:
//
// Rely on RegObject destructure to close registry key.
//
goto Exit;
} // DllUnregisterServer
#undef DPF_MODNAME
#define DPF_MODNAME "InitializeProcessGlobals"
//=============================================================================
// InitializeProcessGlobals
//-----------------------------------------------------------------------------
//
// Description: Initialize global items needed for the DLL to operate.
//
// Arguments: None.
//
// Returns: TRUE if successful, FALSE if an error occurred.
//=============================================================================
BOOL InitializeProcessGlobals(void)
{
BOOL fReturn = TRUE;
BOOL fInittedGlobalLock = FALSE;
if (! DNInitializeCriticalSection(&g_csGlobalsLock))
{
DPFX(DPFPREP, 0, "Failed to initialize global lock!");
goto Failure;
}
fInittedGlobalLock = TRUE;
//
// Don't allow critical section reentry.
//
DebugSetCriticalSectionRecursionCount(&g_csGlobalsLock, 0);
if (!InitializePools())
{
DPFX(DPFPREP, 0, "Failed initializing pools!");
goto Failure;
}
g_blDP8SimSPObjs.Initialize();
g_blDP8SimControlObjs.Initialize();
//
// Seed the random number generator with the current time.
//
InitializeGlobalRand(GETTIMESTAMP());
Exit:
return fReturn;
Failure:
if (fInittedGlobalLock)
{
DNDeleteCriticalSection(&g_csGlobalsLock);
}
fReturn = FALSE;
goto Exit;
} // InitializeProcessGlobals
#undef DPF_MODNAME
#define DPF_MODNAME "CleanupProcessGlobals"
//=============================================================================
// CleanupProcessGlobals
//-----------------------------------------------------------------------------
//
// Description: Releases global items used by DLL.
//
// Arguments: None.
//
// Returns: None.
//=============================================================================
void CleanupProcessGlobals(void)
{
CBilink * pBilink;
CDP8SimSP * pDP8SimSP;
CDP8SimControl * pDP8SimControl;
if (! g_blDP8SimSPObjs.IsEmpty())
{
DNASSERT(! "DP8Sim DLL unloading without all SP objects having been released!");
//
// Force close all the objects still outstanding.
//
pBilink = g_blDP8SimSPObjs.GetNext();
while (pBilink != &g_blDP8SimSPObjs)
{
pDP8SimSP = DP8SIMSP_FROM_BILINK(pBilink);
pBilink = pBilink->GetNext();
DPFX(DPFPREP, 0, "Forcefully releasing SP object 0x%p!", pDP8SimSP);
pDP8SimSP->Close(); // ignore error
//
// Forcefully remove it from the list and delete it instead of
// using pDP8SimSP->Release().
//
pDP8SimSP->m_blList.RemoveFromList();
pDP8SimSP->UninitializeObject();
delete pDP8SimSP;
}
}
if (! g_blDP8SimControlObjs.IsEmpty())
{
DNASSERT(! "DP8Sim DLL unloading without all Control objects having been released!");
//
// Force close all the objects still outstanding.
//
pBilink = g_blDP8SimControlObjs.GetNext();
while (pBilink != &g_blDP8SimControlObjs)
{
pDP8SimControl = DP8SIMCONTROL_FROM_BILINK(pBilink);
pBilink = pBilink->GetNext();
DPFX(DPFPREP, 0, "Forcefully releasing Control object 0x%p!", pDP8SimControl);
pDP8SimControl->Close(0); // ignore error
//
// Forcefully remove it from the list and delete it instead of
// using pDP8SimControl->Release().
//
pDP8SimControl->m_blList.RemoveFromList();
pDP8SimControl->UninitializeObject();
delete pDP8SimControl;
}
}
CleanupPools();
DNDeleteCriticalSection(&g_csGlobalsLock);
} // CleanupProcessGlobals
#undef DPF_MODNAME
#define DPF_MODNAME "LoadAndAllocString"
//=============================================================================
// LoadAndAllocString
//-----------------------------------------------------------------------------
//
// Description: DNMallocs a wide character string from the given resource ID.
//
// Arguments:
// UINT uiResourceID - Resource ID to load.
// WCHAR ** pwszString - Place to store pointer to allocated string.
//
// Returns: HRESULT
//=============================================================================
HRESULT LoadAndAllocString(UINT uiResourceID, WCHAR ** pwszString)
{
HRESULT hr = DPN_OK;
int iLength;
if (DNGetOSType() == VER_PLATFORM_WIN32_NT)
{
WCHAR wszTmpBuffer[MAX_RESOURCE_STRING_LENGTH];
iLength = LoadStringW(g_hDLLInstance, uiResourceID, wszTmpBuffer, MAX_RESOURCE_STRING_LENGTH );
if (iLength == 0)
{
hr = GetLastError();
DPFX(DPFPREP, 0, "Unable to load resource ID %d error 0x%x", uiResourceID, hr);
(*pwszString) = NULL;
goto Exit;
}
(*pwszString) = (WCHAR*) DNMalloc((iLength + 1) * sizeof(WCHAR));
if ((*pwszString) == NULL)
{
DPFX(DPFPREP, 0, "Memory allocation failure!");
hr = DPNERR_OUTOFMEMORY;
goto Exit;
}
wcscpy((*pwszString), wszTmpBuffer);
}
else
{
char szTmpBuffer[MAX_RESOURCE_STRING_LENGTH];
iLength = LoadStringA(g_hDLLInstance, uiResourceID, szTmpBuffer, MAX_RESOURCE_STRING_LENGTH );
if (iLength == 0)
{
hr = GetLastError();
DPFX(DPFPREP, 0, "Unable to load resource ID %u (err =0x%lx)!", uiResourceID, hr);
(*pwszString) = NULL;
goto Exit;
}
(*pwszString) = (WCHAR*) DNMalloc((iLength + 1) * sizeof(WCHAR));
if ((*pwszString) == NULL)
{
DPFX(DPFPREP, 0, "Memory allocation failure!");
hr = DPNERR_OUTOFMEMORY;
goto Exit;
}
hr = STR_jkAnsiToWide((*pwszString), szTmpBuffer, (iLength + 1));
if (hr == DPN_OK)
{
hr = GetLastError();
DPFX(DPFPREP, 0, "Unable to convert from ANSI to Unicode (err =0x%lx)!", hr);
goto Exit;
}
}
Exit:
return hr;
} // LoadAndAllocString
#undef DPF_MODNAME
#define DPF_MODNAME "InitializeGlobalRand"
//=============================================================================
// InitializeGlobalRand
//-----------------------------------------------------------------------------
//
// Description: Initializes the global psuedo-random number generator, using
// the given seed value.
//
// Based off algorithms posted to usenet by George Marsaglia.
//
// Arguments:
// DWORD dwSeed - Seed to use.
//
// Returns: None.
//=============================================================================
void InitializeGlobalRand(const DWORD dwSeed)
{
//
// We don't need to hold a lock, since this should only be done once,
// during initialization time.
//
g_uiRandShr3 = dwSeed;
g_uiRandCong = dwSeed;
} // InitializeGlobalRand
#undef DPF_MODNAME
#define DPF_MODNAME "GetGlobalRand"
//=============================================================================
// GetGlobalRand
//-----------------------------------------------------------------------------
//
// Description: Generates a pseudo-random positive double between 0.0 and
// 1.0, inclusive.
//
// Based off algorithms posted to usenet by George Marsaglia.
//
// Arguments: None.
//
// Returns: Pseudo-random number.
//=============================================================================
double GetGlobalRand(void)
{
double dResult;
DNEnterCriticalSection(&g_csGlobalsLock);
dResult = (RANDALG_CONG() + RANDALG_SHR3()) * 2.328306e-10f;
DNLeaveCriticalSection(&g_csGlobalsLock);
return dResult;
} // GetGlobalRand
#undef DPF_MODNAME
#define DPF_MODNAME "DoCreateInstance"
//=============================================================================
// DoCreateInstance
//-----------------------------------------------------------------------------
//
// Description: Creates an instance of an interface. Required by the general
// purpose class factory functions.
//
// Arguments:
// LPCLASSFACTORY This - Pointer to class factory.
// LPUNKNOWN pUnkOuter - Pointer to unknown interface.
// REFCLSID rclsid - Reference of GUID of desired interface.
// REFIID riid - Reference to another GUID?
// LPVOID * ppvObj - Pointer to pointer to interface.
//
// Returns: HRESULT
//=============================================================================
HRESULT DoCreateInstance(LPCLASSFACTORY This,
LPUNKNOWN pUnkOuter,
REFCLSID rclsid,
REFIID riid,
LPVOID * ppvObj)
{
HRESULT hr;
CDP8SimSP * pDP8SimSP = NULL;
CDP8SimControl * pDP8SimControl = NULL;
DWORD dwSimulatedSP;
DNASSERT(ppvObj != NULL);
//
// See if it's the control object.
//
if (IsEqualCLSID(rclsid, CLSID_DP8SimControl))
{
//
// Create the object instance.
//
pDP8SimControl = new CDP8SimControl;
if (pDP8SimControl == NULL)
{
hr = E_OUTOFMEMORY;
goto Failure;
}
//
// Initialize the base object (which might fail).
//
hr = pDP8SimControl->InitializeObject();
if (hr != S_OK)
{
delete pDP8SimControl;
pDP8SimControl = NULL;
goto Failure;
}
//
// Add it to the global list.
//
DNEnterCriticalSection(&g_csGlobalsLock);
pDP8SimControl->m_blList.InsertBefore(&g_blDP8SimControlObjs);
g_lOutstandingInterfaceCount++; // update count so DllCanUnloadNow works correctly
DNLeaveCriticalSection(&g_csGlobalsLock);
//
// Get the right interface for the caller and bump the refcount.
//
hr = pDP8SimControl->QueryInterface(riid, ppvObj);
if (hr != S_OK)
{
goto Failure;
}
}
else
{
//
// Look up the real SP we're replacing.
//
for(dwSimulatedSP = 0; dwSimulatedSP < NUM_SUPPORTED_SPS; dwSimulatedSP++)
{
if (IsEqualCLSID(rclsid, *(c_aSupportedSPs[dwSimulatedSP].pclsidFakeSP)))
{
break;
}
}
//
// If we didn't find it
//
if (dwSimulatedSP >= NUM_SUPPORTED_SPS)
{
DPFX(DPFPREP, 0, "Unrecognized service provider class ID!");
hr = E_UNEXPECTED;
goto Failure;
}
//
// Create the object instance.
//
pDP8SimSP = new CDP8SimSP(c_aSupportedSPs[dwSimulatedSP].pclsidFakeSP,
c_aSupportedSPs[dwSimulatedSP].pclsidRealSP);
if (pDP8SimSP == NULL)
{
hr = E_OUTOFMEMORY;
goto Failure;
}
//
// Initialize the base object (which might fail).
//
hr = pDP8SimSP->InitializeObject();
if (hr != S_OK)
{
DPFX(DPFPREP, 0, "Couldn't initialize object!");
delete pDP8SimSP;
pDP8SimSP = NULL;
goto Failure;
}
//
// Add it to the global list.
//
DNEnterCriticalSection(&g_csGlobalsLock);
pDP8SimSP->m_blList.InsertBefore(&g_blDP8SimSPObjs);
g_lOutstandingInterfaceCount++; // update count so DllCanUnloadNow works correctly
DNLeaveCriticalSection(&g_csGlobalsLock);
//
// Get the right interface for the caller and bump the refcount.
//
hr = pDP8SimSP->QueryInterface(riid, ppvObj);
if (hr != S_OK)
{
goto Failure;
}
}
Exit:
//
// Release the local reference to the objec(s)t. If this function was
// successful, there's still a reference in ppvObj.
//
if (pDP8SimSP != NULL)
{
pDP8SimSP->Release();
pDP8SimSP = NULL;
}
if (pDP8SimControl != NULL)
{
pDP8SimControl->Release();
pDP8SimControl = NULL;
}
return hr;
Failure:
//
// Make sure we don't hand back a pointer.
//
(*ppvObj) = NULL;
goto Exit;
} // DoCreateInstance
#undef DPF_MODNAME
#define DPF_MODNAME "IsClassImplemented"
//=============================================================================
// IsClassImplemented
//-----------------------------------------------------------------------------
//
// Description: Determine if a class is implemented in this DLL. Required by
// the general purpose class factory functions.
//
// Arguments:
// REFCLSID rclsid - Reference to class GUID.
//
// Returns: BOOL
// TRUE - This DLL implements the class.
// FALSE - This DLL doesn't implement the class.
//=============================================================================
BOOL IsClassImplemented(REFCLSID rclsid)
{
DWORD dwSimulatedSP;
if (IsEqualCLSID(rclsid, CLSID_DP8SimControl))
{
return TRUE;
}
//
// Check if this is a valid simulated SP.
//
for(dwSimulatedSP = 0; dwSimulatedSP < NUM_SUPPORTED_SPS; dwSimulatedSP++)
{
if (IsEqualCLSID(rclsid, *(c_aSupportedSPs[dwSimulatedSP].pclsidFakeSP)))
{
return TRUE;
}
}
return FALSE;
} // IsClassImplemented