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.
753 lines
19 KiB
753 lines
19 KiB
/*++
|
|
|
|
Copyright (c) 1997 Microsoft Corporation
|
|
All rights reserved
|
|
|
|
Module Name:
|
|
|
|
Downlevel cluster port support.
|
|
|
|
Abstract:
|
|
|
|
Supports mixing and matching downlevel and uplevel language
|
|
and monitor ports.
|
|
|
|
Author:
|
|
|
|
Environment:
|
|
|
|
User Mode -Win32
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include <precomp.h>
|
|
#pragma hdrstop
|
|
|
|
|
|
/********************************************************************
|
|
|
|
Downlevel Port Monitor (Dp)
|
|
|
|
Dp support is used when we have an uplevel language monitor
|
|
and downlevel port monitor. We pass a stub function vector
|
|
to the LM and set the hMonitor to the downlevel pIniMonitor.
|
|
|
|
When we get called, we can dereference the hMonitor to call the
|
|
real downlevel monitor.
|
|
|
|
********************************************************************/
|
|
|
|
|
|
BOOL
|
|
DpEnumPorts(
|
|
HANDLE hMonitor,
|
|
LPWSTR pName,
|
|
DWORD Level,
|
|
LPBYTE pPorts,
|
|
DWORD cbBuf,
|
|
LPDWORD pcbNeeded,
|
|
LPDWORD pcReturned
|
|
)
|
|
{
|
|
PINIMONITOR pIniMonitor = (PINIMONITOR)hMonitor;
|
|
|
|
return pIniMonitor->Monitor.pfnEnumPorts( pName,
|
|
Level,
|
|
pPorts,
|
|
cbBuf,
|
|
pcbNeeded,
|
|
pcReturned );
|
|
}
|
|
|
|
BOOL
|
|
DpOpenPort(
|
|
HANDLE hMonitor,
|
|
LPWSTR pName,
|
|
PHANDLE pHandle
|
|
)
|
|
{
|
|
PINIMONITOR pIniMonitor = (PINIMONITOR)hMonitor;
|
|
|
|
return pIniMonitor->Monitor.pfnOpenPort( pName, pHandle );
|
|
}
|
|
|
|
BOOL
|
|
DpOpenPortEx(
|
|
HANDLE hMonitor,
|
|
LPWSTR pPortName,
|
|
LPWSTR pPrinterName,
|
|
PHANDLE pHandle,
|
|
struct _MONITOR FAR *pMonitor
|
|
)
|
|
{
|
|
PINIMONITOR pIniMonitor = (PINIMONITOR)hMonitor;
|
|
|
|
return pIniMonitor->Monitor.pfnOpenPortEx( pPortName,
|
|
pPrinterName,
|
|
pHandle,
|
|
pMonitor );
|
|
}
|
|
|
|
BOOL
|
|
DpAddPort(
|
|
HANDLE hMonitor,
|
|
LPWSTR pName,
|
|
HWND hWnd,
|
|
LPWSTR pMonitorName
|
|
)
|
|
{
|
|
PINIMONITOR pIniMonitor = (PINIMONITOR)hMonitor;
|
|
|
|
return pIniMonitor->Monitor.pfnAddPort( pName,
|
|
hWnd,
|
|
pMonitorName );
|
|
}
|
|
|
|
BOOL
|
|
DpAddPortEx(
|
|
HANDLE hMonitor,
|
|
LPWSTR pName,
|
|
DWORD Level,
|
|
LPBYTE pBuffer,
|
|
LPWSTR pMonitorName
|
|
)
|
|
{
|
|
PINIMONITOR pIniMonitor = (PINIMONITOR)hMonitor;
|
|
|
|
return pIniMonitor->Monitor.pfnAddPortEx( pName,
|
|
Level,
|
|
pBuffer,
|
|
pMonitorName );
|
|
}
|
|
|
|
BOOL
|
|
DpConfigurePort(
|
|
HANDLE hMonitor,
|
|
LPWSTR pName,
|
|
HWND hWnd,
|
|
LPWSTR pPortName
|
|
)
|
|
{
|
|
PINIMONITOR pIniMonitor = (PINIMONITOR)hMonitor;
|
|
|
|
return pIniMonitor->Monitor.pfnConfigurePort( pName,
|
|
hWnd,
|
|
pPortName );
|
|
}
|
|
|
|
BOOL
|
|
DpDeletePort(
|
|
HANDLE hMonitor,
|
|
LPWSTR pName,
|
|
HWND hWnd,
|
|
LPWSTR pPortName
|
|
)
|
|
{
|
|
PINIMONITOR pIniMonitor = (PINIMONITOR)hMonitor;
|
|
|
|
return pIniMonitor->Monitor.pfnDeletePort( pName,
|
|
hWnd,
|
|
pPortName );
|
|
}
|
|
|
|
BOOL
|
|
DpXcvOpenPort(
|
|
HANDLE hMonitor,
|
|
LPCWSTR pszObject,
|
|
ACCESS_MASK GrantedAccess,
|
|
PHANDLE phXcv
|
|
)
|
|
{
|
|
PINIMONITOR pIniMonitor = (PINIMONITOR)hMonitor;
|
|
|
|
return pIniMonitor->Monitor.pfnXcvOpenPort( pszObject,
|
|
GrantedAccess,
|
|
phXcv );
|
|
}
|
|
|
|
|
|
/********************************************************************
|
|
|
|
Downlevel Language Monitor (Dl)
|
|
|
|
Dl support is used when we have a downlevel language monitor
|
|
and uplevel port monitor.
|
|
|
|
This is very messy, since the language monitor is given the
|
|
ports function vector directly, and we don't have any extra
|
|
handles to pass around state information.
|
|
|
|
Instead, we overload the name string yet again. The port name
|
|
is converted to:
|
|
|
|
{NormalPortName},{pIniMonitorHex}
|
|
|
|
LPT1:,a53588
|
|
|
|
We then strip off the two trailing hex numbers and pass LPT1:
|
|
back.
|
|
|
|
********************************************************************/
|
|
|
|
BOOL
|
|
GetDlPointers(
|
|
IN LPCWSTR pszName,
|
|
OUT LPWSTR pszNameNew,
|
|
OUT PINIMONITOR *ppIniMonitor,
|
|
IN DWORD cchBufferSize
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Hack function to take a pszName and convert it to a new name
|
|
string with two additional parameters: hMonitor and pMonitor2
|
|
|
|
Arguments:
|
|
|
|
pszName - Hacked up name overloaded with pIniMonitor.
|
|
|
|
pszNameNew - Receives "real" shorter name of the port.
|
|
|
|
ppIniMonitor - Receives cracked pIniMonitor.
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOL bReturn = FALSE;
|
|
LPCWSTR p;
|
|
LPCWSTR p1 = NULL;
|
|
|
|
for( p = pszName; p = wcschr( p, TEXT( ',' )); ){
|
|
p1 = p;
|
|
++p;
|
|
}
|
|
|
|
if( p1 ){
|
|
|
|
StringCchCopy(pszNameNew, cchBufferSize, pszName );
|
|
pszNameNew[p1-pszName] = 0;
|
|
|
|
++p1;
|
|
|
|
*ppIniMonitor = (PINIMONITOR)atox( p1 );
|
|
|
|
__try {
|
|
|
|
bReturn = ( (*ppIniMonitor)->signature == IMO_SIGNATURE );
|
|
|
|
} except( EXCEPTION_EXECUTE_HANDLER ){
|
|
|
|
}
|
|
|
|
if( bReturn ){
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
SetLastError( ERROR_NOT_SUPPORTED );
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL
|
|
CreateDlName(
|
|
IN LPCWSTR pszName,
|
|
IN PINIMONITOR pIniMonitor,
|
|
IN OUT PWSTR pszNameNew,
|
|
IN SIZE_T cchNameNew
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Create a downlevel name that can be parsed by GetDlPointers.
|
|
|
|
Arguments:
|
|
|
|
pszName - Name of port. The newly created name must be < MAX_PATH,
|
|
and since we need to append one hex values (4 characters) plus
|
|
one comma, we need to verify that the string length has at least
|
|
5 characters left.
|
|
|
|
pIniMonitor - Monitor structure of the uplevel port monitor.
|
|
|
|
Return Value:
|
|
|
|
TRUE - Success
|
|
|
|
FALSE - Failure, due to too long port name length.
|
|
|
|
--*/
|
|
|
|
{
|
|
return BoolFromHResult(StringCchPrintf(pszNameNew, cchNameNew, TEXT( "%s,%p" ), pszName, pIniMonitor));
|
|
}
|
|
|
|
|
|
FARPROC gafpMonitor2Stub[] = {
|
|
(FARPROC) &DpEnumPorts,
|
|
(FARPROC) &DpOpenPort,
|
|
NULL, // OpenPortEx
|
|
NULL, // StartDocPort
|
|
NULL, // WritePort
|
|
NULL, // ReadPort
|
|
NULL, // EndDocPort
|
|
NULL, // ClosePort
|
|
(FARPROC) &DpAddPort,
|
|
(FARPROC) &DpAddPortEx,
|
|
(FARPROC) &DpConfigurePort,
|
|
(FARPROC) &DpDeletePort,
|
|
NULL,
|
|
NULL,
|
|
(FARPROC) &DpXcvOpenPort,
|
|
NULL, // XcvDataPortW
|
|
NULL, // XcvClosePortW
|
|
NULL // Shutdown
|
|
};
|
|
|
|
|
|
|
|
BOOL
|
|
DlEnumPorts(
|
|
LPWSTR pName,
|
|
DWORD Level,
|
|
LPBYTE pPorts,
|
|
DWORD cbBuf,
|
|
LPDWORD pcbNeeded,
|
|
LPDWORD pcReturned
|
|
)
|
|
{
|
|
WCHAR szName[MAX_PATH];
|
|
PINIMONITOR pIniMonitor;
|
|
|
|
if( GetDlPointers( pName, szName, &pIniMonitor, COUNTOF(szName) )){
|
|
|
|
return pIniMonitor->Monitor2.pfnEnumPorts( pIniMonitor->hMonitor,
|
|
szName,
|
|
Level,
|
|
pPorts,
|
|
cbBuf,
|
|
pcbNeeded,
|
|
pcReturned );
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL
|
|
DlOpenPort(
|
|
LPWSTR pName,
|
|
PHANDLE pHandle
|
|
)
|
|
{
|
|
WCHAR szName[MAX_PATH];
|
|
PINIMONITOR pIniMonitor;
|
|
|
|
if( GetDlPointers( pName, szName, &pIniMonitor, COUNTOF(szName) )){
|
|
|
|
return pIniMonitor->Monitor2.pfnOpenPort( pIniMonitor->hMonitor,
|
|
szName,
|
|
pHandle );
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL
|
|
DlOpenPortEx(
|
|
LPWSTR pPortName,
|
|
LPWSTR pPrinterName,
|
|
PHANDLE pHandle,
|
|
struct _MONITOR FAR *pMonitor
|
|
)
|
|
{
|
|
SetLastError( ERROR_NOT_SUPPORTED );
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL
|
|
DlAddPort(
|
|
LPWSTR pName,
|
|
HWND hWnd,
|
|
LPWSTR pMonitorName
|
|
)
|
|
{
|
|
WCHAR szName[MAX_PATH];
|
|
PINIMONITOR pIniMonitor;
|
|
|
|
if( GetDlPointers( pName, szName, &pIniMonitor, COUNTOF(szName) )){
|
|
|
|
return pIniMonitor->Monitor2.pfnAddPort( pIniMonitor->hMonitor,
|
|
szName,
|
|
hWnd,
|
|
pMonitorName );
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL
|
|
DlAddPortEx(
|
|
LPWSTR pName,
|
|
DWORD Level,
|
|
LPBYTE pBuffer,
|
|
LPWSTR pMonitorName
|
|
)
|
|
{
|
|
WCHAR szName[MAX_PATH];
|
|
PINIMONITOR pIniMonitor;
|
|
|
|
if( GetDlPointers( pName, szName, &pIniMonitor, COUNTOF(szName) )){
|
|
|
|
return pIniMonitor->Monitor2.pfnAddPortEx( pIniMonitor->hMonitor,
|
|
pName,
|
|
Level,
|
|
pBuffer,
|
|
pMonitorName );
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL
|
|
DlConfigurePort(
|
|
LPWSTR pName,
|
|
HWND hWnd,
|
|
LPWSTR pPortName
|
|
)
|
|
{
|
|
WCHAR szName[MAX_PATH];
|
|
PINIMONITOR pIniMonitor;
|
|
|
|
if( GetDlPointers( pName, szName, &pIniMonitor, COUNTOF(szName) )){
|
|
|
|
return pIniMonitor->Monitor2.pfnConfigurePort( pIniMonitor->hMonitor,
|
|
szName,
|
|
hWnd,
|
|
pPortName );
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL
|
|
DlDeletePort(
|
|
LPWSTR pName,
|
|
HWND hWnd,
|
|
LPWSTR pPortName
|
|
)
|
|
{
|
|
WCHAR szName[MAX_PATH];
|
|
PINIMONITOR pIniMonitor;
|
|
|
|
if( GetDlPointers( pName, szName, &pIniMonitor, COUNTOF(szName) )){
|
|
|
|
return pIniMonitor->Monitor2.pfnDeletePort( pIniMonitor->hMonitor,
|
|
szName,
|
|
hWnd,
|
|
pPortName );
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
FARPROC gafpDlStub[] = {
|
|
(FARPROC) &DlEnumPorts,
|
|
(FARPROC) &DlOpenPort,
|
|
(FARPROC) &DlOpenPortEx,
|
|
NULL, // StartDocPort
|
|
NULL, // WritePort
|
|
NULL, // ReadPort
|
|
NULL, // EndDocPort
|
|
NULL, // ClosePort
|
|
(FARPROC) &DlAddPort,
|
|
(FARPROC) &DlAddPortEx,
|
|
(FARPROC) &DlConfigurePort,
|
|
(FARPROC) &DlDeletePort,
|
|
NULL,
|
|
NULL,
|
|
};
|
|
|
|
|
|
VOID
|
|
InitializeUMonitor(
|
|
PINIMONITOR pIniMonitor
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize an uplevel port monitor for downlevel support. When a
|
|
downlevel language monitor is used with an uplevel port monitor,
|
|
we need to setup stubs since the language monitor calls the port
|
|
monitor interfaces directly.
|
|
|
|
We create a downlevel function vector with patched entries and pass
|
|
it to the language monitor. The LM is passed in a formatted name
|
|
that has both the port name and also the pIniMonitor encoded in the
|
|
string.
|
|
|
|
Arguments:
|
|
|
|
pIniMonitor - Monitor to initialize.
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
|
|
{
|
|
FARPROC *pfpSrc;
|
|
FARPROC *pfpDest;
|
|
FARPROC *pfpStub;
|
|
INT i;
|
|
|
|
//
|
|
// Create the downlevel port monitor interface. This is
|
|
// used when we have a downlevel language monitor with an
|
|
// uplevel port monitor.
|
|
//
|
|
CopyMemory((LPBYTE)&pIniMonitor->Monitor, (LPBYTE)&pIniMonitor->Monitor2.pfnEnumPorts, sizeof( pIniMonitor->Monitor ));
|
|
|
|
for( i=0,
|
|
pfpSrc = (FARPROC*)&pIniMonitor->Monitor2.pfnEnumPorts,
|
|
pfpDest = (FARPROC*)&pIniMonitor->Monitor,
|
|
pfpStub = gafpDlStub;
|
|
|
|
i < sizeof( pIniMonitor->Monitor )/sizeof( *pfpDest );
|
|
|
|
++i, ++pfpDest, ++pfpStub, ++pfpSrc ){
|
|
|
|
*pfpDest = *pfpStub ?
|
|
*pfpStub :
|
|
*pfpSrc;
|
|
}
|
|
}
|
|
|
|
|
|
/********************************************************************
|
|
|
|
Initialize a Downlevel language or port monitor.
|
|
|
|
********************************************************************/
|
|
|
|
//
|
|
// List of monitor functions for downlevel (3.51) monitors. Instead
|
|
// of receiving a function vector, the spooler has to call GetProcAddress
|
|
// on each of these functions. The order of these ports must be in the
|
|
// same format as the pMonitor2 structure.
|
|
//
|
|
|
|
LPCSTR aszMonitorFunction[] = {
|
|
"EnumPortsW",
|
|
"OpenPort",
|
|
NULL,
|
|
"StartDocPort",
|
|
"WritePort",
|
|
"ReadPort",
|
|
"EndDocPort",
|
|
"ClosePort",
|
|
"AddPortW",
|
|
"AddPortExW",
|
|
"ConfigurePortW",
|
|
"DeletePortW",
|
|
NULL,
|
|
NULL,
|
|
"XcvOpenPortW",
|
|
"XcvDataPortW",
|
|
"XcvClosePortW"
|
|
};
|
|
|
|
|
|
PINIMONITOR
|
|
InitializeDMonitor(
|
|
PINIMONITOR pIniMonitor,
|
|
LPWSTR pszRegistryRoot
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize downlevel monitor.
|
|
|
|
Arguments:
|
|
|
|
pIniMonitor - Partially created pIniMonitor that needs to be initialized
|
|
with functions.
|
|
|
|
Return Value:
|
|
|
|
NULL - Initialization failed, but possibly because monitor could not
|
|
initialize. Still add monitor to spooler datastructures.
|
|
|
|
(PINIMONITOR)-1 - Failed.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOL (*pfnInitialize)(LPWSTR) = NULL;
|
|
BOOL (*pfnInitializeMonitorEx)(LPWSTR, LPMONITOR) = NULL;
|
|
LPMONITOREX (*pfnInitializePrintMonitor)(LPWSTR) = NULL;
|
|
LPMONITOREX pMonEx;
|
|
DWORD cbDpMonitor;
|
|
|
|
PINIMONITOR pReturnValue = (PINIMONITOR)-1;
|
|
|
|
//
|
|
// Try calling the entry points in the following order:
|
|
// InitializePrintMonitor,
|
|
// InitializeMonitorEx,
|
|
// InitializeMonitor
|
|
//
|
|
(FARPROC)pfnInitializePrintMonitor = GetProcAddress(
|
|
pIniMonitor->hModule,
|
|
"InitializePrintMonitor" );
|
|
if( !pfnInitializePrintMonitor ){
|
|
|
|
(FARPROC)pfnInitializeMonitorEx = GetProcAddress(
|
|
pIniMonitor->hModule,
|
|
"InitializeMonitorEx" );
|
|
|
|
if( !pfnInitializeMonitorEx ){
|
|
|
|
(FARPROC)pfnInitialize = GetProcAddress(
|
|
pIniMonitor->hModule,
|
|
"InitializeMonitor" );
|
|
}
|
|
}
|
|
|
|
if ( !pfnInitializePrintMonitor &&
|
|
!pfnInitializeMonitorEx &&
|
|
!pfnInitialize ) {
|
|
|
|
DBGMSG( DBG_WARNING,
|
|
( "InitializeDLPrintMonitor %ws GetProcAddress failed %d\n",
|
|
pszRegistryRoot,
|
|
GetLastError()));
|
|
} else {
|
|
|
|
BOOL bSuccess = FALSE;
|
|
|
|
LeaveSplSem();
|
|
|
|
if( pfnInitializePrintMonitor ) {
|
|
|
|
pMonEx = (*pfnInitializePrintMonitor)(pszRegistryRoot);
|
|
|
|
if( pMonEx ){
|
|
|
|
bSuccess = TRUE;
|
|
cbDpMonitor = pMonEx->dwMonitorSize;
|
|
CopyMemory((LPBYTE)&pIniMonitor->Monitor,
|
|
(LPBYTE)&pMonEx->Monitor,
|
|
min(pMonEx->dwMonitorSize, sizeof(MONITOR)));
|
|
}
|
|
|
|
} else if ( pfnInitializeMonitorEx ) {
|
|
|
|
bSuccess = (*pfnInitializeMonitorEx)( pszRegistryRoot,
|
|
&pIniMonitor->Monitor );
|
|
cbDpMonitor = sizeof(MONITOR);
|
|
|
|
} else {
|
|
|
|
INT i;
|
|
FARPROC* pfp;
|
|
|
|
bSuccess = (BOOL)((*pfnInitialize)(pszRegistryRoot));
|
|
cbDpMonitor = sizeof(MONITOR);
|
|
|
|
for( i=0, pfp=(FARPROC*)&pIniMonitor->Monitor;
|
|
i< COUNTOF( aszMonitorFunction );
|
|
++i, ++pfp ){
|
|
|
|
if( aszMonitorFunction[i] ){
|
|
|
|
*pfp = GetProcAddress( pIniMonitor->hModule,
|
|
aszMonitorFunction[i] );
|
|
}
|
|
}
|
|
}
|
|
|
|
EnterSplSem();
|
|
|
|
if( bSuccess ){
|
|
|
|
INT i;
|
|
INT iMax;
|
|
FARPROC* pfpSrc;
|
|
FARPROC* pfpDest;
|
|
FARPROC* pfpStub;
|
|
|
|
//
|
|
// Store away the pIniMonitor as the handle returned from the monitor.
|
|
// When we call the stub, it will cast it back to a pIniMonitor then
|
|
// use it to get to pIniMonitor->Monitor.fn.
|
|
//
|
|
pIniMonitor->hMonitor = (HANDLE)pIniMonitor;
|
|
|
|
//
|
|
// New size of the stub Monitor2 structure is the size of the
|
|
// downlevel monitor, plus the extra DWORD for Monitor2.cbSize.
|
|
//
|
|
pIniMonitor->Monitor2.cbSize = min( cbDpMonitor + sizeof( DWORD ),
|
|
sizeof( MONITOR2 ));
|
|
|
|
//
|
|
// The number of stub pointers we want to copy is the size of
|
|
// the struct, minus the extra DWORD that we added above.
|
|
//
|
|
iMax = (pIniMonitor->Monitor2.cbSize - sizeof( DWORD )) / sizeof( pfpSrc );
|
|
|
|
//
|
|
// We have copied the monitor entrypoints into the downlevel Monitor
|
|
// structure. Now we must run through the uplevel vector and fill
|
|
// it in with the stubs.
|
|
//
|
|
for( i=0,
|
|
pfpSrc = (FARPROC*)&pIniMonitor->Monitor,
|
|
pfpDest = (FARPROC*)&pIniMonitor->Monitor2.pfnEnumPorts,
|
|
pfpStub = (FARPROC*)gafpMonitor2Stub;
|
|
|
|
i< iMax;
|
|
|
|
++i, ++pfpSrc, ++pfpDest, ++pfpStub ){
|
|
|
|
if( *pfpSrc ){
|
|
|
|
//
|
|
// Stubs aren't needed for all routines. Only use them
|
|
// when they are needed; in other cases, just use the
|
|
// regular one.
|
|
//
|
|
*pfpDest = *pfpStub ?
|
|
*pfpStub :
|
|
*pfpSrc;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Success, return the original pIniMonitor.
|
|
//
|
|
pReturnValue = pIniMonitor;
|
|
|
|
} else {
|
|
|
|
DBGMSG( DBG_WARNING,
|
|
( "InitializeDLPrintMonitor %ws Init failed %d\n",
|
|
pszRegistryRoot,
|
|
GetLastError()));
|
|
|
|
//
|
|
// Some old (before NT 4.0) monitors may not initialize until
|
|
// reboot.
|
|
//
|
|
if( pfnInitialize ){
|
|
pReturnValue = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
return pReturnValue;
|
|
}
|