//+------------------------------------------------------------------------ // // Microsoft Forms // Copyright (C) Microsoft Corporation, 1996 // // File: bsauto.cxx // //------------------------------------------------------------------------- #include "headers.hxx" #include #undef ASSERT DeclareTag(tagSync, "MTScript", "Trace Thread Sync events"); DeclareTag(tagLock, "MTScript", "Trace Thread Lock events"); ExternTag(tagProcess); AutoCriticalSection CScriptHost::s_csSync; CStackDataAry CScriptHost::s_arySyncEvents; CScriptHost::THREADLOCK CScriptHost::s_aThreadLocks[MAX_LOCKS]; UINT CScriptHost::s_cThreadLocks = 0; static const wchar_t *g_pszListDeliminator = L";,"; static wchar_t *g_pszAtomicSyncLock = L"g_pszAtomicSyncLock"; //--------------------------------------------------------------------------- // // Member: CScriptHost::GetTypeInfo, IDispatch // //--------------------------------------------------------------------------- HRESULT CScriptHost::GetTypeInfo(UINT itinfo, ULONG lcid, ITypeInfo ** pptinfo) { VERIFY_THREAD(); HRESULT hr; hr = LoadTypeLibrary(); if (hr) goto Cleanup; *pptinfo = _pTypeInfoIGlobalMTScript; (*pptinfo)->AddRef(); Cleanup: return hr; } //--------------------------------------------------------------------------- // // Member: CScriptHost::GetTypeInfoCount, IDispatch // //--------------------------------------------------------------------------- HRESULT CScriptHost::GetTypeInfoCount(UINT * pctinfo) { VERIFY_THREAD(); *pctinfo = 1; return S_OK; } //--------------------------------------------------------------------------- // // Member: CScriptHost::GetIDsOfNames, IDispatch // //--------------------------------------------------------------------------- HRESULT CScriptHost::GetIDsOfNames(REFIID riid, LPOLESTR * rgszNames, UINT cNames, LCID lcid, DISPID * rgdispid) { VERIFY_THREAD(); HRESULT hr; hr = THR(LoadTypeLibrary()); if (hr) goto Cleanup; hr = _pTypeInfoIGlobalMTScript->GetIDsOfNames(rgszNames, cNames, rgdispid); Cleanup: return hr; } //--------------------------------------------------------------------------- // // Member: CScriptHost::Invoke, IDispatch // //--------------------------------------------------------------------------- HRESULT CScriptHost::Invoke(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags,DISPPARAMS * pdispparams, VARIANT * pvarResult,EXCEPINFO * pexcepinfo, UINT * puArgErr) { VERIFY_THREAD(); HRESULT hr; hr = LoadTypeLibrary(); if (hr) goto Cleanup; hr = _pTypeInfoIGlobalMTScript->Invoke((IGlobalMTScript *)this, dispidMember, wFlags, pdispparams, pvarResult, pexcepinfo, puArgErr); Cleanup: return hr; } //*************************************************************************** // // IGlobalMTScript implementation // //*************************************************************************** HRESULT CScriptHost::get_PublicData(VARIANT * pvData) { VERIFY_THREAD(); HRESULT hr = S_OK; // NOTE: We assume that the output parameter pvData is an empty or // uninitialized VARIANT. This should be safe because it is defined as // the return value for this method to the scripting engines and is // a pure [out] parameter. VariantInit(pvData); // Check to see if the data has changed since we last got it. // _dwPublicSerialNum is a DWORD so we are guaranteed an atomic read. if (_pMT->_dwPublicSerialNum != _dwPublicSN) { LOCK_LOCALS(_pMT); VariantClear(&_vPubCache); // If the data is an IDispatch pointer (how the scripting engines // implement most objects) we must get a marshalled copy to this thread. // Otherwise we can just copy the data into the return value. if (V_VT(&_pMT->_vPublicData) == VT_DISPATCH) { IDispatch *pDisp; Assert(_pMT->_dwPublicDataCookie != 0); hr = _pMT->_pGIT->GetInterfaceFromGlobal(_pMT->_dwPublicDataCookie, IID_IDispatch, (LPVOID*)&pDisp); if (!hr) { V_VT(&_vPubCache) = VT_DISPATCH; V_DISPATCH(&_vPubCache) = pDisp; } } else { hr = VariantCopy(&_vPubCache, &_pMT->_vPublicData); } _dwPublicSN = _pMT->_dwPublicSerialNum; } if (!hr) { hr = VariantCopy(pvData, &_vPubCache); } return hr; } HRESULT CScriptHost::put_PublicData(VARIANT vData) { VERIFY_THREAD(); HRESULT hr = S_OK; // Check for data types which we don't support. if ( V_ISBYREF(&vData) || V_ISARRAY(&vData) || V_ISVECTOR(&vData) || V_VT(&vData) == VT_UNKNOWN) { return E_INVALIDARG; } LOCK_LOCALS(_pMT); // If the previous data is an IDispatch pointer revoke it from the // GlobalInterfaceTable. if (V_VT(&_pMT->_vPublicData) == VT_DISPATCH) { Assert(_pMT->_dwPublicDataCookie != 0); hr = _pMT->_pGIT->RevokeInterfaceFromGlobal(_pMT->_dwPublicDataCookie); AssertSz(!hr, "Unexpected failure revoking itf from GIT"); _pMT->_dwPublicDataCookie = 0; } // If the new data is an IDispatch pointer then we must register it. if (V_VT(&vData) == VT_DISPATCH) { Assert(_pMT->_dwPublicDataCookie == 0); hr = _pMT->_pGIT->RegisterInterfaceInGlobal(V_DISPATCH(&vData), IID_IDispatch, &_pMT->_dwPublicDataCookie); AssertSz(!hr, "Unexpected failure registering itf in GIT"); } // Update the global copy of the data. //$ FUTURE: This can be optimized to reduce memory usage (by not making // a copy of a string in every thread, for example). _pMT->_dwPublicSerialNum++; hr = VariantCopy(&_pMT->_vPublicData, &vData); if (!hr) { // Even if it's an IDispatch, we don't need to marshal it for // ourselves because we're running in the same thread as the script // engine that gave it to us. hr = VariantCopy(&_vPubCache, &vData); _dwPublicSN = _pMT->_dwPublicSerialNum; } return S_OK; } HRESULT CScriptHost::get_PrivateData(VARIANT * pvData) { VERIFY_THREAD(); HRESULT hr = S_OK; // NOTE: We assume that the output parameter pvData is an empty or // uninitialized VARIANT. This should be safe because it is defined as // the return value for this method to the scripting engines and is // a pure [out] parameter. VariantInit(pvData); // Check to see if the data has changed since we last got it. // _dwPrivateSerialNum is a DWORD so we are guaranteed an atomic read. if (_pMT->_dwPrivateSerialNum != _dwPrivateSN) { LOCK_LOCALS(_pMT); VariantClear(&_vPrivCache); // If the data is an IDispatch pointer (how the scripting engines // implement most objects) we must get a marshalled copy to this thread. // Otherwise we can just copy the data into the return value. if (V_VT(&_pMT->_vPrivateData) == VT_DISPATCH) { IDispatch *pDisp; Assert(_pMT->_dwPrivateDataCookie != 0); hr = _pMT->_pGIT->GetInterfaceFromGlobal(_pMT->_dwPrivateDataCookie, IID_IDispatch, (LPVOID*)&pDisp); if (!hr) { V_VT(&_vPrivCache) = VT_DISPATCH; V_DISPATCH(&_vPrivCache) = pDisp; } } else { hr = VariantCopy(&_vPrivCache, &_pMT->_vPrivateData); } _dwPrivateSN = _pMT->_dwPrivateSerialNum; } if (!hr) { hr = VariantCopy(pvData, &_vPrivCache); } return hr; } HRESULT CScriptHost::put_PrivateData(VARIANT vData) { VERIFY_THREAD(); HRESULT hr = S_OK; // Check for data types which we don't support. if ( V_ISBYREF(&vData) || V_ISARRAY(&vData) || V_ISVECTOR(&vData) || V_VT(&vData) == VT_UNKNOWN) { return E_INVALIDARG; } LOCK_LOCALS(_pMT); // If the previous data is an IDispatch pointer revoke it from the // GlobalInterfaceTable. if (V_VT(&_pMT->_vPrivateData) == VT_DISPATCH) { Assert(_pMT->_dwPrivateDataCookie != 0); hr = _pMT->_pGIT->RevokeInterfaceFromGlobal(_pMT->_dwPrivateDataCookie); AssertSz(!hr, "Unexpected failure revoking itf from GIT"); _pMT->_dwPrivateDataCookie = 0; } // If the new data is an IDispatch pointer then we must register it. if (V_VT(&vData) == VT_DISPATCH) { Assert(_pMT->_dwPrivateDataCookie == 0); hr = _pMT->_pGIT->RegisterInterfaceInGlobal(V_DISPATCH(&vData), IID_IDispatch, &_pMT->_dwPrivateDataCookie); AssertSz(!hr, "Unexpected failure registering itf in GIT"); } // Update the global copy of the data. //$ FUTURE: This can be optimized to reduce memory usage (by not making // a copy of a string in every thread, for example). _pMT->_dwPrivateSerialNum++; hr = VariantCopy(&_pMT->_vPrivateData, &vData); if (!hr) { // Even if it's an IDispatch, we don't need to marshal it for // ourselves because we're running in the same thread as the script // engine that gave it to us. hr = VariantCopy(&_vPrivCache, &vData); _dwPrivateSN = _pMT->_dwPrivateSerialNum; } return S_OK; } HRESULT CScriptHost::ExitProcess() { VERIFY_THREAD(); PostToThread(_pMT, MD_PLEASEEXIT); return S_OK; } HRESULT CScriptHost::Restart() { VERIFY_THREAD(); PostToThread(_pMT, MD_RESTART); return S_OK; } HRESULT CScriptHost::get_LocalMachine(BSTR *pbstrName) { TCHAR achCompName[MAX_COMPUTERNAME_LENGTH+1]; DWORD dwLen = MAX_COMPUTERNAME_LENGTH+1; VERIFY_THREAD(); if (!pbstrName) return E_POINTER; GetComputerName(achCompName, &dwLen); achCompName[dwLen] = '\0'; *pbstrName = SysAllocString(achCompName); if (!*pbstrName) return E_OUTOFMEMORY; return S_OK; } HRESULT CScriptHost::Include(BSTR bstrPath) { VERIFY_THREAD(); HRESULT hr; if(!bstrPath) return E_INVALIDARG; if (!_fIsPrimaryScript) MessageEventPump(FALSE); //$ TODO: Define a new named item context for the included file for better // debugging. hr = THR(GetSite()->ExecuteScriptFile(bstrPath)); return hr; } HRESULT CScriptHost::CallScript(BSTR bstrPath, VARIANT *pvarScriptParam) { VERIFY_THREAD(); HRESULT hr; if(!bstrPath) return E_INVALIDARG; if (!_fIsPrimaryScript) MessageEventPump(FALSE); hr = THR(PushScript(_tcsrchr(bstrPath, _T('.')))); if(hr) goto Cleanup; if (pvarScriptParam && pvarScriptParam->vt != VT_ERROR) { hr = THR(VariantCopy(&GetSite()->_varParam, pvarScriptParam)); if (hr) goto Cleanup; } hr = THR(GetSite()->ExecuteScriptFile(bstrPath)); hr = THR(GetSite()->SetScriptState(SCRIPTSTATE_CONNECTED)); if (hr) goto Cleanup; FireEvent(DISPID_MTScript_ScriptMain, 0, NULL); PopScript(); Cleanup: return hr; } HRESULT CScriptHost::SpawnScript(BSTR bstrPath, VARIANT *pvarScriptParam) { VERIFY_THREAD(); HRESULT hr = S_OK; VARIANT *pvarParam = NULL; DWORD dwCookie = 0; BOOL fRegistered = false; // Check for data types which we don't support. if ( !bstrPath || SysStringLen(bstrPath) == 0 || V_ISBYREF(pvarScriptParam) || V_ISARRAY(pvarScriptParam) || V_ISVECTOR(pvarScriptParam) || V_VT(pvarScriptParam) == VT_UNKNOWN) { return E_INVALIDARG; } if (!_fIsPrimaryScript) MessageEventPump(FALSE); if (pvarScriptParam && pvarScriptParam->vt != VT_ERROR) { pvarParam = new VARIANT; if (!pvarParam) return E_OUTOFMEMORY; VariantInit(pvarParam); if (V_VT(pvarScriptParam) == VT_DISPATCH) { // Stick the pointer in the GlobalInterfaceTable, so the other // thread can pull it out safely. hr = _pMT->_pGIT->RegisterInterfaceInGlobal(V_DISPATCH(pvarScriptParam), IID_IDispatch, &dwCookie); if (hr) goto Cleanup; // Stick the cookie in the variant we hand to the other thread. V_VT(pvarParam) = VT_DISPATCH; V_I4(pvarParam) = dwCookie; fRegistered = true; } else { hr = THR(VariantCopy(pvarParam, pvarScriptParam)); if (hr) goto Cleanup; } } hr = _pMT->RunScript(bstrPath, pvarParam); Cleanup: if (pvarParam) { if (fRegistered) { Verify(_pMT->_pGIT->RevokeInterfaceFromGlobal(dwCookie) == S_OK); } if (V_VT(pvarParam) != VT_DISPATCH) VariantClear(pvarParam); delete pvarParam; } return hr; } HRESULT CScriptHost::get_ScriptParam(VARIANT *pvarScriptParam) { VERIFY_THREAD(); HRESULT hr; if (!_fIsPrimaryScript) MessageEventPump(FALSE); if (GetSite()) { hr = THR(VariantCopy(pvarScriptParam, &GetSite()->_varParam)); } else { hr = E_FAIL; } return hr; } HRESULT CScriptHost::get_ScriptPath(BSTR *pbstrPath) { CStr cstrPath; VERIFY_THREAD(); if (!_fIsPrimaryScript) MessageEventPump(FALSE); _pMT->_options.GetScriptPath(&cstrPath); return cstrPath.AllocBSTR(pbstrPath); } typedef HRESULT (TestExternal_Func)(VARIANT *pParam, long *plRetVal); HRESULT CScriptHost::CallExternal(BSTR bstrDLLName, BSTR bstrFunctionName, VARIANT *pParam, long * plRetVal) { VERIFY_THREAD(); HRESULT hr = S_OK; HINSTANCE hInstDLL = NULL; TestExternal_Func *pfn = NULL; if (!_fIsPrimaryScript) MessageEventPump(FALSE); if (!plRetVal || !bstrDLLName || !bstrFunctionName) return E_POINTER; *plRetVal = -1; hInstDLL = LoadLibrary(bstrDLLName); if (NULL == hInstDLL) { return S_FALSE; // Can't return error codes or the script will abort } int cchLen = SysStringLen(bstrFunctionName); char *pchBuf = new char[cchLen+1]; if (pchBuf) { WideCharToMultiByte(CP_ACP, 0, bstrFunctionName, cchLen, pchBuf, cchLen+1, NULL, NULL); pchBuf[cchLen] = '\0'; pfn = (TestExternal_Func *)GetProcAddress(hInstDLL, pchBuf); delete pchBuf; } if (NULL == pfn) { hr = S_FALSE; } else { *plRetVal = 0; hr = (*pfn)(pParam, plRetVal); } FreeLibrary(hInstDLL); return hr; } HRESULT CScriptHost::GetSyncEventName(int nEvent, CStr *pCStr, HANDLE *phEvent) { HRESULT hr = S_OK; *phEvent = NULL; if (nEvent < 0) return E_INVALIDARG; EnterCriticalSection(&s_csSync); if (nEvent >= s_arySyncEvents.Size()) { hr = E_INVALIDARG; goto Cleanup; } pCStr->Set(s_arySyncEvents[nEvent]._cstrName); *phEvent = s_arySyncEvents[nEvent]._hEvent; Cleanup: LeaveCriticalSection(&s_csSync); RRETURN(hr); } HRESULT CScriptHost::GetSyncEvent(LPCTSTR pszName, HANDLE *phEvent) { int i; SYNCEVENT *pse; HRESULT hr = S_OK; *phEvent = NULL; if (_tcslen(pszName) < 1) return E_INVALIDARG; EnterCriticalSection(&s_csSync); for (i = s_arySyncEvents.Size(), pse = s_arySyncEvents; i > 0; i--, pse++) { if (_tcsicmp(pszName, pse->_cstrName) == 0) { *phEvent = pse->_hEvent; break; } } if (i == 0) { // // The event doesn't exist yet. Create one. The primary script thread // owns cleaning all this stuff up. // SYNCEVENT se; se._hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (!se._hEvent) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Cleanup; } else { se._cstrName.Set(pszName); s_arySyncEvents.AppendIndirect(&se); // // The cstr in 'se' will destroy its memory unless we take it away. // It's now owned by the cstrName member of the array. // (void)se._cstrName.TakePch(); *phEvent = se._hEvent; } } Cleanup: LeaveCriticalSection(&s_csSync); RRETURN(hr); } HRESULT CScriptHost::StringToEventArray(const wchar_t *pszNameList, CStackPtrAry *pAryEvents) { HRESULT hr = S_OK; if (wcspbrk(pszNameList, g_pszListDeliminator)) { CStr cstrNameList; HRESULT hr = cstrNameList.Set(pszNameList); wchar_t *pch = NULL; if (hr == S_OK) pch = wcstok(cstrNameList, g_pszListDeliminator); while (hr == S_OK && pch) { HANDLE hEvent; hr = THR(GetSyncEvent(pch, &hEvent)); if (hr != S_OK) break; // Don't allow duplicates. MsgWaitForMultipleObjects will barf. if (pAryEvents->Find(hEvent) != -1) { hr = E_INVALIDARG; break; } hr = pAryEvents->Append(hEvent); pch = wcstok(NULL, g_pszListDeliminator); } } else { HANDLE hEvent; hr = THR(GetSyncEvent(pszNameList, &hEvent)); if (hr == S_OK) hr = pAryEvents->Append(hEvent); } RRETURN(hr); } HRESULT CScriptHost::ResetSync(const BSTR bstrName) { VERIFY_THREAD(); HRESULT hr; if (!_fIsPrimaryScript) MessageEventPump(FALSE); CStackPtrAry aryEvents; hr = StringToEventArray(bstrName, &aryEvents); if (hr == S_OK) { for(int i = aryEvents.Size() - 1; i>= 0; --i) ResetEvent(aryEvents[i]); } return hr; } HRESULT CScriptHost::WaitForSync(BSTR bstrName, long nTimeout, VARIANT_BOOL *pfSignaled) { VERIFY_THREAD(); HANDLE hEvent; HRESULT hr; if (!pfSignaled) return E_POINTER; *pfSignaled = VB_TRUE; hr = THR(GetSyncEvent(bstrName, &hEvent)); if (hr) RRETURN(hr); TraceTag((tagSync, "Thread 0x%x is starting a wait for sync %ls", _dwThreadId, bstrName)); if (MessageEventPump(TRUE, 1, &hEvent, FALSE, (nTimeout > 0) ? nTimeout : INFINITE) != MEP_EVENT_0) { *pfSignaled = VB_FALSE; } // Now make sure that SignalThreadSync() and ResetSync() // are atomic when manipulating multiple syncs. TakeThreadLock(g_pszAtomicSyncLock); ReleaseThreadLock(g_pszAtomicSyncLock); TraceTag((tagSync, "Thread 0x%x has returned from a wait for sync %ls (signaled=%s)", _dwThreadId, bstrName, (*pfSignaled==VB_FALSE) ? "false" : "true")); return S_OK; } HRESULT CScriptHost::WaitForMultipleSyncs(const BSTR bstrNameList, VARIANT_BOOL fWaitForAll, long nTimeout, long *plSignal) { VERIFY_THREAD(); HRESULT hr; DWORD dwRet; *plSignal = 0; CStackPtrAry aryEvents; hr = StringToEventArray(bstrNameList, &aryEvents); if (hr == S_OK) { TraceTag((tagSync, "Thread 0x%x is starting a multiwait for sync %ls", _dwThreadId, bstrNameList)); dwRet = MessageEventPump(TRUE, aryEvents.Size(), aryEvents, (fWaitForAll == VB_TRUE) ? TRUE : FALSE, (nTimeout > 0) ? nTimeout : INFINITE); if (dwRet >= MEP_EVENT_0) { *plSignal = dwRet - MEP_EVENT_0 + 1; // result is 1-based, not zero-based // Now make sure that SignalThreadSync() and ResetSync() // are atomic when manipulating multiple syncs. TakeThreadLock(g_pszAtomicSyncLock); ReleaseThreadLock(g_pszAtomicSyncLock); } TraceTag((tagSync, "Thread 0x%x has returned from a multiwait for sync %ls (signaled=%d)", _dwThreadId, bstrNameList, *plSignal)); } return hr; } HRESULT CScriptHost::SignalThreadSync(BSTR bstrName) { HRESULT hr; if (!_fIsPrimaryScript) MessageEventPump(FALSE); TraceTag((tagSync, "Thread 0x%x is signalling sync %ls", _dwThreadId, bstrName)); CStackPtrAry aryEvents; hr = StringToEventArray(bstrName, &aryEvents); if (hr == S_OK) { if (aryEvents.Size() > 1) TakeThreadLock(g_pszAtomicSyncLock); for(int i = aryEvents.Size() - 1; i>= 0; --i) SetEvent(aryEvents[i]); if (aryEvents.Size() > 1) ReleaseThreadLock(g_pszAtomicSyncLock); } return S_OK; } HRESULT CScriptHost::GetLockCritSec(LPTSTR pszName, CRITICAL_SECTION **ppcs, DWORD **ppdwOwner) { int i; THREADLOCK *ptl; HRESULT hr = S_OK; if (_tcslen(pszName) < 1) return E_INVALIDARG; *ppcs = NULL; EnterCriticalSection(&s_csSync); for (i = s_cThreadLocks, ptl = s_aThreadLocks; i > 0; i--, ptl++) { if (_tcsicmp(pszName, ptl->_cstrName) == 0) { *ppcs = &ptl->_csLock; *ppdwOwner = &ptl->_dwOwner; break; } } if (i == 0) { // // The critical section doesn't exist yet. Create one. The primary // script thread owns cleaning all this stuff up. // if (s_cThreadLocks == MAX_LOCKS) { // BUGBUG -- SetErrorInfo hr = E_OUTOFMEMORY; goto Cleanup; } ptl = &s_aThreadLocks[s_cThreadLocks++]; InitializeCriticalSection(&ptl->_csLock); ptl->_cstrName.Set(pszName); ptl->_dwOwner = 0; *ppcs = &ptl->_csLock; *ppdwOwner = &ptl->_dwOwner; } Cleanup: LeaveCriticalSection(&s_csSync); RRETURN(hr); } HRESULT CScriptHost::TakeThreadLock(BSTR bstrName) { HRESULT hr; CRITICAL_SECTION *pcs; DWORD *pdwOwner; VERIFY_THREAD(); if (!_fIsPrimaryScript) MessageEventPump(FALSE); hr = THR(GetLockCritSec(bstrName, &pcs, &pdwOwner)); if (hr) RRETURN(hr); TraceTag((tagLock, "Thread 0x%x is trying to obtain lock %ls", _dwThreadId, bstrName)); while (!TryEnterCriticalSection(pcs)) { // Make sure we don't get hung here if the thread's trying to exit if (MessageEventPump(TRUE, 0, NULL, FALSE, 100, TRUE) == MEP_EXIT) return E_FAIL; } TraceTag((tagLock, "Thread 0x%x has obtained lock %ls", _dwThreadId, bstrName)); *pdwOwner = GetCurrentThreadId(); return S_OK; } HRESULT CScriptHost::ReleaseThreadLock(BSTR bstrName) { HRESULT hr; CRITICAL_SECTION *pcs; DWORD *pdwOwner; VERIFY_THREAD(); if (!_fIsPrimaryScript) MessageEventPump(FALSE); hr = THR(GetLockCritSec(bstrName, &pcs, &pdwOwner)); if (hr) RRETURN(hr); // LeaveCriticalSection can cause other threads to lock indefinitely on // the critical section if we don't actually own it. if (*pdwOwner != GetCurrentThreadId()) { return HRESULT_FROM_WIN32(ERROR_NOT_OWNER); } LeaveCriticalSection(pcs); TraceTag((tagLock, "Thread 0x%x has released lock %ls", _dwThreadId, bstrName)); return S_OK; } HRESULT CScriptHost::DoEvents() { VERIFY_THREAD(); MessageEventPump(FALSE); return S_OK; } HRESULT CScriptHost::MessageBoxTimeout(BSTR bstrMessage, // Message Text long cButtons, // Number of buttons (max 5) BSTR bstrButtonText, // Comma separated list of button text. Number must match cButtons long lTimeout, // Timeout in minutes. If zero then no timeout. long lEventInterval, // Fire a OnMessageBoxInterval event every lEventInterval minutes VARIANT_BOOL fCanCancel, // If TRUE then timeout can be canceled. VARIANT_BOOL fConfirm, // If TRUE then confirm the button pushed before returning. long *plSelected) // Returns button pushed. 0=timeout, 1=Button1, 2=Button2, etc. { VERIFY_THREAD(); HANDLE hEvent; MBTIMEOUT mbt = { 0 }; BOOL fExit = FALSE; HRESULT hr = S_OK; if (!plSelected) return E_POINTER; *plSelected = -1; if (cButtons < 1 || cButtons > 5) return E_INVALIDARG; hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (!hEvent) return HRESULT_FROM_WIN32(GetLastError()); mbt.bstrMessage = bstrMessage; mbt.cButtons = cButtons; mbt.bstrButtonText = bstrButtonText; mbt.lTimeout = lTimeout; mbt.lEventInterval = lEventInterval; mbt.fCanCancel = (fCanCancel == VB_TRUE) ? TRUE : FALSE; mbt.fConfirm = (fConfirm == VB_TRUE) ? TRUE : FALSE; mbt.hEvent = hEvent; CMessageBoxTimeout *pmbt = new CMessageBoxTimeout(&mbt); if (!pmbt) return E_OUTOFMEMORY; pmbt->StartThread(NULL); while (!fExit) { // Make sure it was our event being signaled that caused the loop to end if (MessageEventPump(TRUE, 1, &hEvent) != MEP_EVENT_0) { hr = S_FALSE; fExit = TRUE; break; } switch (mbt.mbts) { case MBTS_BUTTON1: case MBTS_BUTTON2: case MBTS_BUTTON3: case MBTS_BUTTON4: case MBTS_BUTTON5: case MBTS_TIMEOUT: *plSelected = (long)mbt.mbts; fExit = TRUE; break; case MBTS_INTERVAL: FireEvent(DISPID_MTScript_OnMessageBoxInterval, 0, NULL); ResetEvent(hEvent); break; case MBTS_ERROR: hr = E_FAIL; fExit = TRUE; break; default: AssertSz(FALSE, "FATAL: Invalid value for mbts!"); fExit = TRUE; break; } } if (pmbt->_hwnd != NULL) { EndDialog(pmbt->_hwnd, 0); } pmbt->Release(); CloseHandle(hEvent); return hr; } HRESULT CScriptHost::RunLocalCommand(BSTR bstrCommand, BSTR bstrDir, BSTR bstrTitle, VARIANT_BOOL fMinimize, VARIANT_BOOL fGetOutput, VARIANT_BOOL fWait, VARIANT_BOOL fNoCrashPopup, VARIANT_BOOL fNoEnviron, long * plErrorCode) { VERIFY_THREAD(); CProcessThread *pProc; PROCESS_PARAMS pp; if (!_fIsPrimaryScript) MessageEventPump(FALSE); if (!plErrorCode) return E_POINTER; pp.pszCommand = bstrCommand; pp.pszDir = bstrDir; pp.pszTitle = bstrTitle; pp.fMinimize = (fMinimize == VB_TRUE) ? TRUE : FALSE; pp.fGetOutput = (fGetOutput == VB_TRUE) ? TRUE : FALSE; pp.fNoCrashPopup = (fNoCrashPopup == VB_TRUE) ? TRUE : FALSE; pp.fNoEnviron = (fNoEnviron == VB_TRUE) ? TRUE : FALSE; _hrLastRunLocalError = S_OK; pProc = new CProcessThread(this); if (!pProc) { _hrLastRunLocalError = E_OUTOFMEMORY; *plErrorCode = 0; return S_FALSE; } _hrLastRunLocalError = pProc->StartThread(&pp); if (FAILED(_hrLastRunLocalError)) { *plErrorCode = 0; pProc->Release(); // Don't cause a script error by returning a failure code. return S_FALSE; } _pMT->AddProcess(pProc); if (fWait == VB_TRUE) { HANDLE hEvent = pProc->hThread(); // The actual return code here doesn't matter. We'll do the same thing // no matter what causes MessageEventPump to exit. MessageEventPump(TRUE, 1, &hEvent); } TraceTag((tagProcess, "RunLocalCommand PID=%d, %s", pProc->ProcId(), bstrCommand)); *plErrorCode = pProc->ProcId(); return S_OK; } HRESULT CScriptHost::GetLastRunLocalError(long *plErrorCode) { VERIFY_THREAD(); *plErrorCode = _hrLastRunLocalError; return S_OK; } HRESULT CScriptHost::GetProcessOutput(long lProcessID, BSTR *pbstrData) { VERIFY_THREAD(); CProcessThread *pProc; pProc = _pMT->FindProcess((DWORD)lProcessID); if (!pProc) { return E_INVALIDARG; } return pProc->GetProcessOutput(pbstrData); } HRESULT CScriptHost::GetProcessExitCode(long lProcessID, long *plExitCode) { VERIFY_THREAD(); CProcessThread *pProc; pProc = _pMT->FindProcess((DWORD)lProcessID); if (!pProc) { return E_INVALIDARG; } *plExitCode = pProc->GetExitCode(); return S_OK; } HRESULT CScriptHost::TerminateProcess(long lProcessID) { VERIFY_THREAD(); CProcessThread *pProc; pProc = _pMT->FindProcess((DWORD)lProcessID); if (!pProc) { return E_INVALIDARG; } PostToThread(pProc, MD_PLEASEEXIT); return S_OK; } HRESULT CScriptHost::SendToProcess(long lProcessID, BSTR bstrType, BSTR bstrData, long *plReturn) { VERIFY_THREAD(); MACHPROC_EVENT_DATA med; MACHPROC_EVENT_DATA *pmed; VARIANT vRet; HRESULT hr = S_OK; CProcessThread *pProc; pProc = _pMT->FindProcess((DWORD)lProcessID); //$ TODO -- Fix these error return codes to not cause script errors. if (!pProc || !plReturn) { return E_INVALIDARG; } else if (pProc->GetExitCode() != STILL_ACTIVE) { *plReturn = -1; return S_FALSE; } VariantInit(&vRet); med.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (med.hEvent == NULL) { return HRESULT_FROM_WIN32(GetLastError()); } med.bstrCmd = bstrType; med.bstrParams = bstrData; med.dwProcId = (DWORD)lProcessID; med.pvReturn = &vRet; med.dwGITCookie = 0; med.hrReturn = S_OK; pmed = &med; PostToThread(_pMT, MD_SENDTOPROCESS, &pmed, sizeof(MACHPROC_EVENT_DATA*)); // BUGBUG - we could get a crash if something causes us to exit before // the CMTScript thread handles the MD_SENDTOPROCESS message. MessageEventPump(TRUE, 1, &med.hEvent); hr = med.hrReturn; *plReturn = V_I4(&vRet); VariantClear(&vRet); CloseHandle(med.hEvent); return hr; } #define USERPROFILESTRING_SZ (256 * sizeof(TCHAR)) TCHAR UserProfileString[USERPROFILESTRING_SZ]; HRESULT CScriptHost::SendMail(BSTR bstrTo, BSTR bstrCC, BSTR bstrBCC, BSTR bstrSubject, BSTR bstrMessage, BSTR bstrAttachmentPath, BSTR bstrUsername, BSTR bstrPassword, long * plErrorCode) { // This implementation was stolen from the execmail.exe source code. // Handles to MAPI32.DLL library, host name registry key, email session. //$ FUTURE -- Cache this stuff instead of reloading the library every // time. HINSTANCE hLibrary; LHANDLE hSession; // Function pointers for MAPI calls we use. LPMAPILOGON fnMAPILogon; LPMAPISENDMAIL fnMAPISendMail; LPMAPILOGOFF fnMAPILogoff; // MAPI structures and counters. MapiRecipDesc rgRecipDescStruct[30]; MapiMessage MessageStruct = {0, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0, NULL, 0, NULL}; MapiFileDesc MAPIFileDesc = {0, 0, 0, NULL, NULL, NULL}; FLAGS MAPIFlags = MAPI_NEW_SESSION; // Pointers to email parameter strings. char *pszToList = NULL; char *pszCCList = NULL; char *pszBCCList = NULL; ULONG ulErrorCode; if (!plErrorCode) return E_POINTER; /////////////////////////////////////////////////////////////////////////// // No point going any farther if MAPI32.DLL isn't available. hLibrary = LoadLibrary(L"MAPI32.DLL"); if (hLibrary == NULL) { DWORD dwError = GetLastError(); TraceTag((tagError, "Error: MAPI32.DLL not found on this machine!")); *plErrorCode = HRESULT_FROM_WIN32(dwError); return S_FALSE; } // Must convert all parameters to ANSI ANSIString szTo(bstrTo); ANSIString szCC(bstrCC); ANSIString szBCC(bstrBCC); ANSIString szSubject(bstrSubject); ANSIString szMessage(bstrMessage); ANSIString szAttachment(bstrAttachmentPath); ANSIString szUsername(bstrUsername); ANSIString szPassword(bstrPassword); // Set up MAPI function pointers. fnMAPILogon = (LPMAPILOGON)GetProcAddress(hLibrary, "MAPILogon"); fnMAPISendMail = (LPMAPISENDMAIL)GetProcAddress(hLibrary, "MAPISendMail"); fnMAPILogoff = (LPMAPILOGOFF)GetProcAddress(hLibrary, "MAPILogoff"); // Hook the recipient structure array into the message structure. MessageStruct.lpRecips = rgRecipDescStruct; // Get the default user parameters if none were specified. if (SysStringLen(bstrUsername) == 0) { HKEY hkey; WCHAR KeyPath[] = L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows Messaging Subsystem\\Profiles"; WCHAR Value[] = L"DefaultProfile"; DWORD buf_sz = USERPROFILESTRING_SZ; DWORD val_type; if( RegOpenKeyEx( HKEY_CURRENT_USER, KeyPath, 0, KEY_READ, &hkey ) == ERROR_SUCCESS ) { if ( RegQueryValueEx( hkey, Value, NULL, &val_type, (BYTE*)UserProfileString, &buf_sz ) == ERROR_SUCCESS ) { if ( val_type == REG_SZ ) { szUsername.Set(UserProfileString); } } RegCloseKey( hkey ); } } pszToList = szTo; // Parse ToList into rgRecipDescStruct. while (*pszToList && (MessageStruct.nRecipCount < 30)) { // Strip leading spaces from recipient name and terminate preceding // name string. if (isspace(*pszToList)) { *pszToList=0; pszToList++; } // Add a name to the array and increment the number of recipients. else { rgRecipDescStruct[MessageStruct.nRecipCount].ulReserved = 0; rgRecipDescStruct[MessageStruct.nRecipCount].ulRecipClass = MAPI_TO; rgRecipDescStruct[MessageStruct.nRecipCount].lpszName = pszToList; rgRecipDescStruct[MessageStruct.nRecipCount].lpszAddress = NULL; rgRecipDescStruct[MessageStruct.nRecipCount].ulEIDSize = 0; rgRecipDescStruct[MessageStruct.nRecipCount].lpEntryID = NULL; MessageStruct.nRecipCount++; // Move beginning of string to next name in ToList. do { pszToList++; } while (isgraph(*pszToList)); } } pszCCList = szCC; // Parse CCList into rgRecipDescStruct. while (*pszCCList && (MessageStruct.nRecipCount < 30)) { // Strip leading spaces from recipient name and terminate preceding // name string. if (isspace(*pszCCList)) { *pszCCList=0; pszCCList++; } // Add a name to the array and increment the number of recipients. else { rgRecipDescStruct[MessageStruct.nRecipCount].ulReserved = 0; rgRecipDescStruct[MessageStruct.nRecipCount].ulRecipClass = MAPI_CC; rgRecipDescStruct[MessageStruct.nRecipCount].lpszName = pszCCList; rgRecipDescStruct[MessageStruct.nRecipCount].lpszAddress = NULL; rgRecipDescStruct[MessageStruct.nRecipCount].ulEIDSize = 0; rgRecipDescStruct[MessageStruct.nRecipCount].lpEntryID = NULL; MessageStruct.nRecipCount++; // Move beginning of string to next name in CCList. do { pszCCList++; } while (isgraph(*pszCCList)); } } pszBCCList = szBCC; // Parse BCCList into rgRecipDescStruct. while (*pszBCCList && (MessageStruct.nRecipCount < 30)) { // Strip leading spaces from recipient name and terminate preceding // name string. if (isspace(*pszBCCList)) { *pszBCCList=0; pszBCCList++; } // Add a name to the array and increment the number of recipients. else { rgRecipDescStruct[MessageStruct.nRecipCount].ulReserved = 0; rgRecipDescStruct[MessageStruct.nRecipCount].ulRecipClass = MAPI_BCC; rgRecipDescStruct[MessageStruct.nRecipCount].lpszName = pszBCCList; rgRecipDescStruct[MessageStruct.nRecipCount].lpszAddress = NULL; rgRecipDescStruct[MessageStruct.nRecipCount].ulEIDSize = 0; rgRecipDescStruct[MessageStruct.nRecipCount].lpEntryID = NULL; MessageStruct.nRecipCount++; // Move beginning of string to next name in BCCList. do { pszBCCList++; } while (isgraph(*pszBCCList)); } } if (strlen(szAttachment) > 0) { MAPIFileDesc.ulReserved = 0; MAPIFileDesc.flFlags = 0; MAPIFileDesc.nPosition = 0; MAPIFileDesc.lpszPathName = szAttachment; MAPIFileDesc.lpszFileName = NULL; MAPIFileDesc.lpFileType = NULL; MessageStruct.nFileCount = 1; MessageStruct.lpFiles = &MAPIFileDesc; // muck around with the message text (The attachment // will be attached at the beginning of the mail message // but it replaces the character at that position) // BUGBUG -- Do we need to do this? (lylec) //strcpy(szMessageText," \n"); } MessageStruct.lpszSubject = szSubject; MessageStruct.lpszNoteText = szMessage; *plErrorCode = 0; // Send the message! ulErrorCode = fnMAPILogon(0L, szUsername, szPassword, MAPIFlags, 0L, &hSession); if (ulErrorCode != SUCCESS_SUCCESS) { *plErrorCode = (long)ulErrorCode; } else { ulErrorCode = fnMAPISendMail(hSession, 0L, &MessageStruct, 0L, 0L); if (ulErrorCode != SUCCESS_SUCCESS) { *plErrorCode = (long)ulErrorCode; } fnMAPILogoff(hSession, 0L, 0L, 0L); } FreeLibrary(hLibrary); return S_OK; } HRESULT CScriptHost::OUTPUTDEBUGSTRING(BSTR bstrMessage) { VERIFY_THREAD(); if (!_fIsPrimaryScript) MessageEventPump(FALSE); TCHAR szText[ (MSGDATABUFSIZE-1) / sizeof(TCHAR) ]; CScriptSite *site = GetSite(); const TCHAR *pszScriptName = L""; if (site) { pszScriptName = _tcsrchr((LPTSTR)site->_cstrName, _T('\\')); if (!pszScriptName) pszScriptName = (LPTSTR)site->_cstrName; } int cChars = _snwprintf(szText, ARRAY_SIZE(szText), L"%.12s \t%s", pszScriptName, bstrMessage); szText[ARRAY_SIZE(szText) - 1] = 0; if (cChars < 0) cChars = ARRAY_SIZE(szText); else cChars++; PostToThread(_pMT, MD_OUTPUTDEBUGSTRING, szText, cChars * sizeof(TCHAR)); return S_OK; } HRESULT CScriptHost::UnevalString(BSTR bstrInput, BSTR *pbstrOutput) { int nInputLength = SysStringLen(bstrInput); OLECHAR tmpBuf[512]; OLECHAR *pTmp = 0; OLECHAR *pOutputBuffer; *pbstrOutput = 0; if (sizeof(OLECHAR) * (nInputLength * 2 + 2) > sizeof(tmpBuf)) { pTmp = (OLECHAR *)MemAlloc(sizeof(OLECHAR) * (nInputLength * 2 + 2)); if (!pTmp) return E_OUTOFMEMORY; pOutputBuffer = pTmp; } else { pOutputBuffer = tmpBuf; } int j = 0; pOutputBuffer[j++] = L'"'; for(OLECHAR *pInputEnd = bstrInput + nInputLength; bstrInput < pInputEnd; ++bstrInput) { switch(*bstrInput) { case L'\\': case L'"': case L'\'': pOutputBuffer[j] = L'\\'; pOutputBuffer[j+1] = *bstrInput; j += 2; break; case L'\n': pOutputBuffer[j] = L'\\'; pOutputBuffer[j+1] = L'n'; j += 2; break; case L'\r': pOutputBuffer[j] = L'\\'; pOutputBuffer[j+1] = L'r'; j += 2; break; case L'\t': pOutputBuffer[j] = L'\\'; pOutputBuffer[j+1] = L't'; j += 2; break; default: pOutputBuffer[j++] = *bstrInput; break; } } pOutputBuffer[j++] = L'"'; *pbstrOutput = SysAllocStringLen(pOutputBuffer, j); if (pTmp) MemFree(pTmp); if (!*pbstrOutput) return E_OUTOFMEMORY; return S_OK; } HRESULT CScriptHost::CopyOrAppendFile(BSTR bstrSrc, BSTR bstrDst, long nSrcOffset, long nSrcLength, VARIANT_BOOL fAppend, long *pnSrcFilePosition) { HRESULT hr = S_OK; HANDLE hDst = INVALID_HANDLE_VALUE; HANDLE hSrcFile = INVALID_HANDLE_VALUE; BY_HANDLE_FILE_INFORMATION fi = {0}; long nEndPos; long nLen; DWORD nBytesRead; DWORD nBytesWritten; char rgBuffer[4096]; hDst = CreateFile( bstrDst, GENERIC_WRITE, FILE_SHARE_READ, 0, (fAppend ? OPEN_ALWAYS : CREATE_ALWAYS), FILE_ATTRIBUTE_NORMAL, 0); if (hDst == INVALID_HANDLE_VALUE) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Cleanup; } hSrcFile = CreateFile(bstrSrc, GENERIC_READ, FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (hSrcFile == INVALID_HANDLE_VALUE) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Cleanup; } GetFileInformationByHandle(hSrcFile, &fi); if (fi.nFileSizeHigh != 0 || (fi.nFileSizeLow & 0x80000000) != 0) { hr = E_FAIL;//HRESULT_FROM_WIN32(?????); goto Cleanup; } if (nSrcLength == -1) nEndPos = (long)fi.nFileSizeLow; else { if ( ((_int64)nSrcOffset + nSrcLength) > 0x7f000000) { hr = E_INVALIDARG; goto Cleanup; } nEndPos = nSrcOffset + nSrcLength; } if (nEndPos > (long)fi.nFileSizeLow) nEndPos = (long)fi.nFileSizeLow; if (SetFilePointer(hSrcFile, nSrcOffset, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Cleanup; } if (SetFilePointer(hDst, 0, 0, FILE_END) == INVALID_SET_FILE_POINTER) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Cleanup; } while (nSrcOffset < nEndPos) { nLen = nEndPos - nSrcOffset; if (nLen > sizeof(rgBuffer)) nLen = sizeof(rgBuffer); if (!ReadFile(hSrcFile, rgBuffer, nLen, &nBytesRead, 0)) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Cleanup; } if (!WriteFile(hDst, rgBuffer, nBytesRead, &nBytesWritten, 0)) { hr = HRESULT_FROM_WIN32(GetLastError()); goto Cleanup; } nSrcOffset += nBytesRead; } if (pnSrcFilePosition) *pnSrcFilePosition = nSrcOffset; Cleanup: if (hDst != INVALID_HANDLE_VALUE) CloseHandle(hDst); if (hSrcFile != INVALID_HANDLE_VALUE) CloseHandle(hSrcFile); return hr; } HRESULT CScriptHost::ASSERT(VARIANT_BOOL fAssert, BSTR bstrMessage) { VERIFY_THREAD(); if (!_fIsPrimaryScript) MessageEventPump(FALSE); if (!fAssert) { CHAR ach[1024]; // Add name of currently executing script to the assert message. if (!GetSite() || !GetSite()->_achPath[0]) { ach[0] = 0; } else { // Try to chop off directory name. TCHAR * pchName = wcsrchr(GetSite()->_achPath, _T('\\')); if (pchName) pchName += 1; else pchName = GetSite()->_achPath; WideCharToMultiByte( CP_ACP, 0, pchName, -1, ach, MAX_PATH, NULL, NULL); strcat(ach, ": "); } // Add message to the assert. if (!bstrMessage || !*bstrMessage) { strcat(ach, "MTScript Script Assert"); } else { WideCharToMultiByte( CP_ACP, 0, bstrMessage, -1, &ach[strlen(ach)], ARRAY_SIZE(ach) - MAX_PATH - 3, NULL, NULL); } #if DBG == 1 AssertSz(FALSE, ach); #else if (MessageBoxA(NULL, ach, "MTScript Script Assert", MB_OKCANCEL | MB_SETFOREGROUND) == IDCANCEL) return E_FAIL; #endif } return S_OK; } HRESULT CScriptHost::Sleep (int nTimeout) { VERIFY_THREAD(); MessageEventPump(TRUE, 0, NULL, FALSE, (DWORD)nTimeout); return S_OK; } HRESULT CScriptHost::Reboot() { VERIFY_THREAD(); PostToThread(_pMT, MD_REBOOT); return S_OK; } HRESULT CScriptHost::NotifyScript(BSTR bstrEvent, VARIANT vData) { HRESULT hr = S_OK; VARIANT *pvar; VERIFY_THREAD(); // Check for data types which we don't support. if ( V_ISBYREF(&vData) || V_ISARRAY(&vData) || V_ISVECTOR(&vData) || V_VT(&vData) == VT_DISPATCH //$ FUTURE: Support this later || V_VT(&vData) == VT_UNKNOWN) { return E_INVALIDARG; } if (!_pMT->_pMachine) { return S_OK; } pvar = new VARIANT[2]; VariantInit(&pvar[0]); VariantInit(&pvar[1]); V_VT(&pvar[0]) = VT_BSTR; V_BSTR(&pvar[0]) = SysAllocString(bstrEvent); VariantCopy(&pvar[1], &vData); PostToThread(_pMT->_pMachine, MD_NOTIFYSCRIPT, &pvar, sizeof(VARIANT*)); return hr; } HRESULT CScriptHost::RegisterEventSource(IDispatch *pDisp, BSTR bstrProgID) { HRESULT hr; CScriptEventSink *pSink = NULL; pSink = new CScriptEventSink(this); if (!pSink) return E_OUTOFMEMORY; hr = pSink->Connect(pDisp, bstrProgID); if (!hr) { _aryEvtSinks.Append(pSink); } else pSink->Release(); return hr; } HRESULT CScriptHost::UnregisterEventSource(IDispatch *pDisp) { int i; BOOL fFound = FALSE; for (i = 0; i < _aryEvtSinks.Size(); i++) { if (_aryEvtSinks[i]->IsThisYourSource(pDisp)) { _aryEvtSinks[i]->Disconnect(); _aryEvtSinks.ReleaseAndDelete(i); fFound = TRUE; break; } } return (fFound) ? S_OK : E_INVALIDARG; } HRESULT CScriptHost::get_HostMajorVer(long *plMajorVer) { if (!plMajorVer) return E_POINTER; *plMajorVer = IConnectedMachine_lVersionMajor; return S_OK; } HRESULT CScriptHost::get_HostMinorVer(long *plMinorVer) { if (!plMinorVer) return E_POINTER; *plMinorVer = IConnectedMachine_lVersionMinor; return S_OK; } HRESULT CScriptHost::get_StatusValue(long nIndex, long *pnStatus) { return _pMT->get_StatusValue(nIndex, pnStatus); } HRESULT CScriptHost::put_StatusValue(long nIndex, long nStatus) { return _pMT->put_StatusValue(nIndex, nStatus); }