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.
2619 lines
52 KiB
2619 lines
52 KiB
/*++
|
|
|
|
Copyright (c) 1994 Microsoft Corporation
|
|
All rights reserved.
|
|
|
|
Module Name:
|
|
|
|
spcompat.cxx
|
|
|
|
Abstract:
|
|
|
|
porting the spool library code into printui
|
|
|
|
Author:
|
|
|
|
Lazar Ivanov (LazarI) Jul-05-2000
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "precomp.hxx"
|
|
#pragma hdrstop
|
|
|
|
///////////////////////////////////////////////////
|
|
// @@file@@ debug.cxx
|
|
///////////////////////////////////////////////////
|
|
|
|
#if DBG
|
|
|
|
/********************************************************************
|
|
|
|
TStatus automated error logging and codepath testing.
|
|
|
|
********************************************************************/
|
|
|
|
TStatusBase&
|
|
TStatusBase::
|
|
pNoChk(
|
|
VOID
|
|
)
|
|
{
|
|
_pszFileA = NULL;
|
|
return (TStatusBase&)*this;
|
|
}
|
|
|
|
|
|
|
|
TStatusBase&
|
|
TStatusBase::
|
|
pSetInfo(
|
|
UINT uDbg,
|
|
UINT uLine,
|
|
LPCSTR pszFileA,
|
|
LPCSTR pszModuleA
|
|
)
|
|
{
|
|
_uDbg = uDbg;
|
|
_uLine = uLine;
|
|
_pszFileA = pszFileA;
|
|
SPLASSERT( pszFileA );
|
|
|
|
_pszModuleA = pszModuleA;
|
|
|
|
return (TStatusBase&)*this;
|
|
}
|
|
|
|
|
|
DWORD
|
|
TStatus::
|
|
dwGetStatus(
|
|
VOID
|
|
)
|
|
{
|
|
//
|
|
// For now, return error code. Later it will return the actual
|
|
// error code.
|
|
//
|
|
return _dwStatus;
|
|
}
|
|
|
|
|
|
DWORD
|
|
TStatusBase::
|
|
operator=(
|
|
DWORD dwStatus
|
|
)
|
|
{
|
|
//
|
|
// Check if we have an error, and it's not one of the two
|
|
// accepted "safe" errors.
|
|
//
|
|
// If pszFileA is not set, then we can safely ignore the
|
|
// error as one the client intended.
|
|
//
|
|
if( _pszFileA &&
|
|
dwStatus != ERROR_SUCCESS &&
|
|
dwStatus != _dwStatusSafe1 &&
|
|
dwStatus != _dwStatusSafe2 &&
|
|
dwStatus != _dwStatusSafe3 ){
|
|
|
|
DBGMSG( DBG_WARN,
|
|
( "TStatus set to %d\nLine %d, %hs\n",
|
|
dwStatus,
|
|
_uLine,
|
|
_pszFileA ));
|
|
}
|
|
|
|
return _dwStatus = dwStatus;
|
|
}
|
|
|
|
/********************************************************************
|
|
|
|
Same, but for HRESULTs.
|
|
|
|
********************************************************************/
|
|
|
|
TStatusHBase&
|
|
TStatusHBase::
|
|
pNoChk(
|
|
VOID
|
|
)
|
|
{
|
|
_pszFileA = NULL;
|
|
return (TStatusH&)*this;
|
|
}
|
|
|
|
TStatusHBase&
|
|
TStatusHBase::
|
|
pSetInfo(
|
|
UINT uDbg,
|
|
UINT uLine,
|
|
LPCSTR pszFileA,
|
|
LPCSTR pszModuleA
|
|
)
|
|
{
|
|
_uDbg = uDbg;
|
|
_uLine = uLine;
|
|
_pszFileA = pszFileA;
|
|
SPLASSERT( pszFileA );
|
|
|
|
_pszModuleA = pszModuleA;
|
|
|
|
return (TStatusH&)*this;
|
|
}
|
|
|
|
HRESULT
|
|
TStatusHBase::
|
|
operator=(
|
|
HRESULT hrStatus
|
|
)
|
|
{
|
|
//
|
|
// Check if we have an error, and it's not one of the two
|
|
// accepted "safe" errors.
|
|
//
|
|
// If pszFileA is not set, then we can safely ignore the
|
|
// error as one the client intended.
|
|
//
|
|
|
|
|
|
if( _pszFileA &&
|
|
FAILED(hrStatus) &&
|
|
hrStatus != _hrStatusSafe1 &&
|
|
hrStatus != _hrStatusSafe2 &&
|
|
hrStatus != _hrStatusSafe3 ){
|
|
|
|
DBGMSG( DBG_WARN,
|
|
( "TStatusH set to %x\nLine %d, %hs\n",
|
|
hrStatus,
|
|
_uLine,
|
|
_pszFileA ));
|
|
}
|
|
|
|
return _hrStatus = hrStatus;
|
|
}
|
|
|
|
HRESULT
|
|
TStatusH::
|
|
hrGetStatus(
|
|
VOID
|
|
)
|
|
{
|
|
//
|
|
// For now, return error code. Later it will return the actual
|
|
// error code.
|
|
//
|
|
return _hrStatus;
|
|
}
|
|
/********************************************************************
|
|
|
|
Same, but for BOOLs.
|
|
|
|
********************************************************************/
|
|
|
|
TStatusBBase&
|
|
TStatusBBase::
|
|
pNoChk(
|
|
VOID
|
|
)
|
|
{
|
|
_pszFileA = NULL;
|
|
return (TStatusBBase&)*this;
|
|
}
|
|
|
|
TStatusBBase&
|
|
TStatusBBase::
|
|
pSetInfo(
|
|
UINT uDbg,
|
|
UINT uLine,
|
|
LPCSTR pszFileA,
|
|
LPCSTR pszModuleA
|
|
)
|
|
{
|
|
_uDbg = uDbg;
|
|
_uLine = uLine;
|
|
_pszFileA = pszFileA;
|
|
SPLASSERT( pszFileA );
|
|
|
|
_pszModuleA = pszModuleA;
|
|
|
|
return (TStatusBBase&)*this;
|
|
}
|
|
|
|
BOOL
|
|
TStatusB::
|
|
bGetStatus(
|
|
VOID
|
|
)
|
|
{
|
|
//
|
|
// For now, return error code. Later it will return the actual
|
|
// error code.
|
|
//
|
|
return _bStatus;
|
|
}
|
|
|
|
|
|
BOOL
|
|
TStatusBBase::
|
|
operator=(
|
|
BOOL bStatus
|
|
)
|
|
{
|
|
//
|
|
// Check if we have an error, and it's not one of the two
|
|
// accepted "safe" errors.
|
|
//
|
|
// If pszFileA is not set, then we can safely ignore the
|
|
// error as one the client intended.
|
|
//
|
|
if( _pszFileA && !bStatus ){
|
|
|
|
DWORD dwLastError = GetLastError();
|
|
|
|
if( dwLastError != _dwStatusSafe1 &&
|
|
dwLastError != _dwStatusSafe2 &&
|
|
dwLastError != _dwStatusSafe3 ){
|
|
|
|
DBGMSG( DBG_WARN,
|
|
( "TStatusB set to FALSE, LastError = %d\nLine %d, %hs\n",
|
|
GetLastError(),
|
|
_uLine,
|
|
_pszFileA ));
|
|
}
|
|
}
|
|
|
|
return _bStatus = bStatus;
|
|
}
|
|
|
|
#endif // DBG
|
|
|
|
///////////////////////////////////////////////////
|
|
// @@file@@ string.cxx
|
|
///////////////////////////////////////////////////
|
|
|
|
//
|
|
// Class specific NULL state.
|
|
//
|
|
TCHAR TString::gszNullState[2] = {0,0};
|
|
|
|
//
|
|
// Default construction.
|
|
//
|
|
TString::
|
|
TString(
|
|
VOID
|
|
) : _pszString( &TString::gszNullState[kValid] )
|
|
{
|
|
}
|
|
|
|
//
|
|
// Construction using an existing LPCTSTR string.
|
|
//
|
|
TString::
|
|
TString(
|
|
IN LPCTSTR psz
|
|
) : _pszString( &TString::gszNullState[kValid] )
|
|
{
|
|
bUpdate( psz );
|
|
}
|
|
|
|
//
|
|
// Destruction, ensure we don't free our NULL state.
|
|
//
|
|
TString::
|
|
~TString(
|
|
VOID
|
|
)
|
|
{
|
|
vFree( _pszString );
|
|
}
|
|
|
|
//
|
|
// Copy constructor.
|
|
//
|
|
TString::
|
|
TString(
|
|
const TString &String
|
|
) : _pszString( &TString::gszNullState[kValid] )
|
|
{
|
|
bUpdate( String._pszString );
|
|
}
|
|
|
|
//
|
|
// Indicates if a string has any usable data.
|
|
//
|
|
BOOL
|
|
TString::
|
|
bEmpty(
|
|
VOID
|
|
) const
|
|
{
|
|
return _pszString[0] == 0;
|
|
}
|
|
|
|
//
|
|
// Indicates if a string object is valid.
|
|
//
|
|
BOOL
|
|
TString::
|
|
bValid(
|
|
VOID
|
|
) const
|
|
{
|
|
return _pszString != &TString::gszNullState[kInValid];
|
|
}
|
|
|
|
//
|
|
// Return the length of the string.
|
|
//
|
|
UINT
|
|
TString::
|
|
uLen(
|
|
VOID
|
|
) const
|
|
{
|
|
return lstrlen( _pszString );
|
|
}
|
|
|
|
BOOL
|
|
TString::
|
|
bCat(
|
|
IN LPCTSTR psz
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Safe concatenation of the specified string to the string
|
|
object. If the allocation fails, return FALSE and the
|
|
original string is not modified.
|
|
|
|
Arguments:
|
|
|
|
psz - Input string, may be NULL.
|
|
|
|
Return Value:
|
|
|
|
TRUE = update successful
|
|
FALSE = update failed
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOL bStatus = FALSE;
|
|
|
|
//
|
|
// If a valid string was passed.
|
|
//
|
|
if( psz ){
|
|
|
|
LPTSTR pszTmp = _pszString;
|
|
|
|
//
|
|
// Allocate the new buffer consisting of the size of the orginal
|
|
// string plus the sizeof of the new string plus the null terminator.
|
|
//
|
|
int iLenCb = (
|
|
( lstrlen( pszTmp ) +
|
|
lstrlen( psz ) +
|
|
1 ) *
|
|
sizeof ( pszTmp[0] ) );
|
|
|
|
_pszString = (LPTSTR)AllocMem( iLenCb );
|
|
|
|
//
|
|
// If memory was not available.
|
|
//
|
|
if( !_pszString ){
|
|
|
|
//
|
|
// Release the original buffer.
|
|
//
|
|
vFree( pszTmp );
|
|
|
|
//
|
|
// Mark the string object as invalid.
|
|
//
|
|
_pszString = &TString::gszNullState[kInValid];
|
|
|
|
} else {
|
|
|
|
//
|
|
// Copy the string and concatenate the passed string.
|
|
//
|
|
StringCbCopy( _pszString, iLenCb, pszTmp );
|
|
StringCbCat( _pszString, iLenCb, psz );
|
|
|
|
//
|
|
// Release the original buffer.
|
|
//
|
|
vFree( pszTmp );
|
|
|
|
//
|
|
// Indicate success.
|
|
//
|
|
bStatus = TRUE;
|
|
|
|
}
|
|
|
|
//
|
|
// Skip null pointers, not an error.
|
|
//
|
|
} else {
|
|
|
|
bStatus = TRUE;
|
|
|
|
}
|
|
|
|
return bStatus;
|
|
}
|
|
|
|
BOOL
|
|
TString::
|
|
bLimitBuffer(
|
|
IN UINT nMaxLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Truncates th string if longer than nMaxLength
|
|
including the terminating character
|
|
|
|
Arguments:
|
|
|
|
nMaxLength - the max length of the buffer
|
|
|
|
Return Value:
|
|
|
|
TRUE if the string is truncated and FALSE otherwise
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOL bRet = FALSE;
|
|
if( lstrlen(_pszString) >= nMaxLength )
|
|
{
|
|
// truncate up to nMaxLength including the terminating character
|
|
_pszString[nMaxLength-1] = 0;
|
|
bRet = TRUE;
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
BOOL
|
|
TString::
|
|
bDeleteChar(
|
|
IN UINT nPos
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Deletes the character at the specified pos
|
|
|
|
Arguments:
|
|
|
|
nPos - which char to delete
|
|
|
|
Return Value:
|
|
|
|
TRUE if on success and FALSE otherwise
|
|
|
|
--*/
|
|
{
|
|
BOOL bRet = FALSE;
|
|
UINT u, uLen = lstrlen(_pszString);
|
|
ASSERT(nPos < uLen);
|
|
|
|
if( nPos < uLen )
|
|
{
|
|
// the new length
|
|
uLen--;
|
|
|
|
// shift all the characters
|
|
for( u=nPos; u<uLen; u++ )
|
|
{
|
|
_pszString[u] = _pszString[u+1];
|
|
}
|
|
|
|
// zero terminate
|
|
_pszString[uLen] = 0;
|
|
bRet = TRUE;
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
BOOL
|
|
TString::
|
|
bReplaceAll(
|
|
IN TCHAR chFrom,
|
|
IN TCHAR chTo
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
replaces all chFrom characters to chTo
|
|
|
|
Arguments:
|
|
|
|
chFrom - character that needs to be replaced
|
|
chTo - character to replace with
|
|
|
|
Return Value:
|
|
|
|
TRUE if on success and FALSE otherwise
|
|
|
|
--*/
|
|
{
|
|
UINT u, uLen = lstrlen(_pszString);
|
|
for( u=0; u<uLen; u++ )
|
|
{
|
|
if( chFrom == _pszString[u] )
|
|
{
|
|
// replace
|
|
_pszString[u] = chTo;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
VOID
|
|
TString::
|
|
vToUpper(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
converts the string to upper case
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
if( _pszString && _pszString[0] )
|
|
{
|
|
CharUpper(_pszString);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
TString::
|
|
vToLower(
|
|
VOID
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
converts the string to lower case
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
if( _pszString && _pszString[0] )
|
|
{
|
|
CharLower(_pszString);
|
|
}
|
|
}
|
|
|
|
BOOL
|
|
TString::
|
|
bUpdate(
|
|
IN LPCTSTR psz
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Safe updating of string. If the allocation fails, return FALSE
|
|
and leave the string as is.
|
|
|
|
Arguments:
|
|
|
|
psz - Input string, may be NULL.
|
|
|
|
Return Value:
|
|
|
|
TRUE = update successful
|
|
FALSE = update failed
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// Check if the null pointer is passed.
|
|
//
|
|
if( !psz ){
|
|
|
|
//
|
|
// If not pointing to the gszNullState
|
|
//
|
|
vFree( _pszString );
|
|
|
|
//
|
|
// Mark the object as valid.
|
|
//
|
|
_pszString = &TString::gszNullState[kValid];
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//
|
|
// Create temp pointer and allocate the new string.
|
|
//
|
|
LPTSTR pszTmp = _pszString;
|
|
int iLenCb = ( lstrlen(psz)+1 ) * sizeof( psz[0] );
|
|
_pszString = (LPTSTR) AllocMem( iLenCb );
|
|
|
|
//
|
|
// If memory was not available.
|
|
//
|
|
if( !_pszString ){
|
|
|
|
//
|
|
// Ensure we free any previous string.
|
|
//
|
|
vFree( pszTmp );
|
|
|
|
//
|
|
// Mark the string object as invalid.
|
|
//
|
|
_pszString = &TString::gszNullState[kInValid];
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Copy the string and
|
|
//
|
|
StringCbCopy( _pszString, iLenCb, psz );
|
|
|
|
//
|
|
// If the old string object was not pointing to our
|
|
// class specific gszNullStates then release the memory.
|
|
//
|
|
vFree( pszTmp );
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL
|
|
TString::
|
|
bLoadString(
|
|
IN HINSTANCE hInst,
|
|
IN UINT uID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Safe load of a string from a resources file.
|
|
|
|
Arguments:
|
|
|
|
hInst - Instance handle of resource file.
|
|
uId - Resource id to load.
|
|
|
|
Return Value:
|
|
|
|
TRUE = load successful
|
|
FALSE = load failed
|
|
|
|
--*/
|
|
|
|
{
|
|
LPTSTR pszString = NULL;
|
|
BOOL bStatus = FALSE;
|
|
INT iSize;
|
|
INT iLen;
|
|
|
|
//
|
|
// Continue increasing the buffer until
|
|
// the buffer is big enought to hold the entire string.
|
|
//
|
|
for( iSize = kStrMax; ; iSize += kStrMax ){
|
|
|
|
//
|
|
// Allocate string buffer.
|
|
//
|
|
pszString = (LPTSTR)AllocMem( iSize * sizeof( pszString[0] ) );
|
|
|
|
if( pszString ){
|
|
|
|
iLen = LoadString( hInst, uID, pszString, iSize );
|
|
|
|
if( iLen == 0 ) {
|
|
|
|
DBGMSG( DBG_ERROR, ( "String.vLoadString: failed to load IDS 0x%x, %d\n", uID, GetLastError() ));
|
|
FreeMem( pszString );
|
|
break;
|
|
|
|
//
|
|
// Since LoadString does not indicate if the string was truncated or it
|
|
// just happened to fit. When we detect this ambiguous case we will
|
|
// try one more time just to be sure.
|
|
//
|
|
} else if( iSize - iLen <= sizeof( pszString[0] ) ){
|
|
|
|
FreeMem( pszString );
|
|
|
|
//
|
|
// LoadString was successful release original string buffer
|
|
// and update new buffer pointer.
|
|
//
|
|
} else {
|
|
|
|
vFree( _pszString );
|
|
_pszString = pszString;
|
|
bStatus = TRUE;
|
|
break;
|
|
}
|
|
|
|
} else {
|
|
DBGMSG( DBG_ERROR, ( "String.vLoadString: unable to allocate memory, %d\n", GetLastError() ));
|
|
break;
|
|
}
|
|
}
|
|
return bStatus;
|
|
}
|
|
|
|
VOID
|
|
TString::
|
|
vFree(
|
|
IN LPTSTR pszString
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Safe free, frees the string memory. Ensures
|
|
we do not try an free our global memory block.
|
|
|
|
Arguments:
|
|
|
|
pszString pointer to string meory to free.
|
|
|
|
Return Value:
|
|
|
|
Nothing.
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// If this memory was not pointing to our
|
|
// class specific gszNullStates then release the memory.
|
|
//
|
|
if( pszString != &TString::gszNullState[kValid] &&
|
|
pszString != &TString::gszNullState[kInValid] ){
|
|
|
|
FreeMem( pszString );
|
|
}
|
|
}
|
|
|
|
|
|
BOOL
|
|
TString::
|
|
bFormat(
|
|
IN LPCTSTR pszFmt,
|
|
IN ...
|
|
)
|
|
{
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Format the string opbject similar to sprintf.
|
|
|
|
Arguments:
|
|
|
|
pszFmt pointer format string.
|
|
.. variable number of arguments similar to sprintf.
|
|
|
|
Return Value:
|
|
|
|
TRUE if string was format successfully. FALSE if error
|
|
occurred creating the format string, string object will be
|
|
invalid and the previous string lost.
|
|
|
|
--*/
|
|
|
|
BOOL bStatus = TRUE;
|
|
|
|
va_list pArgs;
|
|
|
|
va_start( pArgs, pszFmt );
|
|
|
|
bStatus = bvFormat( pszFmt, pArgs );
|
|
|
|
va_end( pArgs );
|
|
|
|
return bStatus;
|
|
|
|
}
|
|
|
|
BOOL
|
|
TString::
|
|
bvFormat(
|
|
IN LPCTSTR pszFmt,
|
|
IN va_list avlist
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Format the string opbject similar to vsprintf.
|
|
|
|
Arguments:
|
|
|
|
pszFmt pointer format string.
|
|
pointer to variable number of arguments similar to vsprintf.
|
|
|
|
Return Value:
|
|
|
|
TRUE if string was format successfully. FALSE if error
|
|
occurred creating the format string, string object will be
|
|
invalid and the previous string lost.
|
|
|
|
--*/
|
|
{
|
|
BOOL bStatus;
|
|
|
|
//
|
|
// Save previous string value.
|
|
//
|
|
LPTSTR pszTemp = _pszString;
|
|
|
|
//
|
|
// Format the string.
|
|
//
|
|
_pszString = vsntprintf( pszFmt, avlist );
|
|
|
|
//
|
|
// If format failed mark object as invalid and
|
|
// set the return value.
|
|
//
|
|
if( !_pszString )
|
|
{
|
|
_pszString = &TString::gszNullState[kInValid];
|
|
bStatus = FALSE;
|
|
}
|
|
else
|
|
{
|
|
bStatus = TRUE;
|
|
}
|
|
|
|
//
|
|
// Release near the end because the format string or arguments
|
|
// may be referencing this string object.
|
|
//
|
|
vFree( pszTemp );
|
|
|
|
return bStatus;
|
|
}
|
|
|
|
LPTSTR
|
|
TString::
|
|
vsntprintf(
|
|
IN LPCTSTR szFmt,
|
|
IN va_list pArgs
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
//
|
|
// Formats a string and returns a heap allocated string with the
|
|
// formated data. This routine can be used to for extremely
|
|
// long format strings. Note: If a valid pointer is returned
|
|
// the callng functions must release the data with a call to delete.
|
|
// Example:
|
|
//
|
|
// LPCTSTR p = vsntprintf("Test %s", pString );
|
|
//
|
|
// SetTitle( p );
|
|
//
|
|
// delete [] p;
|
|
//
|
|
|
|
Arguments:
|
|
|
|
pszString pointer format string.
|
|
pointer to a variable number of arguments.
|
|
|
|
Return Value:
|
|
|
|
Pointer to format string. NULL if error.
|
|
|
|
--*/
|
|
|
|
{
|
|
LPTSTR pszBuff = NULL;
|
|
INT iSize = kStrIncrement;
|
|
|
|
for( ; ; )
|
|
{
|
|
//
|
|
// Allocate the message buffer.
|
|
//
|
|
pszBuff = (LPTSTR)AllocMem( iSize * sizeof(TCHAR) );
|
|
|
|
if( !pszBuff )
|
|
{
|
|
break;
|
|
}
|
|
|
|
//
|
|
// StringCchVPrintf fails if the buffer is not big enough.
|
|
//
|
|
if( SUCCEEDED( StringCchVPrintf( pszBuff, iSize, szFmt, pArgs ) ) )
|
|
{
|
|
break;
|
|
}
|
|
|
|
//
|
|
// String did not fit release the current buffer.
|
|
//
|
|
if( pszBuff )
|
|
{
|
|
FreeMem( pszBuff );
|
|
}
|
|
|
|
//
|
|
// Double the buffer size after each failure.
|
|
//
|
|
iSize *= 2;
|
|
|
|
//
|
|
// If the size is greater than 100k exit without formatting a string.
|
|
//
|
|
if( iSize > kStrMaxFormatSize )
|
|
{
|
|
DBGMSG( DBG_ERROR, ("TString::vsntprintf failed string too long.\n") );
|
|
pszBuff = NULL;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
return pszBuff;
|
|
}
|
|
|
|
///////////////////////////////////////////////////
|
|
// @@file@@ state.cxx
|
|
///////////////////////////////////////////////////
|
|
|
|
/********************************************************************
|
|
|
|
Ref counting
|
|
|
|
********************************************************************/
|
|
|
|
/********************************************************************
|
|
|
|
MRefCom: Reference counting using interlocked references.
|
|
This avoids creating a common critical section.
|
|
|
|
vDeleteDecRef is the same as vDecRef, but it logs differently.
|
|
|
|
********************************************************************/
|
|
|
|
VOID
|
|
MRefCom::
|
|
vIncRef(
|
|
VOID
|
|
)
|
|
{
|
|
InterlockedIncrement( &_cRef );
|
|
}
|
|
|
|
LONG
|
|
MRefCom::
|
|
cDecRef(
|
|
VOID
|
|
)
|
|
{
|
|
LONG cRefReturn = InterlockedDecrement( &_cRef );
|
|
|
|
if( !cRefReturn ){
|
|
vRefZeroed();
|
|
}
|
|
return cRefReturn;
|
|
}
|
|
|
|
|
|
VOID
|
|
MRefCom::
|
|
vDecRefDelete(
|
|
VOID
|
|
)
|
|
{
|
|
if( !InterlockedDecrement( &_cRef )){
|
|
vRefZeroed();
|
|
}
|
|
}
|
|
|
|
VOID
|
|
MRefCom::
|
|
vRefZeroed(
|
|
VOID
|
|
)
|
|
{
|
|
}
|
|
|
|
/********************************************************************
|
|
|
|
State
|
|
|
|
********************************************************************/
|
|
|
|
#if DBG
|
|
TState::
|
|
TState(
|
|
VOID
|
|
) : _StateVar(0)
|
|
{
|
|
}
|
|
|
|
TState::
|
|
TState(
|
|
STATEVAR StateVar
|
|
) : _StateVar(StateVar)
|
|
{
|
|
}
|
|
|
|
TState::
|
|
~TState(
|
|
VOID
|
|
)
|
|
{
|
|
}
|
|
|
|
STATEVAR
|
|
TState::
|
|
operator|=(
|
|
STATEVAR StateVarOn
|
|
)
|
|
{
|
|
SPLASSERT( bValidateSet( StateVarOn ));
|
|
|
|
_StateVar |= StateVarOn;
|
|
return _StateVar;
|
|
}
|
|
|
|
STATEVAR
|
|
TState::
|
|
operator&=(
|
|
STATEVAR StateVarMask
|
|
)
|
|
{
|
|
SPLASSERT( bValidateMask( StateVarMask ));
|
|
|
|
_StateVar &= StateVarMask;
|
|
return _StateVar;
|
|
}
|
|
#endif
|
|
|
|
///////////////////////////////////////////////////
|
|
// @@file@@ splutil.cxx
|
|
///////////////////////////////////////////////////
|
|
|
|
MEntry*
|
|
MEntry::
|
|
pFindEntry(
|
|
PDLINK pdlink,
|
|
LPCTSTR pszName
|
|
)
|
|
{
|
|
PDLINK pdlinkT;
|
|
MEntry* pEntry;
|
|
|
|
for( pdlinkT = pdlink->FLink;
|
|
pdlinkT != pdlink;
|
|
pdlinkT = pdlinkT->FLink ){
|
|
|
|
pEntry = MEntry::Entry_pConvert( pdlinkT );
|
|
if( pEntry->_strName == pszName ){
|
|
|
|
return pEntry;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
///////////////////////////////////////////////////
|
|
// @@file@@ threadm.cxx
|
|
///////////////////////////////////////////////////
|
|
|
|
/********************************************************************
|
|
|
|
Public interfaces.
|
|
|
|
********************************************************************/
|
|
|
|
TThreadM::
|
|
TThreadM(
|
|
UINT uMaxThreads,
|
|
UINT uIdleLife,
|
|
CCSLock* pCritSec OPTIONAL
|
|
) :
|
|
|
|
_uMaxThreads(uMaxThreads), _uIdleLife(uIdleLife), _uActiveThreads(0),
|
|
_uRunNowThreads(0), _iIdleThreads(0),
|
|
|
|
_lLocks(1) // the one who calls 'new TThreadM' acquires the initial lock
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Construct a Thread Manager object.
|
|
|
|
Arguments:
|
|
|
|
uMaxThreads - Upper limit of threads that will be created.
|
|
|
|
uIdleLife - Maximum life once a thread goes idle (in ms).
|
|
|
|
pCritSec - Use this crit sec for synchronization (if not specified,
|
|
a private one will be created).
|
|
|
|
Return Value:
|
|
|
|
Notes:
|
|
|
|
_hTrigger is our validity variable. When this value is NULL,
|
|
instantiation failed. If it's non-NULL, the entire object is valid.
|
|
|
|
--*/
|
|
|
|
{
|
|
_hTrigger = CreateEvent( NULL,
|
|
FALSE,
|
|
FALSE,
|
|
NULL );
|
|
|
|
if( !_hTrigger ){
|
|
return;
|
|
}
|
|
|
|
//
|
|
// If no critical section, create our own.
|
|
//
|
|
if (!pCritSec) {
|
|
|
|
_pCritSec = new CCSLock();
|
|
|
|
if( !_pCritSec ){
|
|
|
|
//
|
|
// _hTrigger is our valid variable. If we fail to create
|
|
// the critical section, prepare to return failure.
|
|
//
|
|
CloseHandle( _hTrigger );
|
|
_hTrigger = NULL;
|
|
|
|
return;
|
|
}
|
|
_State |= kPrivateCritSec;
|
|
|
|
} else {
|
|
_pCritSec = pCritSec;
|
|
}
|
|
}
|
|
|
|
VOID
|
|
TThreadM::
|
|
vDelete(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Indicates that the object is pending deletion. Any object that
|
|
inherits from vDelete should _not_ call the destructor directly,
|
|
since there may be pending jobs. Instead, they should call
|
|
TThreadM::vDelete().
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
|
|
{
|
|
CCSLock::Locker CSL( *_pCritSec );
|
|
|
|
//
|
|
// Mark as wanting to be destroyed.
|
|
//
|
|
_State |= kDestroyReq;
|
|
}
|
|
|
|
BOOL
|
|
TThreadM::
|
|
bJobAdded(
|
|
BOOL bRunNow
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Notify the thread manager that a new job has been added. This job
|
|
will be processed fifo.
|
|
|
|
Arguments:
|
|
|
|
bRunNow - Ignore the thread limits and run the job now.
|
|
|
|
Return Value:
|
|
|
|
TRUE - Job successfully added.
|
|
FALSE - Job could not be added.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD dwThreadId;
|
|
HANDLE hThread;
|
|
BOOL rc = TRUE;
|
|
|
|
CCSLock::Locker CSL( *_pCritSec );
|
|
|
|
if( _State.bBit( kDestroyReq )){
|
|
|
|
DBGMSG( DBG_THREADM | DBG_ERROR,
|
|
( "ThreadM.bJobAdded: add failed since DESTROY requested.\n" ));
|
|
|
|
SetLastError( ERROR_INVALID_PARAMETER );
|
|
rc = FALSE;
|
|
|
|
} else {
|
|
|
|
//
|
|
// We either: give it to an idle thread, create a new thread,
|
|
// or leave it in the queue.
|
|
//
|
|
if( _iIdleThreads > 0 ){
|
|
|
|
//
|
|
// There are some idle threads--trigger the event and it
|
|
// will be picked up.
|
|
//
|
|
--_iIdleThreads;
|
|
|
|
DBGMSG( DBG_THREADM,
|
|
( "ThreadM.bJobAdded: Trigger: --iIdle %d, uActive %d\n",
|
|
_iIdleThreads, _uActiveThreads ));
|
|
|
|
//
|
|
// If we set the event, then the worker thread that receives
|
|
// this event should _not_ decrement _iIdleThreads, since
|
|
// we already did this.
|
|
//
|
|
SetEvent( _hTrigger );
|
|
|
|
} else if( _uActiveThreads < _uMaxThreads || bRunNow ){
|
|
|
|
//
|
|
// No idle threads, but we can create a new one since we
|
|
// haven't reached the limit, or the bRunNow flag is set.
|
|
//
|
|
|
|
Lock();
|
|
|
|
hThread = TSafeThread::Create( NULL,
|
|
0,
|
|
xdwThreadProc,
|
|
this,
|
|
0,
|
|
&dwThreadId );
|
|
if( hThread ){
|
|
|
|
CloseHandle( hThread );
|
|
|
|
//
|
|
// We have successfully created a thread; up the
|
|
// count.
|
|
//
|
|
++_uActiveThreads;
|
|
|
|
//
|
|
// We have less active threads than the max; create a new one.
|
|
//
|
|
DBGMSG( DBG_THREADM,
|
|
( "ThreadM.bJobAdded: ct: iIdle %d, ++uActive %d\n",
|
|
_iIdleThreads,
|
|
_uActiveThreads ));
|
|
|
|
} else {
|
|
|
|
Unlock();
|
|
|
|
rc = FALSE;
|
|
|
|
DBGMSG( DBG_THREADM | DBG_WARN,
|
|
( "ThreadM.bJobAdded: unable to ct %d\n",
|
|
GetLastError( )));
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// No idle threads, and we are already at the max so we
|
|
// can't create new threads. Dec iIdleThreads anyway
|
|
// (may go negative, but that's ok).
|
|
//
|
|
// iIdleThreads represents the number of threads that
|
|
// are currently not processing jobs. If the number is
|
|
// negative, this indicates that even if a thread suddenly
|
|
// completes a job and would go idle, there is a queued
|
|
// job that would immediately grab it, so the thread really
|
|
// didn't go into an idle state.
|
|
//
|
|
// The negative number indicates the number of outstanding
|
|
// jobs that are queued (e.g., -5 indicate 5 jobs queued).
|
|
//
|
|
// There is always an increment of iIdleThreads when a
|
|
// job is compeleted.
|
|
//
|
|
--_iIdleThreads;
|
|
|
|
//
|
|
// No threads idle, and at max threads.
|
|
//
|
|
DBGMSG( DBG_THREADM,
|
|
( "ThreadM.bJobAdded: wait: --iIdle %d, uActive %d\n",
|
|
_iIdleThreads,
|
|
_uActiveThreads ));
|
|
}
|
|
|
|
//
|
|
// If we succeeded and bRunNow is set, this indicates that
|
|
// we were able to start a special thread, so we need to adjust
|
|
// the maximum number of threads. When a this special thread
|
|
// job completes, we will decrement it.
|
|
//
|
|
if( bRunNow && rc ){
|
|
|
|
++_uMaxThreads;
|
|
++_uRunNowThreads;
|
|
}
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
/********************************************************************
|
|
|
|
Private routines.
|
|
|
|
********************************************************************/
|
|
|
|
TThreadM::
|
|
~TThreadM(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Destroy the thread manager object. This is private; to request
|
|
that the thread manager quit, call vDelete().
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
|
|
{
|
|
SPLASSERT( _State.bBit( kDestroyReq ));
|
|
|
|
if( _State.bBit( kPrivateCritSec )){
|
|
SPLASSERT( _pCritSec->bOutside( ));
|
|
delete _pCritSec;
|
|
}
|
|
|
|
if( _hTrigger )
|
|
CloseHandle( _hTrigger );
|
|
}
|
|
|
|
DWORD
|
|
TThreadM::
|
|
xdwThreadProc(
|
|
LPVOID pVoid
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Worker thread routine that calls the client to process the jobs.
|
|
|
|
Arguments:
|
|
|
|
pVoid - pTMStateVar
|
|
|
|
Return Value:
|
|
|
|
Ignored.
|
|
|
|
--*/
|
|
|
|
{
|
|
TThreadM* pThreadM = (TThreadM*)pVoid;
|
|
DWORD dwExitCode = pThreadM->dwThreadProc();
|
|
pThreadM->Unlock(); // release
|
|
return dwExitCode;
|
|
}
|
|
|
|
DWORD
|
|
TThreadM::
|
|
dwThreadProc(
|
|
VOID
|
|
)
|
|
{
|
|
CCSLock::Locker CSL( *_pCritSec );
|
|
|
|
DBGMSG( DBG_THREADM,
|
|
( "ThreadM.dwThreadProc: ct: iIdle %d, uActive %d\n",
|
|
_iIdleThreads,
|
|
_uActiveThreads));
|
|
|
|
PJOB pJob = pThreadMJobNext();
|
|
|
|
while( TRUE ){
|
|
|
|
for( ; pJob; pJob=pThreadMJobNext( )){
|
|
|
|
//
|
|
// If bRunNow count is non-zero, this indicates that we just
|
|
// picked up a RunNow job. As soon as it completes, we
|
|
// can decrement the count.
|
|
//
|
|
BOOL bRunNowCompleted = _uRunNowThreads > 0;
|
|
|
|
{
|
|
_pCritSec->Unlock();
|
|
|
|
//
|
|
// Call back to client to process the job.
|
|
//
|
|
DBGMSG( DBG_THREADM,
|
|
( "ThreadM.dwThreadProc: %x processing\n",
|
|
(ULONG_PTR)pJob ));
|
|
|
|
//
|
|
// Call through virtual function to process the
|
|
// user's job.
|
|
//
|
|
vThreadMJobProcess( pJob );
|
|
|
|
DBGMSG( DBG_THREADM,
|
|
( "ThreadM.dwThreadProc: %x processing done\n",
|
|
(ULONG_PTR)pJob ));
|
|
|
|
// aquire the CS again
|
|
_pCritSec->Lock();
|
|
}
|
|
|
|
|
|
//
|
|
// If a RunNow job has been completed, then decrement both
|
|
// counts. uMaxThreads was increased by one when the job was
|
|
// accepted, so now it must be lowered.
|
|
//
|
|
if( bRunNowCompleted ){
|
|
|
|
--_uMaxThreads;
|
|
--_uRunNowThreads;
|
|
}
|
|
|
|
++_iIdleThreads;
|
|
|
|
DBGMSG( DBG_THREADM,
|
|
( "ThreadM.dwThreadProc: ++iIdle %d, uActive %d\n",
|
|
_iIdleThreads,
|
|
_uActiveThreads ));
|
|
}
|
|
|
|
DBGMSG( DBG_THREADM,
|
|
( "ThreadM.dwThreadProc: Sleep: iIdle %d, uActive %d\n",
|
|
_iIdleThreads,
|
|
_uActiveThreads ));
|
|
|
|
{
|
|
_pCritSec->Unlock();
|
|
|
|
//
|
|
// Done, now relax and go idle for a bit. We don't
|
|
// care whether we timeout or get triggered; in either
|
|
// case we check for another job.
|
|
//
|
|
WaitForSingleObject( _hTrigger, _uIdleLife );
|
|
|
|
// aquire the CS again
|
|
_pCritSec->Lock();
|
|
}
|
|
|
|
|
|
//
|
|
// We must check here instead of relying on the return value
|
|
// of WaitForSingleObject since someone may see iIdleThreads!=0
|
|
// and set the trigger, but we timeout before it gets set.
|
|
//
|
|
pJob = pThreadMJobNext();
|
|
|
|
if( pJob ){
|
|
|
|
DBGMSG( DBG_THREADM,
|
|
( "ThreadM.dwThreadProc: Woke and found job: iIdle %d, uActive %d\n",
|
|
_iIdleThreads,
|
|
_uActiveThreads ));
|
|
} else {
|
|
|
|
//
|
|
// No jobs found; break. Be sure to reset the hTrigger, since
|
|
// there are no waiting jobs, and the main thread might
|
|
// have set it in the following case:
|
|
//
|
|
// MainThread: WorkerThread:
|
|
// Sleeping
|
|
// Awoke, not yet in CS.
|
|
// GotJob
|
|
// SetEvent
|
|
// --iIdleThreads
|
|
// Enter CS, found job, process it.
|
|
//
|
|
// In this case, the event is set, but there is no thread
|
|
// to pick it up.
|
|
//
|
|
ResetEvent( _hTrigger );
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Decrement ActiveThreads. This was incremented when the thread
|
|
// was successfully created, and should be decremented when the thread
|
|
// is about to exit.
|
|
//
|
|
--_uActiveThreads;
|
|
|
|
//
|
|
// The thread enters an idle state right before it goes to sleep.
|
|
//
|
|
// When a job is added, the idle count is decremented by the main
|
|
// thread, so the worker thread doesn't decrement it (avoids sync
|
|
// problems). If the worker thread timed out and there were no jobs,
|
|
// then we need to decrement the matching initial increment here.
|
|
//
|
|
--_iIdleThreads;
|
|
|
|
DBGMSG( DBG_THREADM,
|
|
( "ThreadM.dwThreadProc: dt: --iIdle %d, --uActive %d\n",
|
|
_iIdleThreads,
|
|
_uActiveThreads));
|
|
|
|
return 0;
|
|
}
|
|
|
|
///////////////////////////////////////////////////
|
|
// @@file@@ exec.cxx
|
|
///////////////////////////////////////////////////
|
|
|
|
/*++
|
|
|
|
Copyright (c) 1994 Microsoft Corporation
|
|
All rights reserved.
|
|
|
|
Module Name:
|
|
|
|
exec.cxx
|
|
|
|
Abstract:
|
|
|
|
Handles async commands.
|
|
|
|
The client give TExec a worker object (MExecWork). At some later
|
|
time, the worker is given a thread (called via svExecute).
|
|
|
|
If the Job is already on the list but is waiting, we just return.
|
|
If it's not on the list but currently executing, we add it to the
|
|
list when it's done executing.
|
|
|
|
kExecActive -> currently executing (so it's not on the list)
|
|
kExecActiveReq -> kExecActive is on, and it needs to run again.
|
|
|
|
If kExecExit is added, we call vExecExitComplete() (virtual function)
|
|
when all jobs have been completed and assume the the client is cleaning
|
|
up after itself. In fact, the MExecWork object may be destroyed
|
|
in vExecExitComplete().
|
|
|
|
Author:
|
|
|
|
Albert Ting (AlbertT) 8-Nov-1994
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
BOOL
|
|
TExec::
|
|
bJobAdd(
|
|
MExecWork* pExecWork,
|
|
STATEVAR StateVar
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Adds a job request to move to a given state.
|
|
|
|
Arguments:
|
|
|
|
pExecWork - Work structure.
|
|
|
|
StateVar - StateVar that we want to move toward. If kExecExit
|
|
is set, then we will complete the currently executing job
|
|
then exit by calling vExecExitComplete().
|
|
|
|
Note that kExecExit should be added by itself; if it is added
|
|
with other commands, they will be ignored.
|
|
|
|
Return Value:
|
|
|
|
TRUE = Job added (or job already on wait list)
|
|
FALSE = failed, GLE.
|
|
|
|
Adding kExecExit is guarenteed to succeed here.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOL bReturn = TRUE;
|
|
BOOL bCallExecExitComplete = FALSE;
|
|
|
|
{
|
|
CCSLock::Locker CSL( *_pCritSec );
|
|
|
|
DBGMSG( DBG_EXEC,
|
|
( "Exec.bJobAdd: %x StateVar = %x\n", pExecWork, StateVar ));
|
|
|
|
//
|
|
// Job bits must not have PRIVATE bits.
|
|
//
|
|
SPLASSERT( !( StateVar & kExecPrivate ));
|
|
|
|
//
|
|
// Don't allow adding a job after we are set to exit.
|
|
//
|
|
SPLASSERT( !( pExecWork->_State & kExecExit ));
|
|
|
|
//
|
|
// If it's active (currently executing in a thread), then it is
|
|
// not on the wait list and we mark it as ACTIVE_REQ so that
|
|
// when the job completes, it sees that more jobs have accumulated.
|
|
//
|
|
if( pExecWork->_State & kExecActive ){
|
|
|
|
DBGMSG( DBG_EXEC,
|
|
( "\n ACTIVE, ++REQ _State %x _StatePending %x\n",
|
|
(STATEVAR)pExecWork->_State,
|
|
(STATEVAR)pExecWork->_StatePending ));
|
|
|
|
//
|
|
// Can't be an immediate request if executing already.
|
|
//
|
|
SPLASSERT( !( StateVar & kExecRunNow ));
|
|
|
|
pExecWork->_StatePending |= StateVar;
|
|
pExecWork->_State |= kExecActiveReq;
|
|
|
|
bReturn = TRUE;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Store up the work requested since we aren't currently active.
|
|
//
|
|
pExecWork->_State |= StateVar;
|
|
|
|
//
|
|
// If we are not on the wait list, add it.
|
|
//
|
|
if( !pExecWork->Work_bLinked( )){
|
|
|
|
if( StateVar & kExecExit ){
|
|
|
|
bCallExecExitComplete = TRUE;
|
|
|
|
} else {
|
|
|
|
DBGMSG( DBG_EXEC, ( "not linked, added\n" ));
|
|
SPLASSERT( NULL == Work_pFind( pExecWork ));
|
|
|
|
bReturn = bJobAddWorker( pExecWork );
|
|
}
|
|
|
|
} else {
|
|
|
|
DBGMSG( DBG_EXEC, ( "linked, NOP\n" ));
|
|
}
|
|
}
|
|
}
|
|
|
|
if( bCallExecExitComplete ){
|
|
|
|
//
|
|
// Special case exit: we should exit. Once we call
|
|
// vExecExitComplete, we can't refer to *this anymore,
|
|
// since we may have deleted ourself.
|
|
//
|
|
pExecWork->vExecExitComplete();
|
|
bReturn = TRUE;
|
|
|
|
}
|
|
|
|
return bReturn;
|
|
}
|
|
|
|
|
|
VOID
|
|
TExec::
|
|
vJobDone(
|
|
MExecWork* pExecWork,
|
|
STATEVAR StateVar
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
A job has compeleted execution, clean up.
|
|
|
|
Arguments:
|
|
|
|
pExecWork - Unit that just completed executing.
|
|
|
|
StateVar - New state that the object is in (requests that
|
|
successfully completed should be turned off--done by client).
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOL bCallExecExitComplete = FALSE;
|
|
BOOL bCallExecFailedAddJob = FALSE;
|
|
|
|
{
|
|
CCSLock::Locker CSL( *_pCritSec );
|
|
|
|
DBGMSG( DBG_EXEC,
|
|
( "Exec.vJobDone: %x completed -> %x +(new) %x = %x\n",
|
|
pExecWork, StateVar, (DWORD)pExecWork->_StatePending,
|
|
(DWORD)pExecWork->_State | pExecWork->_StatePending ));
|
|
|
|
//
|
|
// kExecRunNow can not be set when the object is working.
|
|
//
|
|
SPLASSERT( !( StateVar & kExecRunNow ));
|
|
|
|
//
|
|
// We have completed the work request, put in the new state.
|
|
// Keep the private bits, and add in the new state variable,
|
|
// plus any additional work that was pending.
|
|
//
|
|
// The ExecNow bit is not saved (it's not part of kExecPrivate)
|
|
// since it's good for one shot only.
|
|
//
|
|
pExecWork->_State = ( pExecWork->_State & kExecPrivate ) |
|
|
( StateVar & ~kExecPrivate ) |
|
|
pExecWork->_StatePending;
|
|
|
|
pExecWork->_State &= ~kExecActive;
|
|
|
|
//
|
|
// If job is done, then quit.
|
|
//
|
|
if( pExecWork->_State & kExecExit ){
|
|
|
|
DBGMSG( DBG_EXEC,
|
|
( "Exec.vJobDone: _State %x, calling vExecExitComplete\n",
|
|
(STATEVAR)pExecWork->_State ));
|
|
|
|
bCallExecExitComplete = TRUE;
|
|
|
|
} else {
|
|
|
|
//
|
|
// If we have more work to do, add ourselves back
|
|
// to the queue.
|
|
//
|
|
if( pExecWork->_State & kExecActiveReq &&
|
|
!bJobAddWorker( pExecWork )){
|
|
|
|
bCallExecFailedAddJob = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( bCallExecFailedAddJob ){
|
|
|
|
//
|
|
// Fail on delayed job add.
|
|
//
|
|
pExecWork->vExecFailedAddJob();
|
|
}
|
|
|
|
if( bCallExecExitComplete ){
|
|
|
|
//
|
|
// Once vExecExitComplete has been called, the current object
|
|
// pExecWork may be destroyed.
|
|
//
|
|
// Don't refer to it again since vExecExitComplete may delete
|
|
// this as part of cleanup.
|
|
//
|
|
pExecWork->vExecExitComplete();
|
|
}
|
|
}
|
|
|
|
|
|
STATEVAR
|
|
TExec::
|
|
svClearPendingWork(
|
|
MExecWork* pExecWork
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Queries what work is currently pending.
|
|
|
|
Arguments:
|
|
|
|
pExecWork -- Work item.
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
|
|
{
|
|
CCSLock::Locker CSL( *_pCritSec );
|
|
|
|
//
|
|
// Return pending work, minus the private and kExecRunNow
|
|
// bits.
|
|
//
|
|
STATEVAR svPendingWork = pExecWork->_StatePending & ~kExecNoOutput;
|
|
pExecWork->_StatePending = 0;
|
|
|
|
return svPendingWork;
|
|
}
|
|
|
|
/********************************************************************
|
|
|
|
Private
|
|
|
|
********************************************************************/
|
|
|
|
TExec::
|
|
TExec(
|
|
CCSLock* pCritSec
|
|
) : TThreadM( 10, 2000, pCritSec ), _pCritSec( pCritSec )
|
|
{
|
|
}
|
|
|
|
BOOL
|
|
TExec::
|
|
bJobAddWorker(
|
|
MExecWork* pExecWork
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Common code to add a job to our linked list.
|
|
|
|
Must be called inside the _pCritSec. It does leave it inside
|
|
this function, however.
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
|
|
{
|
|
SPLASSERT( _pCritSec->bInside( ));
|
|
|
|
BOOL bRunNow = FALSE;
|
|
|
|
//
|
|
// Check if the client wants the job to run right now.
|
|
//
|
|
if( pExecWork->_State & kExecRunNow ){
|
|
|
|
//
|
|
// Add the job to the head of the queue. Since we always pull
|
|
// jobs from the beginning, we'll get to this job first.
|
|
//
|
|
// If a non-RunNow job is added to the list before we execute,
|
|
// we'll still run, since the other job will be added to the
|
|
// end of the list.
|
|
//
|
|
// If another RunNow job is added, we'll spawn separate threads
|
|
// for each (unless an idle thread is available).
|
|
//
|
|
|
|
Work_vAdd( pExecWork );
|
|
bRunNow = TRUE;
|
|
|
|
} else {
|
|
Work_vAppend( pExecWork );
|
|
}
|
|
|
|
if( !bJobAdded( bRunNow ) ){
|
|
|
|
DBGMSG( DBG_INFO, ( "Exec.vJobProcess: unable to add job %x: %d\n",
|
|
pExecWork,
|
|
GetLastError( )));
|
|
|
|
Work_vDelink( pExecWork );
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
PJOB
|
|
TExec::
|
|
pThreadMJobNext(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Gets the next job from the queue. This function is defined in
|
|
TThreadM.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
|
|
{
|
|
CCSLock::Locker CSL( *_pCritSec );
|
|
|
|
MExecWork* pExecWork = Work_pHead();
|
|
|
|
if( !pExecWork ){
|
|
return NULL;
|
|
}
|
|
|
|
Work_vDelink( pExecWork );
|
|
|
|
//
|
|
// Job should never be active here.
|
|
//
|
|
SPLASSERT( !(pExecWork->_State & kExecActive) );
|
|
|
|
//
|
|
// We will handle all requests now, so clear kExecActiveReq.
|
|
// Also remove kExecRunNow, since it's one shot only, and mark us
|
|
// as currently active (kExecActive).
|
|
//
|
|
pExecWork->_State &= ~( kExecActiveReq | kExecRunNow );
|
|
pExecWork->_State |= kExecActive;
|
|
|
|
return (PJOB)pExecWork;
|
|
}
|
|
|
|
VOID
|
|
TExec::
|
|
vThreadMJobProcess(
|
|
PJOB pJob
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Process a job in the current thread. We call the virtual function
|
|
with the job object, then clear out the bits that it has completed.
|
|
(This is a member of TThreadM.)
|
|
|
|
If there is additional pending work (ACTIVE_REQ), then we re-add
|
|
the job.
|
|
|
|
If there is a failure in the re-add case, we must send an
|
|
asynchronous fail message.
|
|
|
|
Arguments:
|
|
|
|
pJob - MExecWork instance.
|
|
|
|
Return Value:
|
|
|
|
--*/
|
|
|
|
{
|
|
SPLASSERT( _pCritSec->bOutside( ));
|
|
|
|
STATEVAR StateVar;
|
|
MExecWork* pExecWork = (MExecWork*)pJob;
|
|
|
|
//
|
|
// Do the work.
|
|
//
|
|
StateVar = pExecWork->svExecute( pExecWork->State() & ~kExecNoOutput );
|
|
|
|
vJobDone( pExecWork, StateVar );
|
|
}
|
|
|
|
///////////////////////////////////////////////////
|
|
// @@file@@ bitarray.cxx
|
|
///////////////////////////////////////////////////
|
|
|
|
/********************************************************************
|
|
|
|
Bit Array class
|
|
|
|
********************************************************************/
|
|
|
|
TBitArray::
|
|
TBitArray(
|
|
IN UINT nBits,
|
|
IN UINT uGrowSize
|
|
) : _nBits( nBits ),
|
|
_pBits( NULL ),
|
|
_uGrowSize( uGrowSize )
|
|
{
|
|
DBGMSG( DBG_TRACE, ( "TBitArray::ctor\n" ) );
|
|
|
|
//
|
|
// If the initial number of bits is not specified then
|
|
// create a default bit array size.
|
|
//
|
|
if( !_nBits )
|
|
{
|
|
_nBits = kBitsInType;
|
|
}
|
|
|
|
//
|
|
// This could fail, thus leaving the bit array
|
|
// in an invalid state, Note _pBits being true is
|
|
// the bValid check.
|
|
//
|
|
_pBits = new Type [ nBitsToType( _nBits ) ];
|
|
|
|
if( _pBits )
|
|
{
|
|
//
|
|
// The grow size should be at least the
|
|
// number of bits in the type.
|
|
//
|
|
if( _uGrowSize < kBitsInType )
|
|
{
|
|
_uGrowSize = kBitsInType;
|
|
}
|
|
|
|
//
|
|
// Clear all the bits.
|
|
//
|
|
memset( _pBits, 0, nBitsToType( _nBits ) * sizeof( Type ) );
|
|
}
|
|
}
|
|
|
|
TBitArray::
|
|
TBitArray(
|
|
const TBitArray &rhs
|
|
) : _nBits( kBitsInType ),
|
|
_pBits( NULL )
|
|
{
|
|
DBGMSG( DBG_TRACE, ( "TBitArray::copy_ctor\n" ) );
|
|
bClone( rhs );
|
|
}
|
|
|
|
const TBitArray &
|
|
TBitArray::
|
|
operator =(
|
|
const TBitArray &rhs
|
|
)
|
|
{
|
|
DBGMSG( DBG_TRACE, ( "TBitArray::operator =\n" ) );
|
|
bClone( rhs );
|
|
return *this;
|
|
}
|
|
|
|
TBitArray::
|
|
~TBitArray(
|
|
VOID
|
|
)
|
|
{
|
|
DBGMSG( DBG_TRACE, ( "TBitArray::dtor\n" ) );
|
|
delete [] _pBits;
|
|
}
|
|
|
|
BOOL
|
|
TBitArray::
|
|
bValid(
|
|
VOID
|
|
) const
|
|
{
|
|
return _pBits != NULL;
|
|
}
|
|
|
|
BOOL
|
|
TBitArray::
|
|
bToString(
|
|
IN TString &strBits
|
|
) const
|
|
{
|
|
BOOL bStatus = bValid();
|
|
|
|
if( bStatus )
|
|
{
|
|
TString strString;
|
|
|
|
strBits.bUpdate( NULL );
|
|
|
|
//
|
|
// Get the upper bound bit.
|
|
//
|
|
UINT uIndex = _nBits - 1;
|
|
|
|
//
|
|
// Print the array in reverse order to make the bit array
|
|
// appear as one large binary number.
|
|
//
|
|
for( UINT i = 0; i < _nBits; i++, uIndex-- )
|
|
{
|
|
strString.bFormat( TEXT( "%d" ), bRead( uIndex ) );
|
|
strBits.bCat( strString );
|
|
}
|
|
|
|
bStatus = strBits.bValid();
|
|
}
|
|
|
|
return bStatus;
|
|
}
|
|
|
|
BOOL
|
|
TBitArray::
|
|
bRead(
|
|
IN UINT Bit
|
|
) const
|
|
{
|
|
BOOL bStatus = bIsValidBit( Bit );
|
|
|
|
if( bStatus )
|
|
{
|
|
bStatus = _pBits[BitToIndex( Bit )] & BitToMask( Bit ) ? TRUE : FALSE;
|
|
}
|
|
|
|
return bStatus;
|
|
}
|
|
|
|
BOOL
|
|
TBitArray::
|
|
bSet(
|
|
IN UINT Bit
|
|
)
|
|
{
|
|
BOOL bStatus = bIsValidBit( Bit );
|
|
|
|
if( bStatus )
|
|
{
|
|
_pBits[BitToIndex( Bit )] |= BitToMask( Bit );
|
|
}
|
|
|
|
return bStatus;
|
|
}
|
|
|
|
BOOL
|
|
TBitArray::
|
|
bReset(
|
|
IN UINT Bit
|
|
)
|
|
{
|
|
BOOL bStatus = bIsValidBit( Bit );
|
|
|
|
if( bStatus )
|
|
{
|
|
_pBits[BitToIndex( Bit )] &= ~BitToMask( Bit );
|
|
}
|
|
|
|
return bStatus;
|
|
}
|
|
|
|
BOOL
|
|
TBitArray::
|
|
bToggle(
|
|
IN UINT Bit
|
|
)
|
|
{
|
|
BOOL bStatus = bIsValidBit( Bit );
|
|
|
|
if( bStatus )
|
|
{
|
|
_pBits[BitToIndex( Bit )] ^= BitToMask( Bit );
|
|
}
|
|
|
|
return bStatus;
|
|
}
|
|
|
|
//
|
|
// Add one new bit to the end of the bit array.
|
|
// If multiple bits need to be added the user of the
|
|
// class should call this routine repeatedly.
|
|
//
|
|
BOOL
|
|
TBitArray::
|
|
bAdd(
|
|
VOID
|
|
)
|
|
{
|
|
BOOL bStatus = FALSE;
|
|
UINT Bit = _nBits + 1;
|
|
|
|
//
|
|
// Check if there is room in the array for one more bit.
|
|
//
|
|
if( Bit <= nBitsToType( _nBits ) * kBitsInType )
|
|
{
|
|
//
|
|
// Update the current bit count and return true.
|
|
//
|
|
_nBits = Bit;
|
|
bStatus = TRUE;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Grow the bit array.
|
|
//
|
|
bStatus = bGrow( Bit );
|
|
}
|
|
|
|
return bStatus;
|
|
}
|
|
|
|
VOID
|
|
TBitArray::
|
|
vSetAll(
|
|
VOID
|
|
)
|
|
{
|
|
for( UINT i = 0; i < _nBits; i++ )
|
|
{
|
|
bSet( i );
|
|
}
|
|
}
|
|
|
|
VOID
|
|
TBitArray::
|
|
vResetAll(
|
|
VOID
|
|
)
|
|
{
|
|
for( UINT i = 0; i < _nBits; i++ )
|
|
{
|
|
bReset( i );
|
|
}
|
|
}
|
|
|
|
UINT
|
|
TBitArray::
|
|
uNumBits(
|
|
VOID
|
|
) const
|
|
{
|
|
return _nBits;
|
|
}
|
|
|
|
|
|
BOOL
|
|
TBitArray::
|
|
bFindNextResetBit(
|
|
IN UINT *puNextFreeBit
|
|
)
|
|
{
|
|
BOOL bStatus = bValid();
|
|
|
|
if( bStatus )
|
|
{
|
|
BOOL bFound = FALSE;
|
|
|
|
//
|
|
// Locate the first type that contains at least one cleared bit.
|
|
//
|
|
for( UINT i = 0; i < nBitsToType( _nBits ); i++ )
|
|
{
|
|
if( _pBits[i] != kBitsInTypeMask )
|
|
{
|
|
//
|
|
// Search for the bit that is cleared.
|
|
//
|
|
for( UINT j = 0; j < kBitsInType; j++ )
|
|
{
|
|
if( !( _pBits[i] & BitToMask( j ) ) )
|
|
{
|
|
*puNextFreeBit = i * kBitsInType + j;
|
|
bFound = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Free bit found terminate the search.
|
|
//
|
|
if( bFound )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Free bit was not found then grow the bit array
|
|
//
|
|
if( !bFound )
|
|
{
|
|
//
|
|
// Assume a new bit will be added.
|
|
//
|
|
*puNextFreeBit = uNumBits();
|
|
|
|
//
|
|
// Add a new bit.
|
|
//
|
|
bStatus = bAdd();
|
|
}
|
|
}
|
|
return bStatus;
|
|
}
|
|
|
|
|
|
/********************************************************************
|
|
|
|
Bit Array - private member functions.
|
|
|
|
********************************************************************/
|
|
|
|
BOOL
|
|
TBitArray::
|
|
bClone(
|
|
const TBitArray &rhs
|
|
)
|
|
{
|
|
BOOL bStatus = FALSE;
|
|
|
|
if( this == &rhs )
|
|
{
|
|
bStatus = TRUE;
|
|
}
|
|
else
|
|
{
|
|
Type *pTempBits = new Type [ nBitsToType( _nBits ) ];
|
|
|
|
if( pTempBits )
|
|
{
|
|
memcpy( pTempBits, rhs._pBits, nBitsToType( _nBits ) * sizeof( Type ) );
|
|
delete [] _pBits;
|
|
_pBits = pTempBits;
|
|
_nBits = rhs._nBits;
|
|
bStatus = TRUE;
|
|
}
|
|
}
|
|
return bStatus;
|
|
}
|
|
|
|
BOOL
|
|
TBitArray::
|
|
bGrow(
|
|
IN UINT uBits
|
|
)
|
|
{
|
|
DBGMSG( DBG_TRACE, ( "TBitArray::bGrow\n" ) );
|
|
|
|
BOOL bStatus = FALSE;
|
|
UINT uNewBits = uBits + _uGrowSize;
|
|
|
|
DBGMSG( DBG_TRACE, ( "Grow to size %d Original size %d Buffer pointer %x\n", uNewBits, _nBits, _pBits ) );
|
|
|
|
//
|
|
// We do support reducing the size of the bit array.
|
|
//
|
|
SPLASSERT( uNewBits > _nBits );
|
|
|
|
//
|
|
// Allocate the enlarged bit array.
|
|
//
|
|
Type *pNewBits = new Type [ nBitsToType( uNewBits ) ];
|
|
|
|
if( pNewBits )
|
|
{
|
|
//
|
|
// Clear the new bits.
|
|
//
|
|
memset( pNewBits, 0, nBitsToType( uNewBits ) * sizeof( Type ) );
|
|
|
|
//
|
|
// Copy the old bits to the new bit array.
|
|
//
|
|
memcpy( pNewBits, _pBits, nBitsToType( _nBits ) * sizeof( Type ) );
|
|
|
|
//
|
|
// Release the old bit array and save the new pointer and size.
|
|
//
|
|
delete [] _pBits;
|
|
_pBits = pNewBits;
|
|
_nBits = uBits;
|
|
|
|
//
|
|
// Success.
|
|
//
|
|
bStatus = TRUE;
|
|
}
|
|
|
|
DBGMSG( DBG_TRACE, ( "New size %d Buffer pointer %x\n", _nBits, _pBits ) );
|
|
|
|
return bStatus;
|
|
}
|
|
|
|
UINT
|
|
TBitArray::
|
|
nBitsToType(
|
|
IN UINT uBits
|
|
) const
|
|
{
|
|
return ( uBits + kBitsInType - 1 ) / kBitsInType;
|
|
}
|
|
|
|
TBitArray::Type
|
|
TBitArray::
|
|
BitToMask(
|
|
IN UINT uBit
|
|
) const
|
|
{
|
|
return 1 << ( uBit % kBitsInType );
|
|
}
|
|
|
|
UINT
|
|
TBitArray::
|
|
BitToIndex(
|
|
IN UINT uBit
|
|
) const
|
|
{
|
|
return uBit / kBitsInType;
|
|
}
|
|
|
|
BOOL
|
|
TBitArray::
|
|
bIsValidBit(
|
|
IN UINT uBit
|
|
) const
|
|
{
|
|
BOOL bStatus = ( uBit < _nBits ) && bValid();
|
|
|
|
if( !bStatus )
|
|
{
|
|
DBGMSG( DBG_TRACE, ( "Invalid bit value %d\n", uBit ) );
|
|
}
|
|
|
|
return bStatus;
|
|
}
|
|
|
|
///////////////////////////////////////////////////
|
|
// @@file@@ loadlib.cxx
|
|
///////////////////////////////////////////////////
|
|
|
|
TLibrary::
|
|
TLibrary(
|
|
LPCTSTR pszLibName
|
|
)
|
|
{
|
|
_hInst = LoadLibrary( pszLibName );
|
|
|
|
if( !_hInst )
|
|
{
|
|
DBGMSG( DBG_WARN, ( "Library.ctr: unable to load "TSTR"\n", pszLibName ));
|
|
}
|
|
}
|
|
|
|
TLibrary::
|
|
~TLibrary(
|
|
)
|
|
{
|
|
if( bValid() )
|
|
{
|
|
FreeLibrary( _hInst );
|
|
}
|
|
}
|
|
|
|
BOOL
|
|
TLibrary::
|
|
bValid(
|
|
VOID
|
|
) const
|
|
{
|
|
return _hInst != NULL;
|
|
}
|
|
|
|
FARPROC
|
|
TLibrary::
|
|
pfnGetProc(
|
|
IN LPCSTR pszProc
|
|
) const
|
|
{
|
|
FARPROC fpProc = bValid() ? GetProcAddress( _hInst, pszProc ) : NULL;
|
|
|
|
if( !fpProc )
|
|
{
|
|
DBGMSG( DBG_WARN, ( "Library.pfnGetProc: failed %s\n", pszProc ));
|
|
}
|
|
return fpProc;
|
|
}
|
|
|
|
FARPROC
|
|
TLibrary::
|
|
pfnGetProc(
|
|
IN UINT uOrdinal
|
|
) const
|
|
{
|
|
FARPROC fpProc = bValid() ? GetProcAddress( _hInst, (LPCSTR)MAKELPARAM( uOrdinal, 0 ) ) : NULL;
|
|
|
|
if( !fpProc )
|
|
{
|
|
DBGMSG( DBG_WARN, ( "Library.pfnGetProc: failed %d\n", uOrdinal ));
|
|
}
|
|
return fpProc;
|
|
}
|
|
|
|
HINSTANCE
|
|
TLibrary::
|
|
hInst(
|
|
VOID
|
|
) const
|
|
{
|
|
return _hInst;
|
|
}
|
|
|