// -------------------------------------------------------------------------- // Module Name: CLogonStatusHost.cpp // // Copyright (c) 2000, Microsoft Corporation // // File that contains implementation for ILogonStatusHost for use by UI host // executables. // // History: 2000-05-10 vtan created // -------------------------------------------------------------------------- #include "priv.h" #include #include #include "UserOM.h" #include "GinaIPC.h" #include "CInteractiveLogon.h" const WCHAR CLogonStatusHost::s_szTermSrvReadyEventName[] = TEXT("TermSrvReadyEvent"); // // IUnknown Interface // ULONG CLogonStatusHost::AddRef (void) { return(++_cRef); } ULONG CLogonStatusHost::Release (void) { ULONG ulResult; ASSERTMSG(_cRef > 0, "Invalid reference count in CLogonStatusHost::Release"); ulResult = --_cRef; if (ulResult <= 0) { delete this; ulResult = 0; } return(ulResult); } HRESULT CLogonStatusHost::QueryInterface (REFIID riid, void **ppvObj) { static const QITAB qit[] = { QITABENT(CLogonStatusHost, IDispatch), QITABENT(CLogonStatusHost, ILogonStatusHost), {0}, }; return(QISearch(this, qit, riid, ppvObj)); } // // IDispatch Interface // STDMETHODIMP CLogonStatusHost::GetTypeInfoCount (UINT* pctinfo) { return(CIDispatchHelper::GetTypeInfoCount(pctinfo)); } STDMETHODIMP CLogonStatusHost::GetTypeInfo (UINT itinfo, LCID lcid, ITypeInfo** pptinfo) { return(CIDispatchHelper::GetTypeInfo(itinfo, lcid, pptinfo)); } STDMETHODIMP CLogonStatusHost::GetIDsOfNames (REFIID riid, OLECHAR** rgszNames, UINT cNames, LCID lcid, DISPID* rgdispid) { return(CIDispatchHelper::GetIDsOfNames(riid, rgszNames, cNames, lcid, rgdispid)); } STDMETHODIMP CLogonStatusHost::Invoke (DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pdispparams, VARIANT* pvarResult, EXCEPINFO* pexcepinfo, UINT* puArgErr) { return(CIDispatchHelper::Invoke(dispidMember, riid, lcid, wFlags, pdispparams, pvarResult, pexcepinfo, puArgErr)); } // // ILogonStatusHost Interface // // -------------------------------------------------------------------------- // CLogonStatusHost::Initialize // // Arguments: hInstance = HINSTANCE of hosting process. // hwndHost = HWND of UI host process. // // Returns: HRESULT // // Purpose: Registers the StatusWindowClass and creates an invisible // window of this class to receive messages from GINA to pass // through to the UI host. // // History: 2000-05-10 vtan created // -------------------------------------------------------------------------- STDMETHODIMP CLogonStatusHost::Initialize (HINSTANCE hInstance, HWND hwndHost) { HRESULT hr; HANDLE hEvent; WNDCLASSEX wndClassEx = {0}; ASSERTMSG(_hInstance == NULL, "CLogonStatusHost::Initialized already invoked by caller."); // Save parameters to member variables. _hInstance = hInstance; _hwndHost = hwndHost; // Register this window class. wndClassEx.cbSize = sizeof(WNDCLASSEX); wndClassEx.lpfnWndProc = StatusWindowProc; wndClassEx.hInstance = hInstance; wndClassEx.lpszClassName = STATUS_WINDOW_CLASS_NAME; _atom = RegisterClassEx(&wndClassEx); // Create the window to receive messages from msgina. _hwnd = CreateWindow(MAKEINTRESOURCE(_atom), TEXT("GINA UI"), WS_OVERLAPPED, 0, 0, 0, 0, NULL, NULL, _hInstance, this); // Signal msgina that we're ready. hEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, TEXT("msgina: StatusHostReadyEvent")); if (hEvent != NULL) { TBOOL(SetEvent(hEvent)); TBOOL(CloseHandle(hEvent)); } // If we have a window then set the host window in, start waiting // for terminal services to be ready and a wait on the parent process. if (_hwnd != NULL) { _interactiveLogon.SetHostWindow(_hwndHost); StartWaitForParentProcess(); StartWaitForTermService(); hr = S_OK; } else { hr = E_OUTOFMEMORY; } return(hr); } // -------------------------------------------------------------------------- // CLogonStatusHost::UnInitialize // // Arguments: // // Returns: HRESULT // // Purpose: Cleans up resources and memory allocated in Initialize. // // History: 2001-01-03 vtan created // -------------------------------------------------------------------------- STDMETHODIMP CLogonStatusHost::UnInitialize (void) { ASSERTMSG(_hInstance != NULL, "CLogonStatusHost::UnInitialized invoked without Initialize."); if (_hwnd != NULL) { EndWaitForTermService(); EndWaitForParentProcess(); if (_fRegisteredNotification != FALSE) { TBOOL(WinStationUnRegisterConsoleNotification(SERVERNAME_CURRENT, _hwnd)); _fRegisteredNotification = FALSE; } TBOOL(DestroyWindow(_hwnd)); _hwnd = NULL; } if (_atom != 0) { TBOOL(UnregisterClass(MAKEINTRESOURCE(_atom), _hInstance)); _atom = 0; } _hwndHost = NULL; _hInstance = NULL; return(S_OK); } // -------------------------------------------------------------------------- // CLogonStatusHost::WindowProcedureHelper // // Arguments: See the platform SDK under WindowProc. // // Returns: HRESULT // // Purpose: Handles certain messages for the status UI host. This allows // things like ALT-F4 to be discarded or power messages to be // responded to correctly. // // History: 2000-05-10 vtan created // -------------------------------------------------------------------------- STDMETHODIMP CLogonStatusHost::WindowProcedureHelper (HWND hwnd, UINT uMsg, VARIANT wParam, VARIANT lParam) { UNREFERENCED_PARAMETER(hwnd); UNREFERENCED_PARAMETER(lParam); HRESULT hr; hr = E_NOTIMPL; switch (uMsg) { case WM_SYSCOMMAND: if (SC_CLOSE == wParam.uintVal) // Blow off ALT-F4 { hr = S_OK; } break; default: break; } return(hr); } // -------------------------------------------------------------------------- // CLogonStatusHost::Handle_WM_UISERVICEREQUEST // // Arguments: wParam = WPARAM sent from GINA. // lParam = LPARAM sent from GINA. // // Returns: LRESULT // // Purpose: Receives messages from GINA bound for the UI host. Turns // around and passes the messages to the UI host. This allows // the actual implementation to change without having to // rebuild the UI host. // // History: 2000-05-10 vtan created // -------------------------------------------------------------------------- LRESULT CLogonStatusHost::Handle_WM_UISERVICEREQUEST (WPARAM wParam, LPARAM lParam) { LRESULT lResult; WPARAM wParamSend; void *pV; lResult = 0; pV = NULL; wParamSend = HM_NOACTION; switch (wParam) { case UI_TERMINATE: ExitProcess(0); break; case UI_STATE_STATUS: _interactiveLogon.Stop(); wParamSend = HM_SWITCHSTATE_STATUS; break; case UI_STATE_LOGON: _interactiveLogon.Start(); wParamSend = HM_SWITCHSTATE_LOGON; break; case UI_STATE_LOGGEDON: _interactiveLogon.Stop(); wParamSend = HM_SWITCHSTATE_LOGGEDON; break; case UI_STATE_HIDE: _interactiveLogon.Stop(); TBOOL(SetProcessWorkingSetSize(GetCurrentProcess(), static_cast(-1), static_cast(-1))); wParamSend = HM_SWITCHSTATE_HIDE; break; case UI_STATE_END: EndWaitForTermService(); EndWaitForParentProcess(); wParamSend = HM_SWITCHSTATE_DONE; break; case UI_NOTIFY_WAIT: wParamSend = HM_NOTIFY_WAIT; break; case UI_SELECT_USER: pV = LocalAlloc(LPTR, sizeof(SELECT_USER)); if (pV != NULL) { SELECT_USER* psl = (SELECT_USER*)pV; LOGONIPC_USERID* pipc = (LOGONIPC_USERID*)lParam; StringCchCopyW(psl->szUsername, ARRAYSIZE(psl->szUsername), pipc->wszUsername); StringCchCopyW(psl->szDomain, ARRAYSIZE(psl->szDomain), pipc->wszDomain); wParamSend = HM_SELECT_USER; lParam = (LPARAM)pV; } break; case UI_SET_ANIMATIONS: wParamSend = HM_SET_ANIMATIONS; break; case UI_INTERACTIVE_LOGON: pV = LocalAlloc(LPTR, sizeof(INTERACTIVE_LOGON_REQUEST)); if (pV != NULL) { INTERACTIVE_LOGON_REQUEST* plr = (INTERACTIVE_LOGON_REQUEST*)pV; LOGONIPC_CREDENTIALS* pipc = (LOGONIPC_CREDENTIALS*)lParam; StringCchCopyW(plr->szUsername, ARRAYSIZE(plr->szUsername), pipc->userID.wszUsername); StringCchCopyW(plr->szDomain, ARRAYSIZE(plr->szDomain), pipc->userID.wszDomain); StringCchCopyW(plr->szPassword, ARRAYSIZE(plr->szPassword), pipc->wszPassword); ZeroMemory(pipc->wszPassword, (lstrlenW(pipc->wszPassword) + 1) * sizeof(WCHAR)); wParamSend = HM_INTERACTIVE_LOGON_REQUEST; lParam = (LPARAM)pV; } break; case UI_DISPLAY_STATUS: wParamSend = HM_DISPLAYSTATUS; break; default: break; } if (wParam != HM_NOACTION) { lResult = SendMessage(_hwndHost, WM_UIHOSTMESSAGE, wParamSend, lParam); } else { lResult = 0; } if (pV != NULL) { (HLOCAL)LocalFree(pV); } return(lResult); } // -------------------------------------------------------------------------- // CLogonStatusHost::Handle_WM_WTSSESSION_CHANGE // // Arguments: wParam = // lParam = // // Returns: LRESULT // // Purpose: Receives messages from GINA bound for the UI host. Turns // around and passes the messages to the UI host. This allows // the actual implementation to change without having to // rebuild the UI host. // // History: 2000-05-10 vtan created // -------------------------------------------------------------------------- LRESULT CLogonStatusHost::Handle_WM_WTSSESSION_CHANGE (WPARAM wParam, LPARAM lParam) { UNREFERENCED_PARAMETER(lParam); LRESULT lResult; lResult = 0; switch (wParam) { case WTS_CONSOLE_CONNECT: case WTS_CONSOLE_DISCONNECT: case WTS_REMOTE_CONNECT: case WTS_REMOTE_DISCONNECT: break; case WTS_SESSION_LOGON: case WTS_SESSION_LOGOFF: lResult = SendMessage(_hwndHost, WM_UIHOSTMESSAGE, HM_DISPLAYREFRESH, 0); break; default: break; } return(lResult); } // -------------------------------------------------------------------------- // CLogonStatusHost::StatusWindowProc // // Arguments: See the platform SDK under WindowProc. // // Returns: // // Purpose: Window procedure for StatusWindowClass. // // History: 2000-05-10 vtan created // -------------------------------------------------------------------------- LRESULT CALLBACK CLogonStatusHost::StatusWindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { LRESULT lResult; CLogonStatusHost *pThis; pThis = reinterpret_cast(GetWindowLongPtr(hwnd, GWLP_USERDATA)); switch (uMsg) { case WM_CREATE: { CREATESTRUCT *pCreateStruct; pCreateStruct = reinterpret_cast(lParam); (LONG_PTR)SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast(pCreateStruct->lpCreateParams)); lResult = 0; break; } case WM_UISERVICEREQUEST: lResult = pThis->Handle_WM_UISERVICEREQUEST(wParam, lParam); break; case WM_WTSSESSION_CHANGE: lResult = pThis->Handle_WM_WTSSESSION_CHANGE(wParam, lParam); break; case WM_SETTINGCHANGE: if (wParam == SPI_SETWORKAREA) { lResult = SendMessage(pThis->_hwndHost, WM_UIHOSTMESSAGE, HM_DISPLAYRESIZE, 0); } else { lResult = 0; } break; default: lResult = DefWindowProc(hwnd, uMsg, wParam, lParam); break; } return(lResult); } // -------------------------------------------------------------------------- // CLogonStatusHost::IsTermServiceDisabled // // Arguments: // // Returns: bool // // Purpose: Determines from the service control manager whether terminal // services is disabled. // // History: 2001-01-04 vtan created // -------------------------------------------------------------------------- bool CLogonStatusHost::IsTermServiceDisabled (void) { bool fResult; SC_HANDLE hSCManager; fResult = false; hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); if (hSCManager != NULL) { SC_HANDLE hSCTermService; hSCTermService = OpenService(hSCManager, TEXT("TermService"), SERVICE_QUERY_CONFIG); if (hSCTermService != NULL) { DWORD dwBytesNeeded; QUERY_SERVICE_CONFIG *pServiceConfig; (BOOL)QueryServiceConfig(hSCTermService, NULL, 0, &dwBytesNeeded); pServiceConfig = static_cast(LocalAlloc(LMEM_FIXED, dwBytesNeeded)); if (pServiceConfig != NULL) { if (QueryServiceConfig(hSCTermService, pServiceConfig, dwBytesNeeded, &dwBytesNeeded) != FALSE) { fResult = (pServiceConfig->dwStartType == SERVICE_DISABLED); } (HLOCAL)LocalFree(pServiceConfig); } TBOOL(CloseServiceHandle(hSCTermService)); } TBOOL(CloseServiceHandle(hSCManager)); } return(fResult); } // -------------------------------------------------------------------------- // CLogonStatusHost::StartWaitForTermService // // Arguments: // // Returns: // // Purpose: Register for console notifications with terminal services. If // the service is disabled don't bother. If the service hasn't // started then create a thread to wait for it and re-perform the // registration. // // History: 2001-01-03 vtan created // -------------------------------------------------------------------------- void CLogonStatusHost::StartWaitForTermService (void) { // Don't do anything if terminal services is disabled. if (!IsTermServiceDisabled()) { // Try to register the notification first. _fRegisteredNotification = WinStationRegisterConsoleNotification(SERVERNAME_CURRENT, _hwnd, NOTIFY_FOR_ALL_SESSIONS); if (_fRegisteredNotification == FALSE) { DWORD dwThreadID; (ULONG)AddRef(); _hThreadWaitForTermService = CreateThread(NULL, 0, CB_WaitForTermService, this, 0, &dwThreadID); if (_hThreadWaitForTermService == NULL) { (ULONG)Release(); } } } } // -------------------------------------------------------------------------- // CLogonStatusHost::EndWaitForTermService // // Arguments: // // Returns: // // Purpose: If a thread has been created and the thread is still executing // then wake it up and force it to exit. If the thread cannot be // woken up then terminate it. Release handles. // // History: 2001-01-03 vtan created // -------------------------------------------------------------------------- void CLogonStatusHost::EndWaitForTermService (void) { HANDLE hThread; // Grab the _hThreadWaitForTermService now. This will indicate to the // thread should it decide to finish executing that it shouldn't release // the reference on itself. hThread = InterlockedExchangePointer(&_hThreadWaitForTermService, NULL); if (hThread != NULL) { // Queue an APC to the wait thread. If the queue succeeds then // wait for the thread to finish executing. If the queue fails // the thread probably finished between the time we executed the // InterlockedExchangePointer above and the QueueUserAPC. if (QueueUserAPC(CB_WakeupThreadAPC, hThread, PtrToUlong(this)) != FALSE) { (DWORD)WaitForSingleObject(hThread, INFINITE); } TBOOL(CloseHandle(hThread)); (ULONG)Release(); } } // -------------------------------------------------------------------------- // CLogonStatusHost::WaitForTermService // // Arguments: // // Returns: // // Purpose: Simple thread that waits for terminal services to signal that // it's ready and then registers for notifications. This is // required because this DLL initializes before terminal services // has had a chance to start up. // // History: 2000-10-20 vtan created // 2001-01-04 vtan allow premature exit // -------------------------------------------------------------------------- void CLogonStatusHost::WaitForTermService (void) { DWORD dwWaitResult; int iCounter; HANDLE hTermSrvReadyEvent, hThread; dwWaitResult = 0; iCounter = 0; hTermSrvReadyEvent = OpenEvent(SYNCHRONIZE, FALSE, s_szTermSrvReadyEventName); while ((dwWaitResult == 0) && (hTermSrvReadyEvent == NULL) && (iCounter < 60)) { ++iCounter; dwWaitResult = SleepEx(1000, TRUE); if (dwWaitResult == 0) { hTermSrvReadyEvent = OpenEvent(SYNCHRONIZE, FALSE, s_szTermSrvReadyEventName); } } if (hTermSrvReadyEvent != NULL) { dwWaitResult = WaitForSingleObjectEx(hTermSrvReadyEvent, 60000, TRUE); if (dwWaitResult == WAIT_OBJECT_0) { _fRegisteredNotification = WinStationRegisterConsoleNotification(SERVERNAME_CURRENT, _hwnd, NOTIFY_FOR_ALL_SESSIONS); } TBOOL(CloseHandle(hTermSrvReadyEvent)); } // Grab the _hThreadWaitForTermService now. This will indicate to the // EndWaitForTermService function that we've reached the point of no // return and we're going to release ourselves. If we can't grab the // handle then EndWaitForTermService must be telling us to stop now. hThread = InterlockedExchangePointer(&_hThreadWaitForTermService, NULL); if (hThread != NULL) { TBOOL(CloseHandle(hThread)); (ULONG)Release(); } } // -------------------------------------------------------------------------- // CLogonStatusHost::CB_WaitForTermService // // Arguments: pParameter = User defined data. // // Returns: DWORD // // Purpose: Stub to call member function. // // History: 2001-01-04 vtan created // -------------------------------------------------------------------------- DWORD WINAPI CLogonStatusHost::CB_WaitForTermService (void *pParameter) { static_cast(pParameter)->WaitForTermService(); return(0); } // -------------------------------------------------------------------------- // CLogonStatusHost::StartWaitForParentProcess // // Arguments: // // Returns: // // Purpose: Create a thread to wait on the parent process. Terminal // services will terminate a non-session 0 winlogon which will // leave us dangling. Detect this case and exit cleanly. This // will allow csrss and win32k to clean up and release resources. // // History: 2001-01-03 vtan created // -------------------------------------------------------------------------- void CLogonStatusHost::StartWaitForParentProcess (void) { ULONG ulReturnLength; PROCESS_BASIC_INFORMATION processBasicInformation; // Open a handle to our parent process. This will be winlogon. // If the parent dies then so do we. if (NT_SUCCESS(NtQueryInformationProcess(GetCurrentProcess(), ProcessBasicInformation, &processBasicInformation, sizeof(processBasicInformation), &ulReturnLength))) { _hProcessParent = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, static_cast(processBasicInformation.InheritedFromUniqueProcessId)); #ifdef DEBUG if (IsDebuggerPresent()) { if (NT_SUCCESS(NtQueryInformationProcess(_hProcessParent, ProcessBasicInformation, &processBasicInformation, sizeof(processBasicInformation), &ulReturnLength))) { TBOOL(CloseHandle(_hProcessParent)); _hProcessParent = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, static_cast(processBasicInformation.InheritedFromUniqueProcessId)); } } #endif /* DEBUG */ if (_hProcessParent != NULL) { DWORD dwThreadID; (ULONG)AddRef(); _hThreadWaitForParentProcess = CreateThread(NULL, 0, CB_WaitForParentProcess, this, 0, &dwThreadID); if (_hThreadWaitForParentProcess == NULL) { (ULONG)Release(); } } } } // -------------------------------------------------------------------------- // CLogonStatusHost::EndWaitForParentProcess // // Arguments: // // Returns: // // Purpose: If a thread waiting on the parent process is executing then // wake it up and force it to exit. If the thread cannot be woken // then terminate it. Release the handles used. // // History: 2000-12-11 vtan created // -------------------------------------------------------------------------- void CLogonStatusHost::EndWaitForParentProcess (void) { HANDLE hThread; // Do exactly the same thing that EndWaitForTermService does to correctly // control the reference count on the "this" object. Whoever grabs the // _hThreadWaitForParentProcess is the guy who releases the reference. hThread = InterlockedExchangePointer(&_hThreadWaitForParentProcess, NULL); if (hThread != NULL) { if (QueueUserAPC(CB_WakeupThreadAPC, hThread, PtrToUlong(this)) != FALSE) { (DWORD)WaitForSingleObject(hThread, INFINITE); } TBOOL(CloseHandle(hThread)); (ULONG)Release(); } // Always release this handle the callback doesn't do this. if (_hProcessParent != NULL) { TBOOL(CloseHandle(_hProcessParent)); _hProcessParent = NULL; } } // -------------------------------------------------------------------------- // CLogonStatusHost::ParentProcessTerminated // // Arguments: // // Returns: // // Purpose: Handles parent process termination. Terminate process on us. // // History: 2000-12-11 vtan created // -------------------------------------------------------------------------- void CLogonStatusHost::WaitForParentProcess (void) { DWORD dwWaitResult; HANDLE hThread; // Make a Win32 API call now so that the thread is converted to // a GUI thread. This will allow the PostMessage call to work // once the parent process is terminated. If the thread isn't // a GUI thread the system will not convert it to one in the // state when the callback is executed. TBOOL(PostMessage(_hwndHost, WM_NULL, 0, 0)); dwWaitResult = WaitForSingleObjectEx(_hProcessParent, INFINITE, TRUE); if (dwWaitResult == WAIT_OBJECT_0) { TBOOL(PostMessage(_hwndHost, WM_UIHOSTMESSAGE, HM_SWITCHSTATE_DONE, 0)); } hThread = InterlockedExchangePointer(&_hThreadWaitForParentProcess, NULL); if (hThread != NULL) { TBOOL(CloseHandle(hThread)); (ULONG)Release(); } } // -------------------------------------------------------------------------- // CLogonStatusHost::CB_WaitForParentProcess // // Arguments: pParameter = User defined data. // // Returns: DWORD // // Purpose: Stub to call member function. // // History: 2001-01-04 vtan created // -------------------------------------------------------------------------- DWORD WINAPI CLogonStatusHost::CB_WaitForParentProcess (void *pParameter) { static_cast(pParameter)->WaitForParentProcess(); return(0); } // -------------------------------------------------------------------------- // CLogonStatusHost::CB_WakeupThreadAPC // // Arguments: dwParam = User defined data. // // Returns: // // Purpose: APCProc to wake up a thread waiting in an alertable state. // // History: 2001-01-04 vtan created // -------------------------------------------------------------------------- void CALLBACK CLogonStatusHost::CB_WakeupThreadAPC (ULONG_PTR dwParam) { UNREFERENCED_PARAMETER(dwParam); } // -------------------------------------------------------------------------- // CLogonStatusHost::CLogonStatusHost // // Arguments: // // Returns: // // Purpose: Constructor for CLogonStatusHost. // // History: 2000-05-10 vtan created // -------------------------------------------------------------------------- CLogonStatusHost::CLogonStatusHost (void) : CIDispatchHelper(&IID_ILogonStatusHost, &LIBID_SHGINALib), _cRef(1), _hInstance(NULL), _hwnd(NULL), _hwndHost(NULL), _atom(0), _fRegisteredNotification(FALSE), _hThreadWaitForTermService(NULL), _hThreadWaitForParentProcess(NULL), _hProcessParent(NULL) { DllAddRef(); } // -------------------------------------------------------------------------- // CLogonStatusHost::~CLogonStatusHost // // Arguments: // // Returns: // // Purpose: Destructor for CLogonStatusHost. // // History: 2000-05-10 vtan created // -------------------------------------------------------------------------- CLogonStatusHost::~CLogonStatusHost (void) { ASSERTMSG((_hProcessParent == NULL) && (_hThreadWaitForParentProcess == NULL) && (_hThreadWaitForTermService == NULL) && (_fRegisteredNotification == FALSE) && (_atom == 0) && (_hwndHost == NULL) && (_hwnd == NULL) && (_hInstance == NULL), "Must UnIniitialize object before destroying in CLogonStatusHost::~CLogonStatusHost"); ASSERTMSG(_cRef == 0, "Reference count expected to be zero in CLogonStatusHost::~CLogonStatusHost"); DllRelease(); } // -------------------------------------------------------------------------- // CLogonStatusHost_Create // // Arguments: riid = Class GUID to QI to return. // ppv = Interface returned. // // Returns: HRESULT // // Purpose: Creates the CLogonStatusHost class and returns the specified // interface supported by the class to the caller. // // History: 2000-05-10 vtan created // -------------------------------------------------------------------------- STDAPI CLogonStatusHost_Create (REFIID riid, void** ppvObj) { HRESULT hr; CLogonStatusHost* pLogonStatusHost; hr = E_OUTOFMEMORY; pLogonStatusHost = new CLogonStatusHost; if (pLogonStatusHost != NULL) { hr = pLogonStatusHost->QueryInterface(riid, ppvObj); pLogonStatusHost->Release(); } return(hr); }