windows-nt/Source/XPSP1/NT/admin/activec/nodemgr/power.cpp
2020-09-26 16:20:57 +08:00

697 lines
21 KiB
C++

/*--------------------------------------------------------------------------*
*
* Microsoft Windows
* Copyright (C) Microsoft Corporation, 1992 - 000
*
* File: power.cpp
*
* Contents: Implementation file for CConsolePower
*
* History: 25-Feb-2000 jeffro Created
*
*--------------------------------------------------------------------------*/
#include <stdafx.h>
#include "power.h"
/*
* allocate a TLS index for CConsolePower objects
*/
const DWORD CConsolePower::s_dwTlsIndex = TlsAlloc();
const DWORD CConsolePower::s_rgExecStateFlag[CConsolePower::eIndex_Count] =
{
ES_SYSTEM_REQUIRED, // eIndex_System
ES_DISPLAY_REQUIRED, // eIndex_Display
};
const CConsolePower::ExecutionStateFunc CConsolePower::s_FuncUninitialized =
(ExecutionStateFunc)(LONG_PTR) -1;
CConsolePower::ExecutionStateFunc CConsolePower::SetThreadExecutionState_ =
s_FuncUninitialized;
/*+-------------------------------------------------------------------------*
* ScPrepExecutionStateFlag
*
* This function increments or decrements the count for dwTestBit for this
* object, and for the thread. On exit dwSTESArg contains the appropriate
* flag to pass to SetThreadExecutionState for dwTestBit. That is, if the
* thread count for dwTestBit is non-zero, dwSTESArg will contain dwTestBit
* on return; if the thread count is zero, dwSTESArg will not contain
* dwTestBit.
*--------------------------------------------------------------------------*/
static SC ScPrepExecutionStateFlag (
DWORD dwTestBit, /* I:single ES_* flag to test */
DWORD dwAdd, /* I:flags to add */
DWORD dwRemove, /* I:flags to remove */
DWORD * pdwSTESArg, /* I/O:arg to SetThreadExecutionState */
LONG * pcObjectRequests, /* I/O:request count for this object */
LONG * pcThreadRequests, /* I/O:request count for this thread */
UINT cIterations = 1) /* I:times to add/remove dwTestBit */
{
DECLARE_SC (sc, _T("ScPrepExecutionStateFlag"));
/*
* validate inputs -- DO NOT clear these output variables, the
* existing values are modified here
*/
sc = ScCheckPointers (pdwSTESArg, pcObjectRequests, pcThreadRequests);
if (sc)
return (sc);
/*
* make sure the bit isn't to be both removed and added
*/
if ((dwAdd & dwTestBit) && (dwRemove & dwTestBit))
return (sc = E_INVALIDARG);
/*
* We should always have a non-negative number of requests for the bit
* under test for this object, and at least as many requests for the
* thread as we do for this object.
*/
ASSERT (*pcObjectRequests >= 0);
ASSERT (*pcThreadRequests >= *pcObjectRequests);
if ((*pcObjectRequests < 0) || (*pcThreadRequests < *pcObjectRequests))
return (sc = E_UNEXPECTED);
/*
* If we're adding the test bit, bump up the request count for this
* object and this thread.
*/
if (dwAdd & dwTestBit)
{
*pcObjectRequests += cIterations;
*pcThreadRequests += cIterations;
}
/*
* Otherwise, if we're removing the test bit, bump down the request counts
* for this object and this thread.
*/
else if (dwRemove & dwTestBit)
{
/*
* Can't remove the bit under test if we don't have an outstanding
* request for it on this object.
*/
if (*pcObjectRequests < cIterations)
return (sc = E_INVALIDARG);
*pcObjectRequests -= cIterations;
*pcThreadRequests -= cIterations;
}
/*
* If the net count for this thread is non-zero, the bit under
* test needs to be in the argument for SetThreadExecutionState;
* if not, the bit under test needs to be removed.
*/
if (*pcThreadRequests != 0)
*pdwSTESArg |= dwTestBit;
else
*pdwSTESArg &= ~dwTestBit;
return (sc);
}
/*+-------------------------------------------------------------------------*
* CConsolePower::CConsolePower
*
* Constructs a CConsolePower object.
*--------------------------------------------------------------------------*/
CConsolePower::CConsolePower () :
m_wndPower (this)
{
DEBUG_INCREMENT_INSTANCE_COUNTER(CConsolePower);
/*
* If any of these fail, s_rgExecStateFlag is out of order. It would
* be better to use COMPILETIME_ASSERT here, but using that gives us
*
* error C2051: case expression not constant
*
* Bummer.
*/
ASSERT (s_rgExecStateFlag[eIndex_System] == ES_SYSTEM_REQUIRED);
ASSERT (s_rgExecStateFlag[eIndex_Display] == ES_DISPLAY_REQUIRED);
}
/*+-------------------------------------------------------------------------*
* CConsolePower::~CConsolePower
*
* Destroys a CConsolePower object. If this object holds references to
* ES_SYSTEM_REQUIRED or ES_DISPLAY_REQUIRED settings, they will be cleared.
*--------------------------------------------------------------------------*/
CConsolePower::~CConsolePower ()
{
DECLARE_SC (sc, _T("CConsolePower::~CConsolePower"));
DEBUG_DECREMENT_INSTANCE_COUNTER(CConsolePower);
/*
* clean up outstanding references, if any
*/
if (!IsBadCodePtr ((FARPROC) SetThreadExecutionState_) &&
((m_Counts.m_rgCount[eIndex_System] != 0) ||
(m_Counts.m_rgCount[eIndex_Display] != 0)))
{
try
{
/*
* get the thread counts
*/
sc = ScCheckPointers (m_spThreadCounts, E_UNEXPECTED);
if (sc)
sc.Throw();
DWORD dwFlags = ES_CONTINUOUS;
/*
* clean up each individual count
*/
for (int i = 0; i < eIndex_Count; i++)
{
/*
* prevent underflow
*/
if (m_Counts.m_rgCount[i] > m_spThreadCounts->m_rgCount[i])
(sc = E_UNEXPECTED).Throw();
sc = ScPrepExecutionStateFlag (s_rgExecStateFlag[i], // dwTestBit
0, // dwAdd
s_rgExecStateFlag[i], // dwRemove
&dwFlags,
&m_Counts.m_rgCount[i],
&m_spThreadCounts->m_rgCount[i],
m_Counts.m_rgCount[i]);
if (sc)
sc.Throw();
}
/*
* clean up the execution state for this thread
*/
if (!SetThreadExecutionState_(dwFlags))
{
sc.FromLastError();
sc.Throw();
}
}
catch (SC& scCaught)
{
sc = scCaught;
}
}
}
/*+-------------------------------------------------------------------------*
* CConsolePower::FinalConstruct
*
* This isn't the typical use of FinalConstruct in ATL objects. It is
* typically used for creating aggregated objects, but using it in this
* way allows us to prevent the creation of CConsolePower objects without
* throwing an exception from the ctor, which ATL can't handle.
*--------------------------------------------------------------------------*/
HRESULT CConsolePower::FinalConstruct ()
{
DECLARE_SC (sc, _T("CConsolePower::FinalConstruct"));
/*
* if this is the first time a CConsolePower has been created, try to
* dynaload SetThreadExecutionState (it's not supported on WinNT and
* Win95)
*/
if (SetThreadExecutionState_ == s_FuncUninitialized)
{
SetThreadExecutionState_ =
(ExecutionStateFunc) GetProcAddress (
GetModuleHandle (_T("kernel32.dll")),
"SetThreadExecutionState");
}
/*
* if SetThreadExecutionState is supported on this platform, do the
* other initialization we'll need
*/
if (!IsBadCodePtr ((FARPROC) SetThreadExecutionState_))
{
/*
* if we couldn't get the thread-local CRefCountedTlsExecutionCounts
* object for this thread, CConsolePower is useless, so fail creation
*/
sc = ScGetThreadCounts (&m_spThreadCounts);
if (sc)
return (sc.ToHr());
sc = ScCheckPointers (m_spThreadCounts, E_UNEXPECTED);
if (sc)
return (sc.ToHr());
/*
* create the window to handle WM_POWERBROADCAST
*/
sc = m_wndPower.ScCreate ();
if (sc)
return (sc.ToHr());
}
return (sc.ToHr());
}
/*+-------------------------------------------------------------------------*
* CConsolePower::ScGetThreadCounts
*
* Returns a pointer to the thread-local CRefCountedTlsExecutionCounts object
* for this thread, allocating one if necessary.
*
* NOTE: The returned pointer has a reference added. It is the client's
* responsibility to release the reference.
*--------------------------------------------------------------------------*/
SC CConsolePower::ScGetThreadCounts (CRefCountedTlsExecutionCounts** ppThreadCounts)
{
DECLARE_SC (sc, _T("CConsolePower::ScGetThreadCounts"));
/*
* we shouldn't get here if we're on a platform that doesn't support
* SetThreadExecutionState
*/
ASSERT (!IsBadCodePtr ((FARPROC) SetThreadExecutionState_));
if (IsBadCodePtr ((FARPROC) SetThreadExecutionState_))
return (sc = E_UNEXPECTED);
sc = ScCheckPointers (ppThreadCounts);
if (sc)
return (sc);
/*
* init output
*/
(*ppThreadCounts) = NULL;
/*
* couldn't allocate a TLS index? fail
*/
if (s_dwTlsIndex == TLS_OUT_OF_INDEXES)
return (sc = E_OUTOFMEMORY);
/*
* Get the existing thread counts structure. If this is the first
* time through (i.e. the first CConsolePower created on this thread),
* we won't have a thread counts structure, so we'll allocate one now.
*/
CTlsExecutionCounts* pTEC = CTlsExecutionCounts::GetThreadInstance(s_dwTlsIndex);
if (pTEC == NULL)
{
/*
* allocate the struct for this thread
*/
(*ppThreadCounts) = CRefCountedTlsExecutionCounts::CreateInstance();
if ((*ppThreadCounts) == NULL)
return (sc = E_OUTOFMEMORY);
/*
* put it in our TLS slot
*/
sc = (*ppThreadCounts)->ScSetThreadInstance (s_dwTlsIndex);
if (sc)
return (sc);
}
else
(*ppThreadCounts) = static_cast<CRefCountedTlsExecutionCounts*>(pTEC);
/*
* put a reference on for the caller
*/
(*ppThreadCounts)->AddRef();
return (sc);
}
/*+-------------------------------------------------------------------------*
* CConsolePower::SetExecutionState
*
* This method wraps the ::SetThreadExecutionState API in a manner that is
* safe in the presence of multiple COM servers (i.e. snap-ins) that might
* need to call ::SetThreadExecutionState.
*
* The problem is that ::SetThreadExecutionState doesn't maintain reference
* counts on the flags that it is passed. For instance:
*
* SetThreadExecutionState (ES_CONTINUOUS | ES_SYSTEM_REQUIRED);
* SetThreadExecutionState (ES_CONTINUOUS | ES_SYSTEM_REQUIRED);
* SetThreadExecutionState (ES_CONTINUOUS);
*
* will result in the ES_SYSTEM_REQUIRED bit being off, even though it was
* set twice and only cleared once. This can lead to conflicts between
* snap-ins, like in this scenario:
*
* SnapinA:
* SetThreadExecutionState (ES_CONTINUOUS | ES_SYSTEM_REQUIRED);
*
* SnapinB:
* SetThreadExecutionState (ES_CONTINUOUS | ES_SYSTEM_REQUIRED);
* SetThreadExecutionState (ES_CONTINUOUS);
*
* (a Long Time passes)
*
* SnapinA:
* SetThreadExecutionState (ES_CONTINUOUS);
*
* Because of the nature of SetThreadExecutionState, during
* the Long Time, SnapinA thinks the ES_SYSTEM_REQUIRED bit is set, even
* though SnapinB has turned it off.
*
* The CConsolePower object maintains a per-snap-in count of the execution
* state bits, so they can all happily coexist.
*--------------------------------------------------------------------------*/
STDMETHODIMP CConsolePower::SetExecutionState (
DWORD dwAdd, /* I:flags to add */
DWORD dwRemove) /* I:flags to remove */
{
DECLARE_SC (sc, _T("CConsolePower::SetExecutionState"));
#ifdef DBG
/*
* this object is CoCreated so we can't tell what the snap-in name is
*/
sc.SetSnapinName (_T("<unknown>"));
#endif
/*
* if SetExecutionState isn't supported on this platform, don't do
* anything, but still "succeed"
*/
if (IsBadCodePtr ((FARPROC) SetThreadExecutionState_))
return ((sc = S_FALSE).ToHr());
const DWORD dwValidFlags = ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED;
DWORD dwFlags = 0;
/*
* if either dwAdd or dwRemove contain flags we don't recognize
* (including ES_CONTINUOUS, which we expect to get in fContinuous)
* fail
*/
if (((dwAdd | dwRemove) & ~dwValidFlags) != 0)
return ((sc = E_INVALIDARG).ToHr());
/*
* if we didn't get any flags, fail
*/
if ((dwAdd == 0) && (dwRemove == 0))
return ((sc = E_INVALIDARG).ToHr());
/*
* make sure we've got our thread counts
*/
sc = ScCheckPointers (m_spThreadCounts, E_UNEXPECTED);
if (sc)
return (sc.ToHr());
dwFlags = ES_CONTINUOUS;
/*
* add/remove each individual flag
*/
for (int i = 0; i < eIndex_Count; i++)
{
sc = ScPrepExecutionStateFlag (s_rgExecStateFlag[i], // dwTestBit
dwAdd,
dwRemove,
&dwFlags,
&m_Counts.m_rgCount[i],
&m_spThreadCounts->m_rgCount[i]);
if (sc)
return (sc.ToHr());
}
/*
* set the execution state for this thread
*/
if (!SetThreadExecutionState_(dwFlags))
sc.FromLastError().ToHr();
return (sc.ToHr());
}
/*+-------------------------------------------------------------------------*
* CConsolePower::ResetIdleTimer
*
* Simple wrapper for SetThreadExecutionState (without ES_CONTINUOUS).
*--------------------------------------------------------------------------*/
STDMETHODIMP CConsolePower::ResetIdleTimer (DWORD dwFlags)
{
DECLARE_SC (sc, _T("CConsolePower::ResetIdleTimer"));
#ifdef DBG
/*
* this object is CoCreated so we can't tell what the snap-in name is
*/
sc.SetSnapinName (_T("<unknown>"));
#endif
/*
* if SetExecutionState isn't supported on this platform, don't do
* anything, but still "succeed"
*/
if (IsBadCodePtr ((FARPROC) SetThreadExecutionState_))
return ((sc = S_FALSE).ToHr());
/*
* Set the execution state for this thread. SetThreadExecutionState
* will do all parameter validation.
*/
if (!SetThreadExecutionState_(dwFlags))
sc.FromLastError().ToHr();
return (sc.ToHr());
}
/*+-------------------------------------------------------------------------*
* CConsolePower::OnPowerBroadcast
*
* WM_POWERBROADCAST handler for CConsolePower.
*--------------------------------------------------------------------------*/
LRESULT CConsolePower::OnPowerBroadcast (WPARAM wParam, LPARAM lParam)
{
/*
* PBT_APMQUERYSUSPEND is the only event that the recipient can
* deny. If a snap-in denies (by returning BROADCAST_QUERY_DENY),
* there's no need to continue to fire the event to other snap-ins,
* so we can break out and return the denial.
*/
bool fBreakIfDenied = (wParam == PBT_APMQUERYSUSPEND);
int cConnections = m_vec.GetSize();
for (int i = 0; i < cConnections; i++)
{
CComQIPtr<IConsolePowerSink> spPowerSink = m_vec.GetAt(i);
if (spPowerSink != NULL)
{
LRESULT lResult = TRUE;
HRESULT hr = spPowerSink->OnPowerBroadcast (wParam, lParam, &lResult);
/*
* if the snap-in denied a PBT_APMQUERYSUSPEND, short out here
*/
if (SUCCEEDED(hr) && fBreakIfDenied && (lResult == BROADCAST_QUERY_DENY))
return (lResult);
}
}
return (TRUE);
}
/*+-------------------------------------------------------------------------*
* CConsolePower::CExecutionCounts::CExecutionCounts
*
* Constructs a CConsolePower::CExecutionCounts object.
*--------------------------------------------------------------------------*/
CConsolePower::CExecutionCounts::CExecutionCounts ()
{
for (int i = 0; i < countof (m_rgCount); i++)
m_rgCount[i] = 0;
}
/*+-------------------------------------------------------------------------*
* CConsolePower::CTlsExecutionCounts::CTlsExecutionCounts
*
* Constructs a CConsolePower::CTlsExecutionCounts object.
*--------------------------------------------------------------------------*/
CConsolePower::CTlsExecutionCounts::CTlsExecutionCounts () :
m_dwTlsIndex (Uninitialized)
{
}
/*+-------------------------------------------------------------------------*
* CConsolePower::CTlsExecutionCounts::~CTlsExecutionCounts
*
* Destroys a CConsolePower::CTlsExecutionCounts object.
*--------------------------------------------------------------------------*/
CConsolePower::CTlsExecutionCounts::~CTlsExecutionCounts ()
{
if (m_dwTlsIndex != Uninitialized)
TlsSetValue (m_dwTlsIndex, NULL);
}
/*+-------------------------------------------------------------------------*
* CConsolePower::CTlsExecutionCounts::ScSetThreadInstance
*
* Accepts a valid TLS index and stores a pointer to this object in the
* TLS slot identified by dwTlsIndex.
*--------------------------------------------------------------------------*/
SC CConsolePower::CTlsExecutionCounts::ScSetThreadInstance (DWORD dwTlsIndex)
{
DECLARE_SC (sc, _T("CConsolePower:CTlsExecutionCounts::ScSetThreadInstance"));
/*
* this can only be called once
*/
ASSERT (m_dwTlsIndex == Uninitialized);
if (m_dwTlsIndex != Uninitialized)
return (sc = E_UNEXPECTED);
/*
* there shouldn't already be something in this slot
*/
if (TlsGetValue (dwTlsIndex) != NULL)
return (sc = E_UNEXPECTED);
/*
* save a pointer to ourselves in the TLS slot
*/
if (!TlsSetValue (dwTlsIndex, this))
return (sc.FromLastError());
/*
* save the TLS index
*/
m_dwTlsIndex = dwTlsIndex;
return (sc);
}
/*+-------------------------------------------------------------------------*
* CConsolePower::CTlsExecutionCounts::GetThreadInstance
*
*
*--------------------------------------------------------------------------*/
CConsolePower::CTlsExecutionCounts*
CConsolePower::CTlsExecutionCounts::GetThreadInstance (DWORD dwTlsIndex)
{
return ((CTlsExecutionCounts*) TlsGetValue (dwTlsIndex));
}
/*+-------------------------------------------------------------------------*
* CConsolePowerWnd::CConsolePowerWnd
*
* Constructs a CConsolePowerWnd object.
*--------------------------------------------------------------------------*/
CConsolePowerWnd::CConsolePowerWnd (CConsolePower* pConsolePower) :
m_pConsolePower(pConsolePower)
{
}
/*+-------------------------------------------------------------------------*
* CConsolePowerWnd::~CConsolePowerWnd
*
* Destroys a CConsolePowerWnd object.
*--------------------------------------------------------------------------*/
CConsolePowerWnd::~CConsolePowerWnd ()
{
/*
* the Windows window for this class should never outlive the
* C++ class that wraps it.
*/
if (IsWindow ())
DestroyWindow();
}
/*+-------------------------------------------------------------------------*
* CConsolePowerWnd::ScCreate
*
* Creates the window for a CConsolePowerWnd object. This window will
* handle WM_POWERBROADCAST.
*--------------------------------------------------------------------------*/
SC CConsolePowerWnd::ScCreate ()
{
DECLARE_SC (sc, _T("CConsolePowerWnd::ScCreate"));
/*
* create an invisible top-level window (only top-level windows receive
* WM_POWERBROADCAST).
*/
RECT rectEmpty = { 0, 0, 0, 0 };
if (!Create (GetDesktopWindow(), rectEmpty))
return (sc.FromLastError());
return (sc);
}
/*+-------------------------------------------------------------------------*
* CConsolePowerWnd::OnPowerBroadcast
*
* WM_POWERBROADCAST handler for CConsolePowerWnd.
*--------------------------------------------------------------------------*/
LRESULT CConsolePowerWnd::OnPowerBroadcast (
UINT msg,
WPARAM wParam,
LPARAM lParam,
BOOL& bHandled)
{
/*
* if we aren't connected to a CConsolePower (shouldn't happen),
* we can't handle the message
*/
ASSERT (m_pConsolePower != NULL);
if (m_pConsolePower == NULL)
{
bHandled = false;
return (0);
}
return (m_pConsolePower->OnPowerBroadcast (wParam, lParam));
}