1 /////////////////////////////////////////////////////////////////////////////
2 // WinMerge: an interactive diff/merge utility
3 // Copyright (C) 1997-2000 Thingamahoochie Software
5 // SPDX-License-Identifier: GPL-2.0-or-later
6 /////////////////////////////////////////////////////////////////////////////
10 * @brief Support for VBS Scriptlets, VB ActiveX DLL, VC++ COM DLL
15 #define POCO_NO_UNWINDOWS 1
18 #include <unordered_set>
23 #include <Poco/Mutex.h>
24 #include <Poco/ScopedLock.h>
25 #include <Poco/RegularExpression.h>
29 #include "FileFilterMgr.h"
32 #include "Exceptions.h"
35 #include "Environment.h"
36 #include "FileFilter.h"
37 #include "coretools.h"
38 #include "OptionsMgr.h"
39 #include "OptionsDef.h"
40 #include "codepage_detect.h"
42 #include "WinMergePluginBase.h"
45 using Poco::RegularExpression;
46 using Poco::FastMutex;
47 using Poco::ScopedLock;
50 * @brief Category of transformation : define the transformation events
52 * @note USER categories are calls to scriptlets, or VB ActiveX DLL, or VC COM DLL
53 * Use text definition : if you add one, nothing to do ;
54 * if you change one, you just have change the dll/scripts for that event
56 const wchar_t *TransformationCategories[] =
61 L"BUFFER_PACK_UNPACK",
63 L"FILE_FOLDER_PACK_UNPACK",
64 nullptr, // last empty : necessary
67 static vector<String> theScriptletList;
68 /// Need to lock the *.sct so the user can't delete them
69 static vector<HANDLE> theScriptletHandleList;
70 static bool scriptletsLoaded=false;
71 static FastMutex scriptletsSem;
72 static std::unordered_map<String, String> customFiltersMap;
74 template<class T> struct AutoReleaser
76 explicit AutoReleaser(T *ptr) : p(ptr) {}
77 ~AutoReleaser() { if (p!=nullptr) p->Release(); }
81 ////////////////////////////////////////////////////////////////////////////////
87 * @brief Check for the presence of Windows Script
89 * .sct plugins require this optional component
91 bool IsWindowsScriptThere()
94 if (!keyFile.QueryRegMachine(_T("SOFTWARE\\Classes\\scriptletfile\\AutoRegister")))
97 String filename = keyFile.ReadString(_T(""), _T(""));
102 return (paths::DoesPathExist(filename) == paths::IS_EXISTING_FILE);
105 ////////////////////////////////////////////////////////////////////////////////
106 // scriptlet/activeX support for function names
108 // list the function IDs and names in a script or activeX dll
109 int GetFunctionsFromScript(IDispatch *piDispatch, vector<String>& namesArray, vector<int>& IdArray, INVOKEKIND wantedKind)
112 if (piDispatch != nullptr)
114 ITypeInfo *piTypeInfo=nullptr;
115 unsigned iTInfo = 0; // 0 for type information of IDispatch itself
116 LCID lcid=0; // locale for localized method names (ignore if no localized names)
117 if (SUCCEEDED(piDispatch->GetTypeInfo(iTInfo, lcid, &piTypeInfo)))
119 TYPEATTR *pTypeAttr=nullptr;
120 if (SUCCEEDED(piTypeInfo->GetTypeAttr(&pTypeAttr)))
122 // allocate arrays for the returned structures
123 // the names array is NUL terminated
124 namesArray.resize(pTypeAttr->cFuncs+1);
125 IdArray.resize(pTypeAttr->cFuncs+1);
127 UINT iMaxFunc = pTypeAttr->cFuncs - 1;
128 for (UINT iFunc = 0 ; iFunc <= iMaxFunc ; ++iFunc)
130 UINT iFuncDesc = iMaxFunc - iFunc;
132 if (SUCCEEDED(piTypeInfo->GetFuncDesc(iFuncDesc, &pFuncDesc)))
134 // exclude properties
135 // exclude IDispatch inherited methods
136 if (pFuncDesc->invkind & wantedKind && !(pFuncDesc->wFuncFlags & 1))
140 if (SUCCEEDED(piTypeInfo->GetNames(pFuncDesc->memid,
141 &bstrName, 1, &cNames)))
143 IdArray[iValidFunc] = pFuncDesc->memid;
144 namesArray[iValidFunc] = ucr::toTString(bstrName);
147 SysFreeString(bstrName);
149 piTypeInfo->ReleaseFuncDesc(pFuncDesc);
152 piTypeInfo->ReleaseTypeAttr(pTypeAttr);
154 piTypeInfo->Release();
160 int GetMethodsFromScript(IDispatch *piDispatch, vector<String>& namesArray, vector<int> &IdArray)
162 return GetFunctionsFromScript(piDispatch, namesArray, IdArray, INVOKE_FUNC);
164 int GetPropertyGetsFromScript(IDispatch *piDispatch, vector<String>& namesArray, vector<int> &IdArray)
166 return GetFunctionsFromScript(piDispatch, namesArray, IdArray, INVOKE_PROPERTYGET);
170 // search a function name in a scriptlet or activeX dll
171 bool SearchScriptForMethodName(LPDISPATCH piDispatch, const wchar_t *functionName)
173 vector<String> namesArray;
175 const int nFnc = GetMethodsFromScript(piDispatch, namesArray, IdArray);
177 const String tfuncname = ucr::toTString(functionName);
178 for (int iFnc = 0 ; iFnc < nFnc ; iFnc++)
180 if (namesArray[iFnc] == tfuncname)
186 // search a property name (with get interface) in a scriptlet or activeX dll
187 bool SearchScriptForDefinedProperties(IDispatch *piDispatch, const wchar_t *functionName)
189 vector<String> namesArray;
191 const int nFnc = GetPropertyGetsFromScript(piDispatch, namesArray, IdArray);
193 const String tfuncname = ucr::toTString(functionName);
194 for (int iFnc = 0 ; iFnc < nFnc ; iFnc++)
196 if (namesArray[iFnc] == tfuncname)
203 int CountMethodsInScript(LPDISPATCH piDispatch)
205 vector<String> namesArray;
207 return GetMethodsFromScript(piDispatch, namesArray, IdArray);
211 * @return ID of the function or -1 if no function with this index
213 int GetMethodIDInScript(LPDISPATCH piDispatch, int methodIndex)
217 vector<String> namesArray;
219 const int nFnc = GetMethodsFromScript(piDispatch, namesArray, IdArray);
221 if (methodIndex < nFnc)
223 fncID = IdArray[methodIndex];
235 ////////////////////////////////////////////////////////////////////////////////
236 // find scripts/activeX for an event : each event is assigned to a subdirectory
240 * @brief Get a list of scriptlet file
242 * @return Returns an array of LPSTR
244 static void GetScriptletsAt(const String& sSearchPath, const String& extension, vector<String>& scriptlets )
247 String strFileSpec = paths::ConcatPath(sSearchPath, _T("*") + extension);
248 HANDLE hff = FindFirstFile(strFileSpec.c_str(), &ffi);
250 if ( hff != INVALID_HANDLE_VALUE )
254 if (!(ffi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
256 strFileSpec = paths::ConcatPath(sSearchPath, ffi.cFileName);
257 if (strFileSpec.substr(strFileSpec.length() - extension.length()) == extension) // excludes *.sct~ files
258 scriptlets.push_back(strFileSpec);
261 while (FindNextFile(hff, &ffi));
266 void PluginInfo::LoadFilterString()
270 String sLine(m_filtersText);
275 String::size_type pos = sLine.rfind(';');
276 sPiece = sLine.substr(pos+1);
277 if (pos == String::npos)
279 sLine = sLine.substr(0, pos);
283 sPiece = strutils::makeupper(strutils::trim_ws_begin(sPiece));
286 std::string regexString = ucr::toUTF8(sPiece);
287 re_opts |= RegularExpression::RE_UTF8;
290 m_filters.push_back(FileFilterElementPtr(new FileFilterElement(regexString, re_opts)));
300 bool PluginInfo::TestAgainstRegList(const String& szTest) const
302 if (m_filters.empty() || szTest.empty())
305 String sLine = szTest;
308 while(!sLine.empty())
310 String::size_type pos = sLine.rfind('|');
311 sPiece = sLine.substr(pos+1);
312 if (pos == String::npos)
314 sLine = sLine.substr(0, pos);
317 sPiece = strutils::makeupper(strutils::trim_ws_begin(sPiece));
319 if (::TestAgainstRegList(&m_filters, sPiece))
327 * @brief Log technical explanation, in English, of script error
330 ScriptletError(const String & scriptletFilepath, const TCHAR *szError)
332 String msg = _T("Plugin scriptlet error <")
339 static std::unordered_set<String> GetDisabledPluginList()
341 std::unordered_set<String> list;
342 std::basic_stringstream<TCHAR> ss(GetOptionsMgr()->GetString(OPT_PLUGINS_DISABLED_LIST));
344 while (std::getline(ss, name, _T('|')))
349 static std::unordered_map<String, String> GetCustomFiltersMap()
351 std::unordered_map<String, String> map;
352 std::basic_stringstream<TCHAR> ss(GetOptionsMgr()->GetString(OPT_PLUGINS_CUSTOM_FILTERS_LIST));
353 String nameAndFiltersText;
354 while (std::getline(ss, nameAndFiltersText, _T('\t')))
356 size_t pos = nameAndFiltersText.find_first_of(':');
357 if (pos != String::npos)
359 String name = nameAndFiltersText.substr(0, pos);
360 String filtersText = nameAndFiltersText.substr(pos + 1);
361 map.emplace(name, filtersText);
364 map.emplace(_T("||initialized||"), _T(""));
368 static String GetCustomFilters(const String& name, const String& filtersTextDefault)
370 FastMutex::ScopedLock lock(scriptletsSem);
371 if (customFiltersMap.empty())
372 customFiltersMap = GetCustomFiltersMap();
373 if (customFiltersMap.find(name) != customFiltersMap.end())
374 return customFiltersMap[name];
376 return filtersTextDefault;
379 class UnpackerGeneratedFromEditorScript: public WinMergePluginBase
382 UnpackerGeneratedFromEditorScript(IDispatch *pDispatch, const std::wstring funcname, int id) : m_pDispatch(pDispatch), m_funcid(id)
385 strutils::format_string1(_T("Unpacker to execute %1 script (automatically generated)") , funcname);
386 m_sFileFilters = _T(".");
387 m_bIsAutomatic = true;
388 m_sEvent = L"FILE_PACK_UNPACK";
389 m_pDispatch->AddRef();
392 virtual ~UnpackerGeneratedFromEditorScript()
396 static HRESULT ReadFile(const String& path, String& text)
399 if (!file.OpenReadOnly(path))
400 return E_ACCESSDENIED;
404 int iGuessEncodingType = GetOptionsMgr()->GetInt(OPT_CP_DETECT);
405 FileTextEncoding encoding = codepage_detect::Guess(
406 paths::FindExtension(path), file.GetBase(),
407 file.GetFileSize() < codepage_detect::BufSize ? file.GetFileSize() : codepage_detect::BufSize,
409 file.SetCodepage(encoding.m_codepage);
411 file.ReadStringAll(text);
416 static HRESULT WriteFile(const String& path, const String& text)
418 UniStdioFile fileOut;
419 if (!fileOut.Open(path, _T("wb")))
420 return E_ACCESSDENIED;
421 fileOut.SetUnicoding(ucr::UNICODESET::UTF8);
422 fileOut.SetBom(true);
424 fileOut.WriteString(text);
429 HRESULT STDMETHODCALLTYPE UnpackFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL* pbChanged, INT* pSubcode, VARIANT_BOOL* pbSuccess) override
432 HRESULT hr = ReadFile(fileSrc, text);
436 if (!plugin::InvokeTransformText(text, changed, m_pDispatch, m_funcid))
438 hr = WriteFile(fileDst, text);
442 *pbChanged = VARIANT_TRUE;
443 *pbSuccess = VARIANT_TRUE;
448 IDispatch *m_pDispatch;
453 * @brief Tiny structure that remembers current scriptlet & event info for calling Log
457 ScriptInfo(const String & scriptletFilepath)
458 : m_scriptletFilepath(scriptletFilepath)
461 void Log(const TCHAR *szError)
463 ScriptletError(m_scriptletFilepath, szError);
465 const String & m_scriptletFilepath;
469 * @brief Try to load a plugin
471 * @return 1 if loaded plugin successfully, negatives for errors
473 int PluginInfo::MakeInfo(const String & scriptletFilepath, IDispatch *lpDispatch)
475 // set up object in case we need to log info
476 ScriptInfo scinfo(scriptletFilepath);
478 // Ensure that interface is released if any bad exit or exception
479 AutoReleaser<IDispatch> drv(lpDispatch);
481 // Is this plugin for this transformationEvent ?
483 // invoke mandatory method get PluginEvent
485 if (!plugin::SearchScriptForDefinedProperties(lpDispatch, L"PluginEvent"))
487 scinfo.Log(_T("PluginEvent method missing"));
490 HRESULT h = ::invokeW(lpDispatch, &ret, L"PluginEvent", opGet[0], nullptr);
491 if (FAILED(h) || ret.vt != VT_BSTR)
493 scinfo.Log( _T("Error accessing PluginEvent method"));
496 m_event = ucr::toTString(ret.bstrVal);
500 // plugins PREDIFF or PACK_UNPACK : functions names are mandatory
501 // Check that the plugin offers the requested functions
502 // set the mode for the events which uses it
504 if (m_event == _T("BUFFER_PREDIFF"))
506 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"PrediffBufferW");
508 else if (m_event == _T("FILE_PREDIFF"))
510 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"PrediffFile");
512 else if (m_event == _T("BUFFER_PACK_UNPACK"))
514 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"UnpackBufferA");
515 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"PackBufferA");
517 else if (m_event == _T("FILE_PACK_UNPACK"))
519 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"UnpackFile");
520 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"PackFile");
522 else if (m_event == _T("FILE_FOLDER_PACK_UNPACK"))
524 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"IsFolder");
525 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"UnpackFile");
526 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"PackFile");
527 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"UnpackFolder");
528 bFound &= plugin::SearchScriptForMethodName(lpDispatch, L"PackFolder");
532 // error (Plugin doesn't support the method as it claimed)
533 scinfo.Log(_T("Plugin doesn't support the method as it claimed"));
537 // plugins EDITOR_SCRIPT : functions names are free
538 // there may be several functions inside one script, count the number of functions
539 if (m_event == _T("EDITOR_SCRIPT"))
541 m_nFreeFunctions = plugin::CountMethodsInScript(lpDispatch);
542 if (m_nFreeFunctions == 0)
543 // error (Plugin doesn't offer any method, what is this ?)
548 // get optional property PluginDescription
549 if (plugin::SearchScriptForDefinedProperties(lpDispatch, L"PluginDescription"))
551 h = ::invokeW(lpDispatch, &ret, L"PluginDescription", opGet[0], nullptr);
552 if (FAILED(h) || ret.vt != VT_BSTR)
554 scinfo.Log(_T("Plugin had PluginDescription property, but error getting its value"));
555 return -60; // error (Plugin had PluginDescription property, but error getting its value)
557 m_description = ucr::toTString(ret.bstrVal);
561 // no description, use filename
562 m_description = paths::FindFileName(scriptletFilepath);
566 // get PluginFileFilters
567 bool hasPluginFileFilters = false;
568 if (plugin::SearchScriptForDefinedProperties(lpDispatch, L"PluginFileFilters"))
570 h = ::invokeW(lpDispatch, &ret, L"PluginFileFilters", opGet[0], nullptr);
571 if (FAILED(h) || ret.vt != VT_BSTR)
573 scinfo.Log(_T("Plugin had PluginFileFilters property, but error getting its value"));
574 return -70; // error (Plugin had PluginFileFilters property, but error getting its value)
576 m_filtersTextDefault = ucr::toTString(ret.bstrVal);
577 hasPluginFileFilters = true;
581 m_bAutomatic = false;
582 m_filtersTextDefault = _T(".");
586 // get optional property PluginIsAutomatic
587 if (plugin::SearchScriptForDefinedProperties(lpDispatch, L"PluginIsAutomatic"))
589 h = ::invokeW(lpDispatch, &ret, L"PluginIsAutomatic", opGet[0], nullptr);
590 if (FAILED(h) || ret.vt != VT_BOOL)
592 scinfo.Log(_T("Plugin had PluginIsAutomatic property, but error getting its value"));
593 return -80; // error (Plugin had PluginIsAutomatic property, but error getting its value)
595 m_bAutomatic = !!ret.boolVal;
599 if (hasPluginFileFilters)
601 scinfo.Log(_T("Plugin had PluginFileFilters property, but lacked PluginIsAutomatic property"));
602 // PluginIsAutomatic property is mandatory for Plugins with PluginFileFilters property
605 // default to false when Plugin doesn't have property
606 m_bAutomatic = false;
610 // get optional property PluginUnpackedFileExtenstion
611 if (plugin::SearchScriptForDefinedProperties(lpDispatch, L"PluginUnpackedFileExtension"))
613 h = ::invokeW(lpDispatch, &ret, L"PluginUnpackedFileExtension", opGet[0], nullptr);
614 if (FAILED(h) || ret.vt != VT_BSTR)
616 scinfo.Log(_T("Plugin had PluginUnpackedFileExtension property, but error getting its value"));
617 return -100; // error (Plugin had PluginUnpackedFileExtenstion property, but error getting its value)
619 m_ext = ucr::toTString(ret.bstrVal);
628 m_name = paths::FindFileName(scriptletFilepath);
630 m_filtersText = GetCustomFilters(m_name, m_filtersTextDefault);
633 // Clear the autorelease holder
636 m_lpDispatch = lpDispatch;
638 m_filepath = scriptletFilepath;
644 * @brief Try to load a plugin
646 * @return 1 if loaded plugin successfully, negatives for errors
648 int PluginInfo::LoadPlugin(const String & scriptletFilepath)
650 // set up object in case we need to log info
651 ScriptInfo scinfo(scriptletFilepath);
653 // Search for the class "WinMergeScript"
654 LPDISPATCH lpDispatch = CreateDispatchBySource(scriptletFilepath.c_str(), L"WinMergeScript");
655 if (lpDispatch == nullptr)
657 scinfo.Log(_T("WinMergeScript entry point not found"));
661 return MakeInfo(scriptletFilepath, lpDispatch);
664 static void ReportPluginLoadFailure(const String & scriptletFilepath)
666 AppErrorMessageBox(strutils::format(_T("Exception loading plugin\r\n%s"), scriptletFilepath));
670 * @brief Guard call to LoadPlugin with Windows SEH to trap GPFs
672 * @return same as LoadPlugin (1=has event, 0=doesn't have event, errors are negative)
674 static int LoadPluginWrapper(PluginInfo & plugin, const String & scriptletFilepath)
679 return plugin.LoadPlugin(scriptletFilepath);
681 catch (SE_Exception&)
683 ReportPluginLoadFailure(scriptletFilepath);
689 * @brief Return list of all candidate plugins in module path
691 * Computes list only the first time, and caches it.
692 * Lock the plugins *.sct (.ocx and .dll are locked when the interface is created)
694 static vector<String>& LoadTheScriptletList()
696 FastMutex::ScopedLock lock(scriptletsSem);
697 if (!scriptletsLoaded)
699 String path = paths::ConcatPath(env::GetProgPath(), _T("MergePlugins"));
701 if (plugin::IsWindowsScriptThere())
702 GetScriptletsAt(path, _T(".sct"), theScriptletList ); // VBS/JVS scriptlet
704 LogErrorString(_T("\n .sct plugins disabled (Windows Script Host not found)"));
705 GetScriptletsAt(path, _T(".ocx"), theScriptletList ); // VB COM object
706 GetScriptletsAt(path, _T(".dll"), theScriptletList ); // VC++ COM object
707 scriptletsLoaded = true;
709 // lock the *.sct to avoid them being deleted/moved away
710 for (size_t i = 0 ; i < theScriptletList.size() ; i++)
712 String scriptlet = theScriptletList.at(i);
713 if (scriptlet.length() > 4 && strutils::compare_nocase(scriptlet.substr(scriptlet.length() - 4), _T(".sct")) != 0)
715 // don't need to lock this file
716 theScriptletHandleList.push_back(nullptr);
721 hFile=CreateFile(scriptlet.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING,
723 if (hFile == INVALID_HANDLE_VALUE)
725 theScriptletList.erase(theScriptletList.begin() + i);
730 theScriptletHandleList.push_back(hFile);
734 return theScriptletList;
737 * @brief Delete the scriptlet list and delete locks to *.sct
739 * Allow to load it again
741 static void UnloadTheScriptletList()
743 FastMutex::ScopedLock lock(scriptletsSem);
744 if (scriptletsLoaded)
746 for (size_t i = 0 ; i < theScriptletHandleList.size() ; i++)
748 HANDLE hFile = theScriptletHandleList.at(i);
749 if (hFile != nullptr)
753 theScriptletHandleList.clear();
754 theScriptletList.clear();
755 scriptletsLoaded = false;
760 * @brief Remove a candidate plugin from the cache
762 static void RemoveScriptletCandidate(const String &scriptletFilepath)
764 for (size_t i=0; i<theScriptletList.size(); ++i)
766 if (scriptletFilepath == theScriptletList[i])
768 HANDLE hFile = theScriptletHandleList.at(i);
769 if (hFile != nullptr)
772 theScriptletHandleList.erase(theScriptletHandleList.begin() + i);
773 theScriptletList.erase(theScriptletList.begin() + i);
780 * @brief Get available scriptlets for an event
782 * @return Returns an array of valid LPDISPATCH
784 static std::map<String, PluginArrayPtr> GetAvailableScripts()
786 vector<String>& scriptlets = LoadTheScriptletList();
787 std::unordered_set<String> disabled_plugin_list = GetDisabledPluginList();
788 std::map<std::wstring, PluginArrayPtr> plugins;
790 std::list<String> badScriptlets;
791 for (size_t i = 0 ; i < scriptlets.size() ; i++)
793 // Note all the info about the plugin
794 PluginInfoPtr plugin(new PluginInfo);
796 String scriptletFilepath = scriptlets.at(i);
797 int rtn = LoadPluginWrapper(*plugin.get(), scriptletFilepath);
800 // Plugin has this event
801 plugin->m_disabled = (disabled_plugin_list.find(plugin->m_name) != disabled_plugin_list.end());
802 if (plugins.find(plugin->m_event) == plugins.end())
803 plugins[plugin->m_event].reset(new PluginArray);
804 plugins[plugin->m_event]->push_back(plugin);
809 badScriptlets.push_back(scriptletFilepath);
813 if (plugins.find(L"EDITOR_SCRIPT") != plugins.end())
815 for (auto plugin : *plugins[L"EDITOR_SCRIPT"])
817 std::vector<String> namesArray;
818 std::vector<int> idArray;
819 int validFuncs = plugin::GetMethodsFromScript(plugin->m_lpDispatch, namesArray, idArray);
820 for (int i = 0; i < validFuncs; ++i)
822 if (plugins.find(L"FILE_PACK_UNPACK") == plugins.end())
823 plugins[L"FILE_PACK_UNPACK"].reset(new PluginArray);
824 PluginInfoPtr pluginNew(new PluginInfo());
825 IDispatch *pDispatch = new UnpackerGeneratedFromEditorScript(plugin->m_lpDispatch, namesArray[i], idArray[i]);
827 pluginNew->MakeInfo(plugin->m_filepath + _T(":") + namesArray[i], pDispatch);
828 plugins[L"FILE_PACK_UNPACK"]->push_back(pluginNew);
833 // Remove any bad plugins from the cache
834 // This might be a good time to see if the user wants to abort or continue
835 while (!badScriptlets.empty())
837 RemoveScriptletCandidate(badScriptlets.front());
838 badScriptlets.pop_front();
844 static void FreeAllScripts(PluginArrayPtr& pArray)
850 ////////////////////////////////////////////////////////////////////////////////////
851 // class CScriptsOfThread : cache the interfaces during the thread life
853 CScriptsOfThread::CScriptsOfThread()
855 // count number of events
858 if (TransformationCategories[i] == nullptr)
860 nTransformationEvents = i;
862 // initialize the thread data
863 m_nThreadId = GetCurrentThreadId();
865 // initialize the plugins pointers
866 typedef PluginArray * LPPluginArray;
867 // CoInitialize the thread, keep the returned value for the destructor
868 hrInitialize = CoInitialize(nullptr);
869 assert(hrInitialize == S_OK || hrInitialize == S_FALSE);
872 CScriptsOfThread::~CScriptsOfThread()
876 if (hrInitialize == S_OK || hrInitialize == S_FALSE)
880 bool CScriptsOfThread::bInMainThread()
882 return (CAllThreadsScripts::bInMainThread(this));
885 PluginArray * CScriptsOfThread::GetAvailableScripts(const wchar_t *transformationEvent)
887 if (m_aPluginsByEvent.empty())
888 m_aPluginsByEvent = ::GetAvailableScripts();
889 if (auto it = m_aPluginsByEvent.find(transformationEvent); it != m_aPluginsByEvent.end())
890 return it->second.get();
891 // return a pointer to an empty list
892 static PluginArray noPlugin;
896 void CScriptsOfThread::SaveSettings()
898 std::vector<String> listDisabled;
899 std::vector<String> listCustomFilters;
900 if (m_aPluginsByEvent.empty())
901 m_aPluginsByEvent = ::GetAvailableScripts();
902 for (auto [key, pArray] : m_aPluginsByEvent)
904 for (size_t j = 0; j < pArray->size(); ++j)
906 const PluginInfoPtr & plugin = pArray->at(j);
907 if (plugin->m_disabled)
908 listDisabled.emplace_back(plugin->m_name);
909 if (plugin->m_filtersTextDefault != plugin->m_filtersText)
910 listCustomFilters.emplace_back(plugin->m_name + _T(":") + plugin->m_filtersText);
911 customFiltersMap.insert_or_assign(plugin->m_name, plugin->m_filtersText);
914 GetOptionsMgr()->SaveOption(OPT_PLUGINS_DISABLED_LIST, strutils::join(listDisabled.begin(), listDisabled.end(), _T("|")));
915 GetOptionsMgr()->SaveOption(OPT_PLUGINS_CUSTOM_FILTERS_LIST, strutils::join(listCustomFilters.begin(), listCustomFilters.end(), _T("\t")));
918 void CScriptsOfThread::FreeAllScripts()
920 // release all the scripts of the thread
921 for (auto [key, pArray] : m_aPluginsByEvent)
922 ::FreeAllScripts(pArray);
924 // force to reload the scriptlet list
925 UnloadTheScriptletList();
928 void CScriptsOfThread::FreeScriptsForEvent(const wchar_t *transformationEvent)
930 if (auto it = m_aPluginsByEvent.find(transformationEvent); it != m_aPluginsByEvent.end())
932 if (it->second != nullptr)
933 ::FreeAllScripts(it->second);
937 PluginInfo *CScriptsOfThread::GetAutomaticPluginByFilter(const wchar_t *transformationEvent, const String& filteredText)
939 PluginArray * piFileScriptArray = GetAvailableScripts(transformationEvent);
940 for (size_t step = 0 ; step < piFileScriptArray->size() ; step ++)
942 const PluginInfoPtr & plugin = piFileScriptArray->at(step);
943 if (!plugin->m_bAutomatic || plugin->m_disabled)
945 if (!plugin->TestAgainstRegList(filteredText))
952 PluginInfo * CScriptsOfThread::GetPluginByName(const wchar_t *transformationEvent, const String& name)
954 if (m_aPluginsByEvent.empty())
955 m_aPluginsByEvent = ::GetAvailableScripts();
956 for (auto [key, pArray] : m_aPluginsByEvent)
958 if (!transformationEvent || key == transformationEvent)
960 for (size_t j = 0; j < pArray->size(); j++)
961 if (pArray->at(j)->m_name == name)
962 return pArray->at(j).get();
968 PluginInfo * CScriptsOfThread::GetPluginInfo(LPDISPATCH piScript)
970 for (auto [key, pArray] : m_aPluginsByEvent)
972 if (pArray == nullptr)
974 for (size_t j = 0 ; j < pArray->size() ; j++)
975 if ((*pArray)[j]->m_lpDispatch == piScript)
976 return (*pArray)[j].get();
982 ////////////////////////////////////////////////////////////////////////////////////
983 // class CAllThreadsScripts : array of CScriptsOfThread, one per active thread
985 std::vector<CScriptsOfThread *> CAllThreadsScripts::m_aAvailableThreads;
986 FastMutex m_aAvailableThreadsLock;
988 void CAllThreadsScripts::Add(CScriptsOfThread * scripts)
990 FastMutex::ScopedLock lock(m_aAvailableThreadsLock);
991 // add the thread in the array
993 // register in the array
994 m_aAvailableThreads.push_back(scripts);
997 void CAllThreadsScripts::Remove(CScriptsOfThread * scripts)
999 FastMutex::ScopedLock lock(m_aAvailableThreadsLock);
1000 // unregister from the list
1001 std::vector<CScriptsOfThread *>::iterator it;
1002 for (it = m_aAvailableThreads.begin(); it != m_aAvailableThreads.end(); ++it)
1003 if ((*it) == scripts)
1005 m_aAvailableThreads.erase(it);
1010 CScriptsOfThread * CAllThreadsScripts::GetActiveSet()
1012 FastMutex::ScopedLock lock(m_aAvailableThreadsLock);
1013 unsigned long nThreadId = GetCurrentThreadId();
1014 for (size_t i = 0 ; i < m_aAvailableThreads.size() ; i++)
1015 if (m_aAvailableThreads[i] && m_aAvailableThreads[i]->m_nThreadId == nThreadId)
1016 return m_aAvailableThreads[i];
1020 CScriptsOfThread * CAllThreadsScripts::GetActiveSetNoAssert()
1022 FastMutex::ScopedLock lock(m_aAvailableThreadsLock);
1023 unsigned long nThreadId = GetCurrentThreadId();
1024 for (size_t i = 0 ; i < m_aAvailableThreads.size() ; i++)
1025 if (m_aAvailableThreads[i] && m_aAvailableThreads[i]->m_nThreadId == nThreadId)
1026 return m_aAvailableThreads[i];
1030 bool CAllThreadsScripts::bInMainThread(CScriptsOfThread * scripts)
1032 FastMutex::ScopedLock lock(m_aAvailableThreadsLock);
1033 return (scripts == m_aAvailableThreads[0]);
1036 ////////////////////////////////////////////////////////////////////////////////////
1037 // class CAssureScriptsForThread : control creation/destruction of CScriptsOfThread
1039 CAssureScriptsForThread::CAssureScriptsForThread()
1041 CScriptsOfThread * scripts = CAllThreadsScripts::GetActiveSetNoAssert();
1042 if (scripts == nullptr)
1044 scripts = new CScriptsOfThread;
1045 // insert the script in the repository
1046 CAllThreadsScripts::Add(scripts);
1050 CAssureScriptsForThread::~CAssureScriptsForThread()
1052 CScriptsOfThread * scripts = CAllThreadsScripts::GetActiveSetNoAssert();
1053 if (scripts == nullptr)
1055 if (scripts->Unlock())
1057 CAllThreadsScripts::Remove(scripts);
1062 ////////////////////////////////////////////////////////////////////////////////
1063 // wrap invokes with error handlers
1066 * @brief Display a message box with the plugin name and the error message
1068 * @note Use MessageBox instead of AfxMessageBox so we can set the caption.
1069 * VB/VBS plugins has an internal error handler, and a message box with caption,
1070 * and we try to reproduce it for other plugins.
1072 static void ShowPluginErrorMessage(IDispatch *piScript, LPTSTR description)
1074 PluginInfo * pInfo = CAllThreadsScripts::GetActiveSet()->GetPluginInfo(piScript);
1075 assert(pInfo != nullptr);
1076 assert(description != nullptr);
1077 AppErrorMessageBox(strutils::format(_T("%s: %s"), pInfo->m_name, description));
1081 * @brief safe invoke helper (by ordinal)
1083 * @note Free all variants passed to it (except ByRef ones)
1085 static HRESULT safeInvokeA(LPDISPATCH pi, VARIANT *ret, DISPID id, LPCCH op, ...)
1089 TCHAR errorText[500];
1090 bool bExceptionCatched = false;
1092 int nargs = LOBYTE((UINT_PTR)op);
1093 vector<VARIANT> args(nargs);
1096 for (vector<VARIANT>::iterator it = args.begin(); it != args.end(); ++it)
1097 *it = va_arg(list, VARIANT);
1104 h = invokeA(pi, ret, id, op, nargs == 0 ? nullptr : &args[0]);
1106 h = invokeA(pi, ret, id, op, (VARIANT *)(&op + 1));
1109 catch(SE_Exception& e)
1111 // structured exception are catched here thanks to class SE_Exception
1112 if (!(e.GetErrorMessage(errorText, 500, nullptr)))
1113 // don't localize this as we do not localize the known exceptions
1114 _tcscpy_safe(errorText, _T("Unknown CException"));
1115 bExceptionCatched = true;
1119 // don't localize this as we do not localize the known exceptions
1120 _tcscpy_safe(errorText, _T("Unknown C++ exception"));
1121 bExceptionCatched = true;
1124 if (bExceptionCatched)
1126 ShowPluginErrorMessage(pi, errorText);
1134 * @brief safe invoke helper (by function name)
1136 * @note Free all variants passed to it (except ByRef ones)
1138 static HRESULT safeInvokeW(LPDISPATCH pi, VARIANT *ret, LPCOLESTR silent, LPCCH op, ...)
1142 TCHAR errorText[500];
1143 bool bExceptionCatched = false;
1145 int nargs = LOBYTE((UINT_PTR)op);
1146 vector<VARIANT> args(nargs);
1149 for (vector<VARIANT>::iterator it = args.begin(); it != args.end(); ++it)
1150 *it = va_arg(list, VARIANT);
1157 h = invokeW(pi, ret, silent, op, nargs == 0 ? nullptr : &args[0]);
1159 h = invokeW(pi, ret, silent, op, (VARIANT *)(&op + 1));
1162 catch(SE_Exception& e)
1164 // structured exception are catched here thanks to class SE_Exception
1165 if (!(e.GetErrorMessage(errorText, 500, nullptr)))
1166 // don't localize this as we do not localize the known exceptions
1167 _tcscpy_safe(errorText, _T("Unknown CException"));
1168 bExceptionCatched = true;
1172 // don't localize this as we do not localize the known exceptions
1173 _tcscpy_safe(errorText, _T("Unknown C++ exception"));
1174 bExceptionCatched = true;
1177 if (bExceptionCatched)
1179 ShowPluginErrorMessage(pi, errorText);
1187 ////////////////////////////////////////////////////////////////////////////////
1188 // invoke for plugins
1194 * ----- about VariantClear -----
1195 * VariantClear is done in safeInvokeW/safeInvokeA except for :
1196 * - the returned value
1198 * note : BYREF arguments don't need VariantClear if the refered value
1199 * is deleted in the function destructor. Example :
1203 * vValue.plVal = &vValue;
1207 bool InvokePrediffBuffer(BSTR & bstrBuf, int & nChanged, IDispatch *piScript)
1209 UINT nBufSize = SysStringLen(bstrBuf);
1211 // prepare the arguments
1212 // argument text buffer by reference
1214 vpbstrBuf.vt = VT_BYREF | VT_BSTR;
1215 vpbstrBuf.pbstrVal = &bstrBuf;
1216 // argument buffer size by reference
1218 vpiSize.vt = VT_BYREF | VT_I4;
1219 vpiSize.plVal = (long*) &nBufSize;
1220 // argument flag changed (VT_BOOL is short)
1221 VARIANT_BOOL changed = 0;
1222 VARIANT vpboolChanged;
1223 vpboolChanged.vt = VT_BYREF | VT_BOOL;
1224 vpboolChanged.pboolVal = &changed;
1225 // argument return value (VT_BOOL is short)
1226 VARIANT vboolHandled;
1227 vboolHandled.vt = VT_BOOL;
1228 vboolHandled.boolVal = false;
1230 // invoke method by name, reverse order for arguments
1231 // for VC, if the invoked function changes the buffer address,
1232 // it must free the old buffer with SysFreeString
1233 // VB does it automatically
1234 // VARIANT_BOOL DiffingPreprocessW(BSTR * buffer, UINT * nSize, VARIANT_BOOL * bChanged)
1235 HRESULT h = ::safeInvokeW(piScript, &vboolHandled, L"PrediffBufferW", opFxn[3],
1236 vpboolChanged, vpiSize, vpbstrBuf);
1237 bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1238 if (bSuccess && changed)
1240 // remove trailing charracters in the rare case that bstrBuf was not resized
1241 if (SysStringLen(bstrBuf) != nBufSize)
1242 bSuccess = !FAILED(SysReAllocStringLen(&bstrBuf, bstrBuf, nBufSize));
1247 // clear the returned variant
1248 VariantClear(&vboolHandled);
1253 bool InvokeUnpackBuffer(VARIANT & array, int & nChanged, IDispatch *piScript, int & subcode)
1256 SafeArrayGetUBound(array.parray, 0, &nArraySize);
1259 // prepare the arguments
1260 // argument file buffer
1262 vparrayBuf.vt = VT_BYREF | VT_ARRAY | VT_UI1;
1263 vparrayBuf.pparray = &(array.parray);
1264 // argument buffer size by reference
1266 vpiSize.vt = VT_BYREF | VT_I4;
1267 vpiSize.plVal = (long*) &nArraySize;
1268 // argument flag changed (VT_BOOL is short)
1269 VARIANT_BOOL changed = 0;
1270 VARIANT vpboolChanged;
1271 vpboolChanged.vt = VT_BYREF | VT_BOOL;
1272 vpboolChanged.pboolVal = &changed;
1273 // argument subcode by reference
1275 viSubcode.vt = VT_BYREF | VT_I4;
1276 viSubcode.plVal = (long*) &subcode;
1277 // argument return value (VT_BOOL is short)
1278 VARIANT vboolHandled;
1279 vboolHandled.vt = VT_BOOL;
1280 vboolHandled.boolVal = false;
1282 // invoke method by name, reverse order for arguments
1283 // VARIANT_BOOL UnpackBufferA(SAFEARRAY * array, UINT * nSize, VARIANT_BOOL * bChanged, UINT * subcode)
1284 HRESULT h = ::safeInvokeW(piScript, &vboolHandled, L"UnpackBufferA", opFxn[4],
1285 viSubcode, vpboolChanged, vpiSize, vparrayBuf);
1286 bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1287 if (bSuccess && changed)
1289 // remove trailing charracters if the array was not resized
1291 SafeArrayGetUBound(array.parray, 0, &nNewArraySize);
1294 if (nNewArraySize != nArraySize)
1296 SAFEARRAYBOUND sab = {static_cast<ULONG>(nArraySize), 0};
1297 SafeArrayRedim(array.parray, &sab);
1302 // clear the returned variant
1303 VariantClear(&vboolHandled);
1308 bool InvokePackBuffer(VARIANT & array, int & nChanged, IDispatch *piScript, int subcode)
1311 SafeArrayGetUBound(array.parray, 0, &nArraySize);
1314 // prepare the arguments
1315 // argument file buffer
1317 vparrayBuf.vt = VT_BYREF | VT_ARRAY | VT_UI1;
1318 vparrayBuf.pparray = &(array.parray);
1319 // argument buffer size by reference
1321 vpiSize.vt = VT_BYREF | VT_I4;
1322 vpiSize.plVal = (long*) &nArraySize;
1323 // argument flag changed (VT_BOOL is short)
1324 VARIANT_BOOL changed = 0;
1325 VARIANT vpboolChanged;
1326 vpboolChanged.vt = VT_BYREF | VT_BOOL;
1327 vpboolChanged.pboolVal = &changed;
1330 viSubcode.vt = VT_I4;
1331 viSubcode.lVal = subcode;
1332 // argument return value (VT_BOOL is short)
1333 VARIANT vboolHandled;
1334 vboolHandled.vt = VT_BOOL;
1335 vboolHandled.boolVal = false;
1337 // invoke method by name, reverse order for arguments
1338 // VARIANT_BOOL PackBufferA(SAFEARRAY * array, UINT * nSize, VARIANT_BOOL * bChanged, UINT subcode)
1339 HRESULT h = ::safeInvokeW(piScript, &vboolHandled, L"PackBufferA", opFxn[4],
1340 viSubcode, vpboolChanged, vpiSize, vparrayBuf);
1341 bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1342 if (bSuccess && changed)
1344 // remove trailing charracters if the array was not resized
1346 SafeArrayGetUBound(array.parray, 0, &nNewArraySize);
1349 if (nNewArraySize != nArraySize)
1351 SAFEARRAYBOUND sab = {static_cast<ULONG>(nArraySize), 0};
1352 SafeArrayRedim(array.parray, &sab);
1356 // clear the returned variant
1357 VariantClear(&vboolHandled);
1363 static bool unpack(const wchar_t *method, const String& source, const String& dest, int & nChanged, IDispatch *piScript, int & subCode)
1367 vbstrSrc.vt = VT_BSTR;
1368 vbstrSrc.bstrVal = SysAllocString(ucr::toUTF16(source).c_str());
1369 // argument transformed text
1371 vbstrDst.vt = VT_BSTR;
1372 vbstrDst.bstrVal = SysAllocString(ucr::toUTF16(dest).c_str());
1373 // argument subcode by reference
1375 vpiSubcode.vt = VT_BYREF | VT_I4;
1376 vpiSubcode.plVal = (long*) &subCode;
1377 // argument flag changed (VT_BOOL is short)
1378 VARIANT_BOOL changed = 0;
1379 VARIANT vpboolChanged;
1380 vpboolChanged.vt = VT_BYREF | VT_BOOL;
1381 vpboolChanged.pboolVal = &changed;
1382 // argument return value (VT_BOOL is short)
1383 VARIANT vboolHandled;
1384 vboolHandled.vt = VT_BOOL;
1385 vboolHandled.boolVal = false;
1387 // invoke method by name, reverse order for arguments
1388 // VARIANT_BOOL UnpackFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL * bChanged, INT * bSubcode)
1389 HRESULT h = ::safeInvokeW(piScript, &vboolHandled, method, opFxn[4],
1390 vpiSubcode, vpboolChanged, vbstrDst, vbstrSrc);
1391 bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1392 if (bSuccess && changed)
1395 // clear the returned variant
1396 VariantClear(&vboolHandled);
1401 static bool pack(const wchar_t *method, const String& source, const String& dest, int & nChanged, IDispatch *piScript, int & subCode)
1405 vbstrSrc.vt = VT_BSTR;
1406 vbstrSrc.bstrVal = SysAllocString(ucr::toUTF16(source).c_str());
1407 // argument transformed text
1409 vbstrDst.vt = VT_BSTR;
1410 vbstrDst.bstrVal = SysAllocString(ucr::toUTF16(dest).c_str());
1413 viSubcode.vt = VT_I4;
1414 viSubcode.lVal = subCode;
1415 // argument flag changed (VT_BOOL is short)
1416 VARIANT_BOOL changed = 0;
1417 VARIANT vpboolChanged;
1418 vpboolChanged.vt = VT_BYREF | VT_BOOL;
1419 vpboolChanged.pboolVal = &changed;
1420 // argument return value (VT_BOOL is short)
1421 VARIANT vboolHandled;
1422 vboolHandled.vt = VT_BOOL;
1423 vboolHandled.boolVal = false;
1425 // invoke method by name, reverse order for arguments
1426 // VARIANT_BOOL PackFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL * bChanged, INT bSubcode)
1427 HRESULT h = ::safeInvokeW(piScript, &vboolHandled, method, opFxn[4],
1428 viSubcode, vpboolChanged, vbstrDst, vbstrSrc);
1429 bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1430 if (bSuccess && changed)
1433 // clear the returned variant
1434 VariantClear(&vboolHandled);
1439 bool InvokeUnpackFile(const String& fileSource, const String& fileDest, int & nChanged, IDispatch *piScript, int & subCode)
1441 return unpack(L"UnpackFile", fileSource, fileDest, nChanged, piScript, subCode);
1444 bool InvokePackFile(const String& fileSource, const String& fileDest, int & nChanged, IDispatch *piScript, int subCode)
1446 return pack(L"PackFile", fileSource, fileDest, nChanged, piScript, subCode);
1449 bool InvokeUnpackFolder(const String& fileSource, const String& folderDest, int & nChanged, IDispatch *piScript, int & subCode)
1451 return unpack(L"UnpackFolder", fileSource, folderDest, nChanged, piScript, subCode);
1454 bool InvokePackFolder(const String& folderSource, const String& fileDest, int & nChanged, IDispatch *piScript, int subCode)
1456 return pack(L"PackFolder", folderSource, fileDest, nChanged, piScript, subCode);
1459 bool InvokePrediffFile(const String& fileSource, const String& fileDest, int & nChanged, IDispatch *piScript)
1463 vbstrSrc.vt = VT_BSTR;
1464 vbstrSrc.bstrVal = SysAllocString(ucr::toUTF16(fileSource).c_str());
1465 // argument transformed text
1467 vbstrDst.vt = VT_BSTR;
1468 vbstrDst.bstrVal = SysAllocString(ucr::toUTF16(fileDest).c_str());
1469 // argument flag changed (VT_BOOL is short)
1470 VARIANT_BOOL changed = 0;
1471 VARIANT vpboolChanged;
1472 vpboolChanged.vt = VT_BYREF | VT_BOOL;
1473 vpboolChanged.pboolVal = &changed;
1474 // argument return value (VT_BOOL is short)
1475 VARIANT vboolHandled;
1476 vboolHandled.vt = VT_BOOL;
1477 vboolHandled.boolVal = false;
1479 // invoke method by name, reverse order for arguments
1480 // VARIANT_BOOL PrediffFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL * bChanged)
1481 HRESULT h = ::safeInvokeW(piScript, &vboolHandled, L"PrediffFile", opFxn[3],
1482 vpboolChanged, vbstrDst, vbstrSrc);
1483 bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1484 if (bSuccess && changed)
1487 // clear the returned variant
1488 VariantClear(&vboolHandled);
1494 bool InvokeTransformText(String & text, int & changed, IDispatch *piScript, int fncId)
1498 pvPszBuf.vt = VT_BSTR;
1499 pvPszBuf.bstrVal = SysAllocString(ucr::toUTF16(text).c_str());
1500 // argument transformed text
1501 VARIANT vTransformed;
1502 vTransformed.vt = VT_BSTR;
1503 vTransformed.bstrVal = nullptr;
1505 // invoke method by ordinal
1506 // BSTR customFunction(BSTR text)
1507 HRESULT h = ::safeInvokeA(piScript, &vTransformed, fncId, opFxn[1], pvPszBuf);
1509 if (! FAILED(h) && vTransformed.bstrVal)
1511 text = ucr::toTString(vTransformed.bstrVal);
1517 // clear the returned variant
1518 VariantClear(&vTransformed);
1520 return (! FAILED(h));
1523 bool InvokeIsFolder(const String& path, IDispatch *piScript)
1527 vbstrPath.vt = VT_BSTR;
1528 vbstrPath.bstrVal = SysAllocString(ucr::toUTF16(path).c_str());
1530 VARIANT vboolHandled;
1531 vboolHandled.vt = VT_BOOL;
1532 vboolHandled.boolVal = false;
1534 // invoke method by name, reverse order for arguments
1535 // VARIANT_BOOL ShowSettingsDialog()
1536 HRESULT h = ::safeInvokeW(piScript, &vboolHandled, L"IsFolder", opFxn[1], vbstrPath);
1537 bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1539 // clear the returned variant
1540 VariantClear(&vboolHandled);
1545 bool InvokeShowSettingsDialog(IDispatch *piScript)
1547 VARIANT vboolHandled;
1548 vboolHandled.vt = VT_BOOL;
1549 vboolHandled.boolVal = false;
1551 // invoke method by name, reverse order for arguments
1552 // VARIANT_BOOL ShowSettingsDialog()
1553 HRESULT h = ::safeInvokeW(piScript, &vboolHandled, L"ShowSettingsDialog", opFxn[0]);
1554 bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1556 // clear the returned variant
1557 VariantClear(&vboolHandled);