Source code of Windows XP (NT5)
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.
 
 
 
 
 
 

853 lines
24 KiB

//--------------------------------------------------------------------------
// Manage the windows list, such that we can get the IDispatch for each of
// the shell windows to be marshalled to different processes
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
// Includes...
#include "priv.h"
#include "sccls.h"
#include <varutil.h>
#include "winlist.h"
#include "iedde.h"
#define DM_WINLIST 0
void IEInitializeClassFactoryObject(IUnknown* punkAuto);
void IERevokeClassFactoryObject(void);
class CShellWindowListCF : public IClassFactory
{
public:
// IUnKnown
STDMETHODIMP QueryInterface(REFIID, void **);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
// IClassFactory
STDMETHODIMP CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObject);
STDMETHODIMP LockServer(BOOL fLock);
// constructor
CShellWindowListCF();
BOOL Init(void);
protected:
~CShellWindowListCF();
// locals
LONG _cRef;
IShellWindows *_pswWinList;
};
DWORD g_dwWinListCFRegister = 0;
DWORD g_fWinListRegistered = FALSE; // Only used in browser only mode...
IShellWindows *g_pswWinList = NULL;
// Function to get called by the tray to create the global window list and register
// it with the system
//=================================== Class Factory implemention ========================
CShellWindowListCF::CShellWindowListCF()
{
_cRef = 1;
DllAddRef();
}
BOOL CShellWindowListCF::Init()
{
HRESULT hr = CSDWindows_CreateInstance(&_pswWinList);
g_pswWinList = _pswWinList;
// First see if there already is one defined...
if (FAILED(hr))
{
TraceMsg(DM_WINLIST, "WinList_Init CoCreateInstance Failed: %x", hr);
return FALSE;
}
// And register our class factory with the system...
hr = CoRegisterClassObject(CLSID_ShellWindows, this,
CLSCTX_LOCAL_SERVER | CLSCTX_INPROC_SERVER,
REGCLS_MULTIPLEUSE, &g_dwWinListCFRegister);
// this call governs when we will call CoRevoke on the CF
if (SUCCEEDED(hr) && g_pswWinList)
g_pswWinList->ProcessAttachDetach(TRUE);
// Create an instance of the underlying window list class...
TraceMsg(DM_WINLIST, "WinList_Init CoRegisterClass: %x", hr);
return SUCCEEDED(hr);
}
CShellWindowListCF::~CShellWindowListCF()
{
if (_pswWinList)
{
g_pswWinList = NULL;
_pswWinList->Release();
}
DllRelease();
}
STDMETHODIMP CShellWindowListCF::QueryInterface(REFIID riid, void **ppvObj)
{
static const QITAB qit[] = {
QITABENT(CShellWindowListCF, IClassFactory), // IID_IClassFactory
{ 0 },
};
return QISearch(this, qit, riid, ppvObj);
}
STDMETHODIMP_(ULONG) CShellWindowListCF::AddRef()
{
return InterlockedIncrement(&_cRef);
}
STDMETHODIMP_(ULONG) CShellWindowListCF::Release()
{
if (InterlockedDecrement(&_cRef))
return _cRef;
delete this;
return 0;
}
STDMETHODIMP CShellWindowListCF::CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObj)
{
// aggregation checking is done in class factory
// For now simply use our QueryService to get the dispatch.
// this will do all of the things to create it and the like.
if (!_pswWinList)
{
ASSERT(0);
return E_FAIL;
}
return _pswWinList->QueryInterface(riid, ppvObj);
}
STDMETHODIMP CShellWindowListCF::LockServer(BOOL fLock)
{
return S_OK; // we don't do anything with this...
}
// As this is marshalled over to the main shell process hopefully this will take care of
// most of the serialization problems. Probably still need a way to handle the case better
// where a window is coming up at the same time the last one is going down...
STDAPI CWinListShellProc_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi)
{
*ppunk = NULL;
if (g_dwWinListCFRegister)
return CO_E_OBJISREG;
CShellWindowListCF *pswWinList = new CShellWindowListCF;
if (pswWinList)
{
pswWinList->Init(); // tell it to initialize
*ppunk = SAFECAST(pswWinList, IUnknown *);
return S_OK;
}
return E_OUTOFMEMORY;
}
BOOL WinList_Init(void)
{
// Create our clas factory to register out there...
TraceMsg(DM_WINLIST, "WinList_Init called");
//
// If this is not a browser-only install. Register the class factory
// object now with no instance. Otherwise, do it when the first instance
// is created (see shbrowse.cpp).
//
if (!g_fBrowserOnlyProcess)
{
//
// First, register the class factory object for CLSID_InternetExplorer.
// Note that we pass NULL indicating that subsequent CreateInstance
// should simply create a new instance.
//
IEInitializeClassFactoryObject(NULL);
CShellWindowListCF *pswWinList = new CShellWindowListCF;
if (pswWinList)
{
BOOL fRetVal = pswWinList->Init(); // tell it to initialize
pswWinList->Release(); // Release our handle hopefully init registered
//
// Initialize IEDDE.
//
if (!IsBrowseNewProcessAndExplorer())
IEDDE_Initialize();
return fRetVal;
}
}
else
{
//
// Initialize IEDDE. - Done before cocreate below for timing issues
//
IEDDE_Initialize();
// All of the main processing moved to first call to WinList_GetShellWindows
// as the creating of OLE object across processes messed up DDE timing.
return TRUE;
}
return FALSE;
}
#ifdef UNIX
HRESULT CoCreateShellWindows(REFCLSID rclsid, IUnknown *pUnkOuter, DWORD dwClsContext, REFIID riid, void **ppv)
{
HRESULT hr;
if (!g_pswWinList)
{
hr = CSDWindows_CreateInstance(&g_pswWinList);
if (FAILED(hr))
{
return E_FAIL;
}
}
hr = g_pswWinList->QueryInterface(riid, ppv);
if (SUCCEEDED(hr))
{
g_dwWinListCFRegister = 1;
}
return hr;
}
#endif
// Helper function to get the ShellWindows Object
IShellWindows* WinList_GetShellWindows(BOOL fForceMarshalled)
{
IShellWindows *psw;
if (fForceMarshalled)
psw = NULL;
else
psw = g_pswWinList;
if (psw)
{
// Optimize the inter-thread case by using the global WinList,
// this makes opening folders much faster.
psw->AddRef();
}
else
{
SHCheckRegistry();
#ifndef NO_RPCSS_ON_UNIX
HRESULT hr = CoCreateInstance(CLSID_ShellWindows, NULL,
CLSCTX_LOCAL_SERVER | CLSCTX_INPROC_SERVER,
IID_PPV_ARG(IShellWindows, &psw));
#else
HRESULT hr = CoCreateShellWindows(CLSID_ShellWindows, NULL,
CLSCTX_LOCAL_SERVER | CLSCTX_INPROC_SERVER,
IID_PPV_ARG(IShellWindows, &psw));
#endif
if ( (g_fBrowserOnlyProcess || !IsInternetExplorerApp()) && !g_fWinListRegistered)
{
// If it failed and we are not funning in integrated mode, and this is the
// first time for this process, we should then register the Window List with
// the shell process. We moved that from WinList_Init as that caused us to
// do interprocess send/post messages to early which caused DDE to break...
g_fWinListRegistered = TRUE; // only call once
if (FAILED(hr))
{
SHLoadInProc(CLSID_WinListShellProc);
hr = CoCreateInstance(CLSID_ShellWindows, NULL,
CLSCTX_LOCAL_SERVER | CLSCTX_INPROC_SERVER,
IID_PPV_ARG(IShellWindows, &psw));
}
if (psw)
{
psw->ProcessAttachDetach(TRUE);
}
}
// hr == REGDB_E_CLASSNOTREG when the shell process isn't running.
// hr == RPC_E_CANTCALLOUT_ININPUTSYNCCALL happens durring DDE launch of IE.
// should investigate, but removing assert for IE5 ship.
if (!(SUCCEEDED(hr) || hr == REGDB_E_CLASSNOTREG || hr == RPC_E_CANTCALLOUT_ININPUTSYNCCALL))
{
TraceMsg(TF_WARNING,
"WinList_GetShellWindows CoCreateInst(CLSID_ShellWindows) failed %x", hr);
}
}
return psw;
}
// Function to terminate our use of the window list.
void WinList_Terminate(void)
{
// Lets release everything in a thread safe way...
TraceMsg(DM_WINLIST, "WinList_Terminate called");
IEDDE_Uninitialize();
// Release our usage of the object to allow the system to clean it up
if (!g_fBrowserOnlyProcess)
{
// this is the explorer process, and we control the vertical
if (g_dwWinListCFRegister) {
IShellWindows* psw = WinList_GetShellWindows(FALSE);
if (psw)
{
#ifdef DEBUG
long cwindow = -1;
psw->get_Count(&cwindow);
//ASSERT(cwindow==0);
if (cwindow != 0)
TraceMsg(DM_ERROR, "wl_t: cwindow=%d (!=0)", cwindow);
#endif
psw->ProcessAttachDetach(FALSE);
psw->Release();
}
// the processattachdetach() should kill the CF in our process
if (g_dwWinListCFRegister != 0)
TraceMsg(DM_ERROR, "wl_t: g_dwWinListCFRegister=%d (!=0)", g_dwWinListCFRegister);
}
IERevokeClassFactoryObject();
CUrlHistory_CleanUp();
}
else
{
if (g_fWinListRegistered)
{
// only do this if we actually registered...
IShellWindows* psw = WinList_GetShellWindows(TRUE);
if (psw)
{
psw->ProcessAttachDetach(FALSE); // Tell it we are going away...
psw->Release();
}
}
}
#ifdef UNIX
if (g_pswWinList)
{
g_pswWinList->Release();
}
#endif
}
// chrisfra 10/17/96 - mike schmidt needs to look at why delayed
// register causes death in OleUnitialize accessing freed vtable
// in winlist, under ifdef, i've also made WinList_GetShellWindows ignore
// BOOLEAN parameter
STDAPI WinList_Revoke(long dwRegister)
{
#ifndef DELAY_REGISTER
IShellWindows* psw = WinList_GetShellWindows(TRUE);
#else
IShellWindows* psw = WinList_GetShellWindows(FALSE);
#endif
HRESULT hr = E_FAIL;
TraceMsg(DM_WINLIST, "WinList_Reevoke called on %x", dwRegister);
if (psw)
{
hr = psw->Revoke((long)dwRegister);
if (FAILED(hr))
{
TraceMsg(TF_WARNING,
"WinList_Revoke(%x) failed. hresult = %x", dwRegister, hr);
}
psw->Release();
}
return hr;
}
STDAPI WinList_NotifyNewLocation(IShellWindows* psw, long dwRegister, LPCITEMIDLIST pidl)
{
HRESULT hr = E_UNEXPECTED;
if (pidl)
{
VARIANT var;
hr = InitVariantFromIDList(&var, pidl);
if (SUCCEEDED(hr))
{
hr = psw->OnNavigate(dwRegister, &var);
VariantClearLazy(&var);
}
}
return hr;
}
// Register with the window list that we have a pidl that we are starting up.
STDAPI WinList_RegisterPending(DWORD dwThread, LPCITEMIDLIST pidl, LPCITEMIDLIST pidlRoot, long *pdwRegister)
{
HRESULT hr = E_UNEXPECTED;
ASSERT(!pidlRoot);
if (pidl)
{
IShellWindows* psw = WinList_GetShellWindows(FALSE);
if (psw)
{
VARIANT var;
hr = InitVariantFromIDList(&var, pidl);
if (SUCCEEDED(hr))
{
hr = psw->RegisterPending(dwThread, &var, PVAREMPTY, SWC_BROWSER, pdwRegister);
VariantClearLazy(&var);
}
}
}
return hr;
}
/*
* PERFORMANCE note - getting back the automation object (ppauto) is really
* expensive due to the marshalling overhead. Don't query for it unless you
* absolutely need it!
*/
STDAPI WinList_FindFolderWindow(LPCITEMIDLIST pidl, LPCITEMIDLIST pidlRoot, HWND *phwnd, IWebBrowserApp **ppauto)
{
HRESULT hr = E_UNEXPECTED;
ASSERT(!pidlRoot);
if (ppauto)
*ppauto = NULL;
if (phwnd)
*phwnd = NULL;
if (pidl)
{
// Try a cached psw if we don't need ppauto
IShellWindows* psw = WinList_GetShellWindows(ppauto != NULL);
if (psw)
{
VARIANT var;
hr = InitVariantFromIDList(&var, pidl);
if (SUCCEEDED(hr))
{
IDispatch* pdisp = NULL;
hr = psw->FindWindowSW(&var, PVAREMPTY, SWC_BROWSER, (long *)phwnd,
ppauto ? (SWFO_NEEDDISPATCH | SWFO_INCLUDEPENDING) : SWFO_INCLUDEPENDING,
&pdisp);
if (pdisp)
{
// if this fails it's because we are inside SendMessage loop and ole doesn't like it
if (ppauto)
hr = pdisp->QueryInterface(IID_PPV_ARG(IWebBrowserApp, ppauto));
pdisp->Release();
}
VariantClearLazy(&var);
}
psw->Release();
}
}
return hr;
}
// Support for Being able to open a folder and get it's idispatch...
//
class CWaitForWindow
{
public:
ULONG AddRef(void);
ULONG Release(void);
BOOL Init(IShellWindows *psw, LPCITEMIDLIST pidl, DWORD dwPending);
void CleanUp(void);
HRESULT WaitForWindowToOpen(DWORD dwTimeout);
CWaitForWindow(void);
private:
~CWaitForWindow(void);
// internal class to watch for events...
class CWindowEvents : public DShellWindowsEvents
{
public:
// IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void ** ppvObj);
STDMETHODIMP_(ULONG) AddRef(void) ;
STDMETHODIMP_(ULONG) Release(void);
// IDispatch
STDMETHOD(GetTypeInfoCount)(THIS_ UINT * pctinfo);
STDMETHOD(GetTypeInfo)(THIS_ UINT itinfo, LCID lcid, ITypeInfo * * pptinfo);
STDMETHOD(GetIDsOfNames)(THIS_ REFIID riid, OLECHAR * * rgszNames,
UINT cNames, LCID lcid, DISPID * rgdispid);
STDMETHOD(Invoke)(THIS_ DISPID dispidMember, REFIID riid,
LCID lcid, WORD wFlags, DISPPARAMS * pdispparams, VARIANT * pvarResult,
EXCEPINFO * pexcepinfo, UINT * puArgErr);
} m_EventHandler;
friend class CWindowEvents;
LONG m_cRef;
DWORD m_dwCookie;
IShellWindows *m_psw;
IConnectionPoint *m_picp;
DWORD m_dwPending;
LPITEMIDLIST m_pidl;
HANDLE m_hevent;
BOOL m_fAdvised;
};
ULONG CWaitForWindow::AddRef(void)
{
return InterlockedIncrement(&m_cRef);
}
ULONG CWaitForWindow::Release(void)
{
if (InterlockedDecrement(&m_cRef))
return m_cRef;
delete this;
return 0;
}
CWaitForWindow::CWaitForWindow(void) : m_cRef(1)
{
ASSERT(m_psw == NULL);
ASSERT(m_picp == NULL);
ASSERT(m_hevent == NULL);
ASSERT(m_dwCookie == 0);
ASSERT(m_fAdvised == FALSE);
}
CWaitForWindow::~CWaitForWindow(void)
{
ATOMICRELEASE(m_psw);
CleanUp();
if (m_hevent)
CloseHandle(m_hevent);
if (m_pidl)
ILFree(m_pidl);
}
BOOL CWaitForWindow::Init(IShellWindows *psw, LPCITEMIDLIST pidl, DWORD dwPending)
{
// First try to create an event object
m_hevent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!m_hevent)
return FALSE;
// We do not have a window or it is pending...
// first lets setup that we want to be notified of new windows.
if (FAILED(ConnectToConnectionPoint(SAFECAST(&m_EventHandler, IDispatch*), DIID_DShellWindowsEvents, TRUE, psw, &m_dwCookie, &m_picp)))
return FALSE;
// Save away passed in stuff that we care about.
m_psw = psw;
psw->AddRef();
m_pidl = ILClone(pidl);
m_dwPending = dwPending;
return TRUE;
}
void CWaitForWindow::CleanUp(void)
{
// Don't need to listen anmore.
if (m_dwCookie)
{
m_picp->Unadvise(m_dwCookie);
m_dwCookie = 0;
}
ATOMICRELEASE(m_picp);
}
HRESULT CWaitForWindow::WaitForWindowToOpen(DWORD dwTimeOut)
{
if (!m_hevent || !m_dwCookie)
return E_FAIL;
ENTERCRITICAL;
if (!m_fAdvised)
ResetEvent(m_hevent);
LEAVECRITICAL;
DWORD dwStart = GetTickCount();
DWORD dwWait = dwTimeOut;
DWORD dwWaitResult;
do
{
dwWaitResult = MsgWaitForMultipleObjects(1, &m_hevent, FALSE, // fWaitAll, wait for any one
dwWait, QS_ALLINPUT);
// Check if we are signaled for a send message.
if (dwWaitResult != WAIT_OBJECT_0 + 1)
{
break; // No. Break out of the loop.
}
// We may need to dispatch stuff here.
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// than MSEC_MAXWAIT if we wait more than that.
dwWait = dwStart+dwTimeOut - GetTickCount();
} while (dwWait <= dwTimeOut);
BOOL fAdvised;
{
ENTERCRITICAL;
fAdvised = m_fAdvised;
m_fAdvised = FALSE;
LEAVECRITICAL;
}
return fAdvised ? S_OK : E_FAIL;
}
STDMETHODIMP CWaitForWindow::CWindowEvents::QueryInterface(REFIID riid, void **ppv)
{
static const QITAB qit[] = {
QITABENTMULTI2(CWaitForWindow::CWindowEvents, DIID_DShellWindowsEvents, DShellWindowsEvents),
QITABENTMULTI(CWaitForWindow::CWindowEvents, IDispatch, DShellWindowsEvents),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
ULONG CWaitForWindow::CWindowEvents::AddRef(void)
{
CWaitForWindow* pdfwait = IToClass(CWaitForWindow, m_EventHandler, this);
return pdfwait->AddRef();
}
ULONG CWaitForWindow::CWindowEvents::Release(void)
{
CWaitForWindow* pdfwait = IToClass(CWaitForWindow, m_EventHandler, this);
return pdfwait->Release();
}
HRESULT CWaitForWindow::CWindowEvents::GetTypeInfoCount(UINT *pctinfo)
{
return E_NOTIMPL;
}
HRESULT CWaitForWindow::CWindowEvents::GetTypeInfo(UINT itinfo, LCID lcid, ITypeInfo **pptinfo)
{
return E_NOTIMPL;
}
HRESULT CWaitForWindow::CWindowEvents::GetIDsOfNames(REFIID riid, OLECHAR **rgszNames, UINT cNames, LCID lcid, DISPID *rgdispid)
{
return E_NOTIMPL;
}
HRESULT CWaitForWindow::CWindowEvents::Invoke(DISPID dispid, REFIID riid,
LCID lcid, WORD wFlags, DISPPARAMS * pdispparams, VARIANT * pvarResult,
EXCEPINFO * pexcepinfo, UINT * puArgErr)
{
CWaitForWindow* pdfwait = IToClass(CWaitForWindow, m_EventHandler, this);
if (dispid == DISPID_WINDOWREGISTERED)
{
ENTERCRITICAL;
// Signal the event
pdfwait->m_fAdvised = TRUE;
::SetEvent(pdfwait->m_hevent);
LEAVECRITICAL;
}
return S_OK;
}
// WARNING:: this assumes not rooted
STDAPI SHGetIDispatchForFolder(LPCITEMIDLIST pidl, IWebBrowserApp **ppauto)
{
HRESULT hr = E_UNEXPECTED;
if (ppauto)
*ppauto = NULL;
if (!pidl)
return E_POINTER;
// Try a cached psw if we don't need ppauto
IShellWindows* psw = WinList_GetShellWindows(ppauto != NULL);
if (psw)
{
VARIANT var;
hr = InitVariantFromIDList(&var, pidl);
if (SUCCEEDED(hr))
{
LONG lhwnd;
IDispatch* pdisp;
hr = psw->FindWindowSW(&var, PVAREMPTY, SWC_BROWSER, &lhwnd,
ppauto ? (SWFO_NEEDDISPATCH | SWFO_INCLUDEPENDING) : SWFO_INCLUDEPENDING,
&pdisp);
if ((hr == E_PENDING) || (hr == S_FALSE))
{
HRESULT hrOld = hr;
hr = E_FAIL;
CWaitForWindow *pdfwait = new CWaitForWindow(); // Setup a wait object...
if (pdfwait)
{
if (pdfwait->Init(psw, pidl, 0))
{
if (hrOld == S_FALSE)
{
// Startup opening a new window
SHELLEXECUTEINFO sei = {sizeof(sei)};
sei.lpIDList = (void *)pidl;
//
// WARNING - old versions of ShellExec() didnt pay attention - ZekeL - 30-DEC-98
// to whether the hwnd is in the same process or not,
// and so could fault in TryDDEShortcut().
// only pass the hwnd if the shell window shares
// the same process.
//
sei.hwnd = GetShellWindow();
DWORD idProcess;
GetWindowThreadProcessId(sei.hwnd, &idProcess);
if (idProcess != GetCurrentProcessId())
sei.hwnd = NULL;
// Everything should have been initialize to NULL(0)
sei.fMask = SEE_MASK_IDLIST | SEE_MASK_FLAG_DDEWAIT;
sei.nShow = SW_SHOWNORMAL;
hr = ShellExecuteEx(&sei) ? S_OK : S_FALSE;
}
while ((hr = psw->FindWindowSW(&var, PVAREMPTY, SWC_BROWSER, &lhwnd,
ppauto ? (SWFO_NEEDDISPATCH | SWFO_INCLUDEPENDING) : SWFO_INCLUDEPENDING,
&pdisp)) != S_OK)
{
if (FAILED(pdfwait->WaitForWindowToOpen(20 * 1000)))
{
hr = E_ABORT;
break;
}
}
}
pdfwait->CleanUp(); // No need to watch things any more...
pdfwait->Release(); // release our use of this object...
}
}
if (hr == S_OK && ppauto)
{
// if this fails this is because we are inside SendMessage loop
hr = pdisp->QueryInterface(IID_PPV_ARG(IWebBrowserApp, ppauto));
}
if (pdisp)
pdisp->Release();
VariantClear(&var);
}
psw->Release();
}
return hr;
}
#undef VariantCopy
WINOLEAUTAPI VariantCopyLazy(VARIANTARG * pvargDest, VARIANTARG * pvargSrc)
{
VariantClearLazy(pvargDest);
switch(pvargSrc->vt) {
case VT_I4:
case VT_UI4:
case VT_BOOL:
// we can add more
*pvargDest = *pvargSrc;
return S_OK;
case VT_UNKNOWN:
if (pvargDest) {
*pvargDest = *pvargSrc;
if (pvargDest->punkVal)
pvargDest->punkVal->AddRef();
return S_OK;
}
ASSERT(0);
return E_INVALIDARG;
}
return VariantCopy(pvargDest, pvargSrc);
}
//
// WARNING: This function must be placed at the end because we #undef
// VariantClear
//
#undef VariantClear
HRESULT VariantClearLazy(VARIANTARG *pvarg)
{
switch(pvarg->vt)
{
case VT_I4:
case VT_UI4:
case VT_EMPTY:
case VT_BOOL:
// No operation
break;
case VT_UNKNOWN:
if(V_UNKNOWN(pvarg) != NULL)
V_UNKNOWN(pvarg)->Release();
break;
case VT_DISPATCH:
if(V_DISPATCH(pvarg) != NULL)
V_DISPATCH(pvarg)->Release();
break;
case VT_SAFEARRAY:
THR(SafeArrayDestroy(V_ARRAY(pvarg)));
break;
default:
return VariantClear(pvarg);
}
V_VT(pvarg) = VT_EMPTY;
return S_OK;
}