OSDN Git Service

Merge with stable
[winmerge-jp/winmerge-jp.git] / Src / Plugins.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 //    WinMerge:  an interactive diff/merge utility
3 //    Copyright (C) 1997-2000  Thingamahoochie Software
4 //    Author: Dean Grimm
5 //
6 //    This program is free software; you can redistribute it and/or modify
7 //    it under the terms of the GNU General Public License as published by
8 //    the Free Software Foundation; either version 2 of the License, or
9 //    (at your option) any later version.
10 //
11 //    This program is distributed in the hope that it will be useful,
12 //    but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //    GNU General Public License for more details.
15 //
16 //    You should have received a copy of the GNU General Public License
17 //    along with this program; if not, write to the Free Software
18 //    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 //
20 /////////////////////////////////////////////////////////////////////////////
21 /**
22  *  @file Plugins.cpp
23  *
24  *  @brief Support for VBS Scriptlets, VB ActiveX DLL, VC++ COM DLL
25  */ 
26 // ID line follows -- this is updated by SVN
27 // $Id: Plugins.cpp 7052 2009-12-22 17:45:22Z kimmov $
28
29 #include "Plugins.h"
30 #define POCO_NO_UNWINDOWS 1
31 #include <vector>
32 #include <list>
33 #include <algorithm>
34 #include <cstdarg>
35 #include <cassert>
36 #include <Poco/Mutex.h>
37 #include <Poco/ScopedLock.h>
38 #include <Poco/RegularExpression.h>
39 #include <windows.h>
40 #include "MergeApp.h"
41 #include "FileTransform.h"
42 #include "FileFilterMgr.h"
43 #include "lwdisp.h"
44 #include "resource.h"
45 #include "Exceptions.h"
46 #include "RegKey.h"
47 #include "paths.h"
48 #include "Environment.h"
49 #include "FileFilter.h"
50
51 using std::vector;
52 using Poco::RegularExpression;
53 using Poco::FastMutex;
54 using Poco::ScopedLock;
55
56 static vector<String> theScriptletList;
57 /// Need to lock the *.sct so the user can't delete them
58 static vector<HANDLE> theScriptletHandleList;
59 static bool scriptletsLoaded=false;
60 static FastMutex scriptletsSem;
61
62 template<class T> struct AutoReleaser
63 {
64         AutoReleaser(T *ptr) : p(ptr) {}
65         ~AutoReleaser() { if (p) p->Release(); }
66         T *p;
67 };
68
69 ////////////////////////////////////////////////////////////////////////////////
70 /**
71  * @brief Check for the presence of Windows Script
72  *
73  * .sct plugins require this optional component
74  */
75 bool IsWindowsScriptThere()
76 {
77         CRegKeyEx keyFile;
78         if (!keyFile.QueryRegMachine(_T("SOFTWARE\\Classes\\scriptletfile\\AutoRegister")))
79                 return false;
80
81         String filename = keyFile.ReadString(_T(""), _T("")).c_str();
82         keyFile.Close();
83         if (filename.empty())
84                 return false;
85
86         return (paths_DoesPathExist(filename) == IS_EXISTING_FILE);
87 }
88
89 ////////////////////////////////////////////////////////////////////////////////
90 // scriptlet/activeX support for function names
91
92 // list the function IDs and names in a script or activeX dll
93 int GetFunctionsFromScript(IDispatch *piDispatch, vector<String>& namesArray, vector<int>& IdArray, INVOKEKIND wantedKind)
94 {
95         HRESULT hr;
96         UINT iValidFunc = 0;
97         if (piDispatch)
98         {
99                 ITypeInfo *piTypeInfo=0;
100                 unsigned  iTInfo = 0; // 0 for type information of IDispatch itself
101                 LCID  lcid=0; // locale for localized method names (ignore if no localized names)
102                 if (SUCCEEDED(hr = piDispatch->GetTypeInfo(iTInfo, lcid, &piTypeInfo)))
103                 {
104                         TYPEATTR *pTypeAttr=0;
105                         if (SUCCEEDED(hr = piTypeInfo->GetTypeAttr(&pTypeAttr)))
106                         {
107                                 // allocate arrays for the returned structures
108                                 // the names array is NULL terminated
109                                 namesArray.resize(pTypeAttr->cFuncs+1);
110                                 IdArray.resize(pTypeAttr->cFuncs+1);
111
112                                 UINT iMaxFunc = pTypeAttr->cFuncs - 1;
113                                 for (UINT iFunc = 0 ; iFunc <= iMaxFunc ; ++iFunc)
114                                 {
115                                         UINT iFuncDesc = iMaxFunc - iFunc;
116                                         FUNCDESC *pFuncDesc;
117                                         if (SUCCEEDED(hr = piTypeInfo->GetFuncDesc(iFuncDesc, &pFuncDesc)))
118                                         {
119                                                 // exclude properties
120                                                 // exclude IDispatch inherited methods
121                                                 if (pFuncDesc->invkind & wantedKind && !(pFuncDesc->wFuncFlags & 1))
122                                                 {
123                                                         BSTR bstrName;
124                                                         UINT cNames;
125                                                         if (SUCCEEDED(hr = piTypeInfo->GetNames(pFuncDesc->memid,
126                                                                 &bstrName, 1, &cNames)))
127                                                         {
128                                                                 IdArray[iValidFunc] = pFuncDesc->memid;
129                                                                 namesArray[iValidFunc] = ucr::toTString(bstrName);
130                                                                 iValidFunc ++;
131                                                         }
132                                                         SysFreeString(bstrName);
133                                                 }
134                                                 piTypeInfo->ReleaseFuncDesc(pFuncDesc);
135                                         }
136                                 }
137                                 piTypeInfo->ReleaseTypeAttr(pTypeAttr);
138                         }
139                         piTypeInfo->Release();
140                 }
141         }
142         return iValidFunc;
143 }
144
145 int GetMethodsFromScript(IDispatch *piDispatch, vector<String>& namesArray, vector<int> &IdArray)
146 {
147         return GetFunctionsFromScript(piDispatch, namesArray, IdArray, INVOKE_FUNC);
148 }
149 int GetPropertyGetsFromScript(IDispatch *piDispatch, vector<String>& namesArray, vector<int> &IdArray)
150 {
151         return GetFunctionsFromScript(piDispatch, namesArray, IdArray, INVOKE_PROPERTYGET);
152 }
153
154
155 // search a function name in a scriptlet or activeX dll
156 bool SearchScriptForMethodName(LPDISPATCH piDispatch, const wchar_t *functionName)
157 {
158         bool bFound = false;
159
160         vector<String> namesArray;
161         vector<int> IdArray;
162         int nFnc = GetMethodsFromScript(piDispatch, namesArray, IdArray);
163
164         String tfuncname = ucr::toTString(functionName);
165         int iFnc;
166         for (iFnc = 0 ; iFnc < nFnc ; iFnc++)
167         {
168                 if (namesArray[iFnc] == tfuncname)
169                         bFound = true;
170         }
171         return bFound;
172 }
173
174 // search a property name (with get interface) in a scriptlet or activeX dll
175 bool SearchScriptForDefinedProperties(IDispatch *piDispatch, const wchar_t *functionName)
176 {
177         bool bFound = false;
178
179         vector<String> namesArray;
180         vector<int> IdArray;
181         int nFnc = GetPropertyGetsFromScript(piDispatch, namesArray, IdArray);
182
183         String tfuncname = ucr::toTString(functionName);
184         int iFnc;
185         for (iFnc = 0 ; iFnc < nFnc ; iFnc++)
186         {
187                 if (namesArray[iFnc] == tfuncname)
188                         bFound = true;
189         }
190         return bFound;
191 }
192
193
194 int CountMethodsInScript(LPDISPATCH piDispatch)
195 {
196         vector<String> namesArray;
197         vector<int> IdArray;
198         int nFnc = GetMethodsFromScript(piDispatch, namesArray, IdArray);
199
200         return nFnc;
201 }
202
203 /** 
204  * @return ID of the function or -1 if no function with this index
205  */
206 int GetMethodIDInScript(LPDISPATCH piDispatch, int methodIndex)
207 {
208         int fncID;
209
210         vector<String> namesArray;
211         vector<int> IdArray;
212         int nFnc = GetMethodsFromScript(piDispatch, namesArray, IdArray);
213
214         if (methodIndex < nFnc)
215         {
216                 fncID = IdArray[methodIndex];
217         }
218         else
219         {
220                 fncID = -1;
221         }
222         
223         return fncID;
224 }
225
226 ////////////////////////////////////////////////////////////////////////////////
227 // find scripts/activeX for an event : each event is assigned to a subdirectory 
228
229
230 /**
231  * @brief Get a list of scriptlet file
232  *
233  * @return Returns an array of LPSTR
234  */
235 static void GetScriptletsAt(const String& sSearchPath, const String& extension, vector<String>& scriptlets )
236 {
237         WIN32_FIND_DATA ffi;
238         String strFileSpec = paths_ConcatPath(sSearchPath, _T("*") + extension);
239         HANDLE hff = FindFirstFile(strFileSpec.c_str(), &ffi);
240         
241         if (  hff != INVALID_HANDLE_VALUE )
242         {
243                 do
244                 {
245                         if (!(ffi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
246                         {
247                                 strFileSpec = paths_ConcatPath(sSearchPath, ffi.cFileName);
248                                 scriptlets.push_back(strFileSpec);  
249                         }
250                 }
251                 while (FindNextFile(hff, &ffi));
252                 FindClose(hff);
253         }
254 }
255
256 void PluginInfo::LoadFilterString()
257 {
258         m_filters.clear();
259
260         String sLine(m_filtersText);
261         String sPiece;
262
263         while(1)
264         {
265                 String::size_type pos = sLine.rfind(';');
266                 sPiece = sLine.substr(pos+1);
267                 if (pos == String::npos)
268                         pos = 0;
269                 sLine = sLine.substr(0, pos);
270                 if (sPiece.empty())
271                         break;
272
273                 sPiece = string_makeupper(string_trim_ws_begin(sPiece));
274
275                 int re_opts = 0;
276                 std::string regexString = ucr::toUTF8(sPiece);
277                 re_opts |= RegularExpression::RE_UTF8;
278                 try
279                 {
280                         m_filters.push_back(FileFilterElementPtr(new FileFilterElement(regexString, re_opts)));
281                 }
282                 catch (...)
283                 {
284                         // TODO:
285                 }
286         };
287 }
288
289
290 bool PluginInfo::TestAgainstRegList(const String& szTest)
291 {
292         if (m_filters.empty() || szTest.empty())
293                 return false;
294
295         String sLine = szTest;
296         String sPiece;
297
298         while(!sLine.empty())
299         {
300                 String::size_type pos = sLine.rfind('|');
301                 sPiece = sLine.substr(pos+1);
302                 if (pos == String::npos)
303                         pos = 0;
304                 sLine = sLine.substr(0, pos);
305                 if (sPiece.empty())
306                         continue;
307                 sPiece = string_makeupper(string_trim_ws_begin(sPiece));
308
309                 if (::TestAgainstRegList(&m_filters, sPiece))
310                         return true;
311         };
312
313         return false;
314 }
315
316 /**
317  * @brief Log technical explanation, in English, of script error
318  */
319 static void
320 ScriptletError(const String & scriptletFilepath, const wchar_t *transformationEvent, const TCHAR *szError)
321 {
322         String msg = _T("Plugin scriptlet error <")
323                 + scriptletFilepath
324                 + _T("> [")
325                 + ucr::toTString(transformationEvent)
326                 + _T("] ")
327                 + szError;
328     LogErrorString(msg);
329 }
330
331 /**
332  * @brief Tiny structure that remembers current scriptlet & event info for calling Log
333  */
334 struct ScriptInfo
335 {
336         ScriptInfo(const String & scriptletFilepath, const wchar_t *transformationEvent)
337                 : m_scriptletFilepath(scriptletFilepath)
338                 , m_transformationEvent(transformationEvent)
339         {
340         }
341         void Log(const TCHAR *szError)
342         {
343                 ScriptletError(m_scriptletFilepath, m_transformationEvent, szError);
344         }
345         const String & m_scriptletFilepath;
346         const wchar_t *m_transformationEvent;
347 };
348
349 /**
350  * @brief Try to load a plugin
351  *
352  * @return 1 if plugin handles this event, 0 if not, negatives for errors
353  */
354 int PluginInfo::LoadPlugin(const String & scriptletFilepath, const wchar_t *transformationEvent)
355 {
356         // set up object in case we need to log info
357         ScriptInfo scinfo(scriptletFilepath, transformationEvent);
358
359         // Search for the class "WinMergeScript"
360         LPDISPATCH lpDispatch = CreateDispatchBySource(scriptletFilepath.c_str(), L"WinMergeScript");
361         if (lpDispatch == 0)
362         {
363                 scinfo.Log(_T("WinMergeScript entry point not found"));
364                 return -10; // error
365         }
366
367         // Ensure that interface is released if any bad exit or exception
368         AutoReleaser<IDispatch> drv(lpDispatch);
369
370         // Is this plugin for this transformationEvent ?
371         VARIANT ret;
372         // invoke mandatory method get PluginEvent
373         VariantInit(&ret);
374         if (!SearchScriptForDefinedProperties(lpDispatch, L"PluginEvent"))
375         {
376                 scinfo.Log(_T("PluginEvent method missing"));
377                 return -20; // error
378         }
379         HRESULT h = ::invokeW(lpDispatch, &ret, L"PluginEvent", opGet[0], NULL);
380         if (FAILED(h) || ret.vt != VT_BSTR)
381         {
382                 scinfo.Log(     _T("Error accessing PluginEvent method"));
383                 return -30; // error
384         }
385         if (wcscmp(ret.bstrVal, transformationEvent) != 0)
386         {
387                 return 0; // doesn't handle this event
388         }
389         VariantClear(&ret);
390
391         // plugins PREDIFF or PACK_UNPACK : functions names are mandatory
392         // Check that the plugin offers the requested functions
393         // set the mode for the events which uses it
394         bool bFound = true;
395         if (wcscmp(transformationEvent, L"BUFFER_PREDIFF") == 0)
396         {
397                 bFound &= SearchScriptForMethodName(lpDispatch, L"PrediffBufferW");
398         }
399         else if (wcscmp(transformationEvent, L"FILE_PREDIFF") == 0)
400         {
401                 bFound &= SearchScriptForMethodName(lpDispatch, L"PrediffFile");
402         }
403         else if (wcscmp(transformationEvent, L"BUFFER_PACK_UNPACK") == 0)
404         {
405                 bFound &= SearchScriptForMethodName(lpDispatch, L"UnpackBufferA");
406                 bFound &= SearchScriptForMethodName(lpDispatch, L"PackBufferA");
407         }
408         else if (wcscmp(transformationEvent, L"FILE_PACK_UNPACK") == 0)
409         {
410                 bFound &= SearchScriptForMethodName(lpDispatch, L"UnpackFile");
411                 bFound &= SearchScriptForMethodName(lpDispatch, L"PackFile");
412         }
413         if (!bFound)
414         {
415                 // error (Plugin doesn't support the method as it claimed)
416                 scinfo.Log(_T("Plugin doesn't support the method as it claimed"));
417                 return -40; 
418         }
419
420         // plugins EDITOR_SCRIPT : functions names are free
421         // there may be several functions inside one script, count the number of functions
422         if (wcscmp(transformationEvent, L"EDITOR_SCRIPT") == 0)
423         {
424                 m_nFreeFunctions = CountMethodsInScript(lpDispatch);
425                 if (m_nFreeFunctions == 0)
426                         // error (Plugin doesn't offer any method, what is this ?)
427                         return -50;
428         }
429
430
431         // get optional property PluginDescription
432         if (SearchScriptForDefinedProperties(lpDispatch, L"PluginDescription"))
433         {
434                 h = ::invokeW(lpDispatch, &ret, L"PluginDescription", opGet[0], NULL);
435                 if (FAILED(h) || ret.vt != VT_BSTR)
436                 {
437                         scinfo.Log(_T("Plugin had PluginDescription property, but error getting its value"));
438                         return -60; // error (Plugin had PluginDescription property, but error getting its value)
439                 }
440                 m_description = ucr::toTString(ret.bstrVal);
441         }
442         else
443         {
444                 // no description, use filename
445                 m_description = paths_FindFileName(scriptletFilepath);
446         }
447         VariantClear(&ret);
448
449         // get PluginFileFilters
450         bool hasPluginFileFilters = false;
451         if (SearchScriptForDefinedProperties(lpDispatch, L"PluginFileFilters"))
452         {
453                 h = ::invokeW(lpDispatch, &ret, L"PluginFileFilters", opGet[0], NULL);
454                 if (FAILED(h) || ret.vt != VT_BSTR)
455                 {
456                         scinfo.Log(_T("Plugin had PluginFileFilters property, but error getting its value"));
457                         return -70; // error (Plugin had PluginFileFilters property, but error getting its value)
458                 }
459                 m_filtersText = ucr::toTString(ret.bstrVal);
460                 hasPluginFileFilters = true;
461         }
462         else
463         {
464                 m_bAutomatic = false;
465                 m_filtersText = _T(".");
466         }
467         VariantClear(&ret);
468
469         // get optional property PluginIsAutomatic
470         if (SearchScriptForDefinedProperties(lpDispatch, L"PluginIsAutomatic"))
471         {
472                 h = ::invokeW(lpDispatch, &ret, L"PluginIsAutomatic", opGet[0], NULL);
473                 if (FAILED(h) || ret.vt != VT_BOOL)
474                 {
475                         scinfo.Log(_T("Plugin had PluginIsAutomatic property, but error getting its value"));
476                         return -80; // error (Plugin had PluginIsAutomatic property, but error getting its value)
477                 }
478                 m_bAutomatic = !!ret.boolVal;
479         }
480         else
481         {
482                 if (hasPluginFileFilters)
483                 {
484                         scinfo.Log(_T("Plugin had PluginFileFilters property, but lacked PluginIsAutomatic property"));
485                         // PluginIsAutomatic property is mandatory for Plugins with PluginFileFilters property
486                         return -90;
487                 }
488                 // default to false when Plugin doesn't have property
489                 m_bAutomatic = false;
490         }
491         VariantClear(&ret);
492
493         LoadFilterString();
494
495         // keep the filename
496         m_name = paths_FindFileName(scriptletFilepath);
497
498         // Clear the autorelease holder
499         drv.p = NULL;
500
501         m_lpDispatch = lpDispatch;
502
503         m_filepath = scriptletFilepath;
504
505         return 1;
506 }
507
508 static void ReportPluginLoadFailure(const String & scriptletFilepath, const wchar_t *transformationEvent)
509 {
510         String sEvent = ucr::toTString(transformationEvent);
511         AppErrorMessageBox(string_format(_T("Exception loading plugin for event: %s\r\n%s"), sEvent.c_str(), scriptletFilepath.c_str()));
512 }
513
514 /**
515  * @brief Guard call to LoadPlugin with Windows SEH to trap GPFs
516  *
517  * @return same as LoadPlugin (1=has event, 0=doesn't have event, errors are negative)
518  */
519 static int LoadPluginWrapper(PluginInfo & plugin, const String & scriptletFilepath, const wchar_t *transformationEvent)
520 {
521         SE_Handler seh;
522         try
523         {
524                 return plugin.LoadPlugin(scriptletFilepath, transformationEvent);
525         }
526         catch (SE_Exception&)
527         {
528                 ReportPluginLoadFailure(scriptletFilepath, transformationEvent);
529         }
530         return false;
531 }
532
533 /**
534  * @brief Return list of all candidate plugins in module path
535  *
536  * Computes list only the first time, and caches it.
537  * Lock the plugins *.sct (.ocx and .dll are locked when the interface is created)
538  */
539 static vector<String>& LoadTheScriptletList()
540 {
541         FastMutex::ScopedLock lock(scriptletsSem);
542         if (!scriptletsLoaded)
543         {
544                 String path = paths_ConcatPath(env_GetProgPath(), _T("MergePlugins"));
545
546                 if (IsWindowsScriptThere())
547                         GetScriptletsAt(path, _T(".sct"), theScriptletList );           // VBS/JVS scriptlet
548                 else
549                         LogErrorString(_T("\n  .sct plugins disabled (Windows Script Host not found)"));
550                 GetScriptletsAt(path, _T(".ocx"), theScriptletList );           // VB COM object
551                 GetScriptletsAt(path, _T(".dll"), theScriptletList );           // VC++ COM object
552                 scriptletsLoaded = true;
553
554                 // lock the *.sct to avoid them being deleted/moved away
555                 int i;
556                 for (i = 0 ; i < theScriptletList.size() ; i++)
557                 {
558                         String scriptlet = theScriptletList.at(i);
559                         if (scriptlet.length() > 4 && string_compare_nocase(scriptlet.substr(scriptlet.length() - 4), _T(".sct")) != 0)
560                         {
561                                 // don't need to lock this file
562                                 theScriptletHandleList.push_back(NULL);
563                                 continue;
564                         }
565
566                         HANDLE hFile;
567                         hFile=CreateFile(scriptlet.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
568                                 0, NULL);
569                         if (hFile == INVALID_HANDLE_VALUE)
570                         {
571                                 theScriptletList.erase(theScriptletList.begin() + i);
572                                 i --;
573                         }
574                         else
575                         {
576                                 theScriptletHandleList.push_back(hFile);
577                         }
578                 }
579         }
580         return theScriptletList;
581 }
582 /**
583  * @brief Delete the scriptlet list and delete locks to *.sct
584  *
585  * Allow to load it again
586  */
587 static void UnloadTheScriptletList()
588 {
589         FastMutex::ScopedLock lock(scriptletsSem);
590         if (scriptletsLoaded)
591         {
592                 int i;
593                 for (i = 0 ; i < theScriptletHandleList.size() ; i++)
594                 {
595                         HANDLE hFile = theScriptletHandleList.at(i);
596                         if (hFile != 0)
597                                 CloseHandle(hFile);
598                 }
599
600                 theScriptletHandleList.clear();
601                 theScriptletList.clear();
602                 scriptletsLoaded = false;
603         }
604 }
605
606 /**
607  * @brief Remove a candidate plugin from the cache
608  */
609 static void RemoveScriptletCandidate(const String &scriptletFilepath)
610 {
611         for (int i=0; i<theScriptletList.size(); ++i)
612         {
613                 if (scriptletFilepath == theScriptletList[i])
614                 {
615                         HANDLE hFile = theScriptletHandleList.at(i);
616                         if (hFile != 0)
617                                 CloseHandle(hFile);
618
619                         theScriptletHandleList.erase(theScriptletHandleList.begin() + i);
620                         theScriptletList.erase(theScriptletList.begin() + i);
621                         return;
622                 }
623         }
624 }
625
626 /** 
627  * @brief Get available scriptlets for an event
628  *
629  * @return Returns an array of valid LPDISPATCH
630  */
631 static PluginArray * GetAvailableScripts( const wchar_t *transformationEvent, bool getScriptletsToo ) 
632 {
633         vector<String>& scriptlets = LoadTheScriptletList();
634
635         PluginArray * pPlugins = new PluginArray;
636
637         int i;
638         std::list<String> badScriptlets;
639         for (i = 0 ; i < scriptlets.size() ; i++)
640         {
641                 // Note all the info about the plugin
642                 PluginInfoPtr plugin(new PluginInfo);
643
644                 String scriptletFilepath = scriptlets.at(i);
645                 int rtn = LoadPluginWrapper(*plugin.get(), scriptletFilepath, transformationEvent);
646                 if (rtn == 1)
647                 {
648                         // Plugin has this event
649                         pPlugins->push_back(plugin);
650                 }
651                 else if (rtn < 0)
652                 {
653                         // Plugin is bad
654                         badScriptlets.push_back(scriptletFilepath);
655                 }
656         }
657
658         // Remove any bad plugins from the cache
659         // This might be a good time to see if the user wants to abort or continue
660         while (!badScriptlets.empty())
661         {
662                 RemoveScriptletCandidate(badScriptlets.front());
663                 badScriptlets.pop_front();
664         }
665
666         return pPlugins;
667 }
668
669 static void FreeAllScripts(PluginArrayPtr& pArray) 
670 {
671         pArray->clear();
672         pArray.reset();
673 }
674
675 ////////////////////////////////////////////////////////////////////////////////////
676 // class CScriptsOfThread : cache the interfaces during the thread life
677
678 CScriptsOfThread::CScriptsOfThread()
679 {
680         // count number of events
681         int i;
682         for (i = 0 ;  ; i ++)
683                 if (TransformationCategories[i] == NULL)
684                         break;
685         nTransformationEvents = i;
686
687         // initialize the thread data
688         m_nThreadId = GetCurrentThreadId();
689         m_nLocks = 0;
690         // initialize the plugins pointers
691         typedef PluginArray * LPPluginArray;
692         m_aPluginsByEvent.resize(nTransformationEvents);
693         // CoInitialize the thread, keep the returned value for the destructor 
694         hrInitialize = CoInitialize(NULL);
695         assert(hrInitialize == S_OK || hrInitialize == S_FALSE);
696 }
697
698 CScriptsOfThread::~CScriptsOfThread()
699 {
700         if (hrInitialize == S_OK || hrInitialize == S_FALSE)
701                 CoUninitialize();
702
703         FreeAllScripts();
704 }
705
706 bool CScriptsOfThread::bInMainThread()
707 {
708         return (CAllThreadsScripts::bInMainThread(this));
709 }
710
711 PluginArray * CScriptsOfThread::GetAvailableScripts(const wchar_t *transformationEvent)
712 {
713         int i;
714         for (i = 0 ; i < nTransformationEvents ; i ++)
715                 if (wcscmp(transformationEvent, TransformationCategories[i]) == 0)
716                 {
717                         if (m_aPluginsByEvent[i] == NULL)
718                                 m_aPluginsByEvent[i].reset(::GetAvailableScripts(transformationEvent, bInMainThread()));
719                         return m_aPluginsByEvent[i].get();
720                 }
721         // return a pointer to an empty list
722         static PluginArray noPlugin;
723         return &noPlugin;
724 }
725
726
727
728 void CScriptsOfThread::FreeAllScripts()
729 {
730         // release all the scripts of the thread
731         int i;
732         for (i = 0 ; i < nTransformationEvents ; i++)
733                 if (m_aPluginsByEvent[i])
734                         ::FreeAllScripts(m_aPluginsByEvent[i]);
735
736         // force to reload the scriptlet list
737         UnloadTheScriptletList();
738 }
739
740 void CScriptsOfThread::FreeScriptsForEvent(const wchar_t *transformationEvent)
741 {
742         int i;
743         for (i = 0 ; i < nTransformationEvents ; i ++)
744                 if (wcscmp(transformationEvent, TransformationCategories[i]) == 0)
745                 {
746                         if (m_aPluginsByEvent[i])
747                                 ::FreeAllScripts(m_aPluginsByEvent[i]);
748                         return;
749                 }
750 }
751
752
753 PluginInfo * CScriptsOfThread::GetPluginByName(const wchar_t *transformationEvent, const String& name)
754 {
755         int i;
756         for (i = 0 ; i < nTransformationEvents ; i ++)
757                 if (wcscmp(transformationEvent, TransformationCategories[i]) == 0)
758                 {
759                         if (m_aPluginsByEvent[i] == NULL)
760                                 m_aPluginsByEvent[i].reset(::GetAvailableScripts(transformationEvent, bInMainThread()));
761
762                         for (int j = 0 ; j <  m_aPluginsByEvent[i]->size() ; j++)
763                                 if (m_aPluginsByEvent[i]->at(j)->m_name == name)
764                                         return m_aPluginsByEvent[i]->at(j).get();
765                 }
766         return NULL;
767 }
768
769 PluginInfo *  CScriptsOfThread::GetPluginInfo(LPDISPATCH piScript)
770 {
771         int i, j;
772         for (i = 0 ; i < nTransformationEvents ; i ++) 
773         {
774                 if (m_aPluginsByEvent[i] == NULL)
775                         continue;
776                 const PluginArrayPtr& pArray = m_aPluginsByEvent[i];
777                 for (j = 0 ; j < pArray->size() ; j++)
778                         if ((*pArray)[j]->m_lpDispatch == piScript)
779                                 return (*pArray)[j].get();
780         }
781
782         return NULL;
783 }
784
785 ////////////////////////////////////////////////////////////////////////////////////
786 // class CAllThreadsScripts : array of CScriptsOfThread, one per active thread
787
788 std::vector<CScriptsOfThread *> CAllThreadsScripts::m_aAvailableThreads;
789 FastMutex m_aAvailableThreadsLock;
790
791 void CAllThreadsScripts::Add(CScriptsOfThread * scripts)
792 {
793         FastMutex::ScopedLock lock(m_aAvailableThreadsLock);
794         // add the thread in the array
795
796         // register in the array
797         m_aAvailableThreads.push_back(scripts);
798 }
799
800 void CAllThreadsScripts::Remove(CScriptsOfThread * scripts)
801 {
802         FastMutex::ScopedLock lock(m_aAvailableThreadsLock);
803         // unregister from the list
804         std::vector<CScriptsOfThread *>::iterator it;
805         for (it =  m_aAvailableThreads.begin(); it != m_aAvailableThreads.end(); ++it)
806                 if ((*it) == scripts)
807                 {
808                         m_aAvailableThreads.erase(it);
809                         break;
810                 }
811 }
812
813 CScriptsOfThread * CAllThreadsScripts::GetActiveSet()
814 {
815         FastMutex::ScopedLock lock(m_aAvailableThreadsLock);
816         unsigned long nThreadId = GetCurrentThreadId();
817         int i;
818         for (i = 0 ; i < m_aAvailableThreads.size() ; i++)
819                 if (m_aAvailableThreads[i] && m_aAvailableThreads[i]->m_nThreadId == nThreadId)
820                         return m_aAvailableThreads[i];
821         assert(0);
822         return NULL;
823 }
824 CScriptsOfThread * CAllThreadsScripts::GetActiveSetNoAssert()
825 {
826         FastMutex::ScopedLock lock(m_aAvailableThreadsLock);
827         unsigned long nThreadId = GetCurrentThreadId();
828         int i;
829         for (i = 0 ; i < m_aAvailableThreads.size() ; i++)
830                 if (m_aAvailableThreads[i] && m_aAvailableThreads[i]->m_nThreadId == nThreadId)
831                         return m_aAvailableThreads[i];
832         return NULL;
833 }
834
835 bool CAllThreadsScripts::bInMainThread(CScriptsOfThread * scripts)
836 {
837         FastMutex::ScopedLock lock(m_aAvailableThreadsLock);
838         return (scripts == m_aAvailableThreads[0]);
839 }
840
841 ////////////////////////////////////////////////////////////////////////////////////
842 // class CAssureScriptsForThread : control creation/destruction of CScriptsOfThread
843
844 CAssureScriptsForThread::CAssureScriptsForThread()
845 {
846         CScriptsOfThread * scripts = CAllThreadsScripts::GetActiveSetNoAssert();
847         if (scripts == NULL)
848         {
849                 scripts = new CScriptsOfThread;
850                 // insert the script in the repository
851                 CAllThreadsScripts::Add(scripts);
852         }
853         scripts->Lock();
854 }
855 CAssureScriptsForThread::~CAssureScriptsForThread()
856 {
857         CScriptsOfThread * scripts = CAllThreadsScripts::GetActiveSetNoAssert();
858         if (scripts == NULL)
859                 return;
860         if (scripts->Unlock() == true)
861         {
862                 CAllThreadsScripts::Remove(scripts);
863                 delete scripts;
864         }
865 }
866
867 ////////////////////////////////////////////////////////////////////////////////
868 // reallocation, take care of flag bWriteable
869
870 static void reallocBuffer(LPSTR & pszBuf, UINT & nOldSize, UINT nSize, bool bWriteable)
871 {
872         if (!bWriteable)
873                 // alloc a new buffer
874                 pszBuf = (LPSTR) malloc(nSize);
875         else if (nSize > nOldSize) 
876         {
877                 // free the previous buffer, alloc a new one (so we don't copy the old values)
878                 free(pszBuf);
879                 pszBuf = (LPSTR) malloc(nSize);
880         }
881         else
882                 // just truncate the buffer
883                 pszBuf = (LPSTR) realloc(pszBuf, nSize);
884         nOldSize = nSize;
885 }
886 static void reallocBuffer(LPWSTR & pszBuf, UINT & nOldSize, UINT nSize, bool bWriteable)
887 {
888         if (!bWriteable)
889                 // alloc a new buffer
890                 pszBuf = (LPWSTR) malloc(nSize*sizeof(WCHAR));
891         else if (nSize > nOldSize) 
892         {
893                 // free the previous buffer, alloc a new one (so we don't copy the old values)
894                 free(pszBuf);
895                 pszBuf = (LPWSTR) malloc(nSize*sizeof(WCHAR));
896         }
897         else
898                 // just truncate the buffer
899                 pszBuf = (LPWSTR) realloc(pszBuf, nSize*sizeof(WCHAR));
900         nOldSize = nSize;
901 }
902
903
904 ////////////////////////////////////////////////////////////////////////////////
905 // wrap invokes with error handlers
906
907 /**
908  * @brief Display a message box with the plugin name and the error message
909  *
910  * @note Use MessageBox instead of AfxMessageBox so we can set the caption.
911  * VB/VBS plugins has an internal error handler, and a message box with caption,
912  * and we try to reproduce it for other plugins.
913  */
914 static void ShowPluginErrorMessage(IDispatch *piScript, LPTSTR description)
915 {
916         PluginInfo * pInfo = CAllThreadsScripts::GetActiveSet()->GetPluginInfo(piScript);
917         assert(pInfo != NULL);
918         assert(description != NULL);    
919         AppErrorMessageBox(string_format(_T("%s: %s"), pInfo->m_name.c_str(), description));
920 }
921
922 /**
923  * @brief safe invoke helper (by ordinal)
924  *
925  * @note Free all variants passed to it (except ByRef ones) 
926  */
927 static HRESULT safeInvokeA(LPDISPATCH pi, VARIANT *ret, DISPID id, LPCCH op, ...)
928 {
929         HRESULT h;
930         SE_Handler seh;
931         TCHAR errorText[500];
932         bool bExceptionCatched = false; 
933 #ifdef WIN64
934         int nargs = LOBYTE((UINT_PTR)op);
935         vector<VARIANT> args(nargs);
936         va_list list;
937         va_start(list, op);
938         for (vector<VARIANT>::iterator it = args.begin(); it != args.end(); ++it)
939                 *it = va_arg(list, VARIANT);
940         va_end(list);
941 #endif
942
943         try 
944         {
945 #ifdef WIN64
946                 h = invokeA(pi, ret, id, op, nargs == 0 ? NULL : &args[0]);
947 #else
948                 h = invokeA(pi, ret, id, op, (VARIANT *)(&op + 1));
949 #endif
950         }
951         catch(SE_Exception& e) 
952         {
953                 // structured exception are catched here thanks to class SE_Exception
954                 if (!(e.GetErrorMessage(errorText, 500, NULL)))
955                         // don't localize this as we do not localize the known exceptions
956                         _tcscpy(errorText, _T("Unknown CException"));
957                 bExceptionCatched = true;
958         }
959         catch(...) 
960         {
961                 // don't localize this as we do not localize the known exceptions
962                 _tcscpy(errorText, _T("Unknown C++ exception"));
963                 bExceptionCatched = true;
964         }
965
966         if (bExceptionCatched)
967         {
968                 ShowPluginErrorMessage(pi, errorText);
969                 // set h to FAILED
970                 h = -1;
971         }
972
973         return h;
974 }
975 /**
976  * @brief safe invoke helper (by function name)
977  *
978  * @note Free all variants passed to it (except ByRef ones) 
979  */
980 static HRESULT safeInvokeW(LPDISPATCH pi, VARIANT *ret, LPCOLESTR silent, LPCCH op, ...)
981 {
982         HRESULT h;
983         SE_Handler seh;
984         TCHAR errorText[500];
985         bool bExceptionCatched = false;
986 #ifdef WIN64
987         int nargs = LOBYTE((UINT_PTR)op);
988         vector<VARIANT> args(nargs);
989         va_list list;
990         va_start(list, op);
991         for (vector<VARIANT>::iterator it = args.begin(); it != args.end(); ++it)
992                 *it = va_arg(list, VARIANT);
993         va_end(list);
994 #endif
995         
996         try 
997         {
998 #ifdef WIN64
999                 h = invokeW(pi, ret, silent, op, nargs == 0 ? NULL : &args[0]);
1000 #else
1001                 h = invokeW(pi, ret, silent, op, (VARIANT *)(&op + 1));
1002 #endif
1003         }
1004         catch(SE_Exception& e) 
1005         {
1006                 // structured exception are catched here thanks to class SE_Exception
1007                 if (!(e.GetErrorMessage(errorText, 500, NULL)))
1008                         // don't localize this as we do not localize the known exceptions
1009                         _tcscpy(errorText, _T("Unknown CException"));
1010                 bExceptionCatched = true;
1011         }
1012         catch(...) 
1013         {
1014                 // don't localize this as we do not localize the known exceptions
1015                 _tcscpy(errorText, _T("Unknown C++ exception"));
1016                 bExceptionCatched = true;
1017         }
1018
1019         if (bExceptionCatched)
1020         {
1021                 ShowPluginErrorMessage(pi, errorText);
1022                 // set h to FAILED
1023                 h = -1;
1024         }
1025
1026         return h;
1027 }
1028
1029 ////////////////////////////////////////////////////////////////////////////////
1030 // invoke for plugins
1031
1032 /*
1033  * ----- about VariantClear -----
1034  * VariantClear is done in safeInvokeW/safeInvokeA except for :
1035  * - the returned value
1036  * - BYREF arguments
1037  * note : BYREF arguments don't need VariantClear if the refered value
1038  * is deleted in the function destructor. Example :
1039  * {
1040  *   int Value;
1041  *   VARIANT vValue;
1042  *   vValue.plVal = &vValue;
1043  *   ...
1044  */
1045
1046 bool InvokePrediffBuffer(BSTR & bstrBuf, int & nChanged, IDispatch *piScript)
1047 {
1048         UINT nBufSize = SysStringLen(bstrBuf);
1049
1050         // prepare the arguments
1051         // argument text buffer by reference
1052         VARIANT vpbstrBuf;
1053         vpbstrBuf.vt = VT_BYREF | VT_BSTR;
1054         vpbstrBuf.pbstrVal = &bstrBuf;
1055         // argument buffer size by reference
1056         VARIANT vpiSize;
1057         vpiSize.vt = VT_BYREF | VT_I4;
1058         vpiSize.plVal = (long*) &nBufSize;
1059         // argument flag changed (VT_BOOL is short)
1060         VARIANT_BOOL changed = 0;
1061         VARIANT vpboolChanged;
1062         vpboolChanged.vt = VT_BYREF | VT_BOOL;
1063         vpboolChanged.pboolVal = &changed;
1064         // argument return value (VT_BOOL is short)
1065         VARIANT vboolHandled;
1066         vboolHandled.vt = VT_BOOL;
1067         vboolHandled.boolVal = false;
1068
1069         // invoke method by name, reverse order for arguments
1070         // for VC, if the invoked function changes the buffer address, 
1071         // it must free the old buffer with SysFreeString
1072         // VB does it automatically
1073         // VARIANT_BOOL DiffingPreprocessW(BSTR * buffer, UINT * nSize, VARIANT_BOOL * bChanged)
1074         HRESULT h = ::safeInvokeW(piScript,     &vboolHandled, L"PrediffBufferW", opFxn[3], 
1075                             vpboolChanged, vpiSize, vpbstrBuf);
1076         bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1077         if (bSuccess && changed)
1078         {
1079                 // remove trailing charracters in the rare case that bstrBuf was not resized 
1080                 if (SysStringLen(bstrBuf) != nBufSize)
1081                         bSuccess = !FAILED(SysReAllocStringLen(&bstrBuf, bstrBuf, nBufSize));
1082                 if (bSuccess)
1083                         nChanged ++;
1084         }
1085
1086         // clear the returned variant
1087         VariantClear(&vboolHandled);
1088
1089         return  (bSuccess);
1090 }
1091
1092 bool InvokeUnpackBuffer(VARIANT & array, int & nChanged, IDispatch *piScript, int & subcode)
1093 {
1094         LONG nArraySize;
1095         SafeArrayGetUBound(array.parray, 0, &nArraySize);
1096         ++nArraySize;
1097
1098         // prepare the arguments
1099         // argument file buffer
1100         VARIANT vparrayBuf;
1101         vparrayBuf.vt = VT_BYREF | VT_ARRAY | VT_UI1;
1102         vparrayBuf.pparray = &(array.parray);
1103         // argument buffer size by reference
1104         VARIANT vpiSize;
1105         vpiSize.vt = VT_BYREF | VT_I4;
1106         vpiSize.plVal = (long*) &nArraySize;
1107         // argument flag changed (VT_BOOL is short)
1108         VARIANT_BOOL changed = 0;
1109         VARIANT vpboolChanged;
1110         vpboolChanged.vt = VT_BYREF | VT_BOOL;
1111         vpboolChanged.pboolVal = &changed;
1112         // argument subcode by reference
1113         VARIANT viSubcode;
1114         viSubcode.vt = VT_BYREF | VT_I4;
1115         viSubcode.plVal = (long*) &subcode;
1116         // argument return value (VT_BOOL is short)
1117         VARIANT vboolHandled;
1118         vboolHandled.vt = VT_BOOL;
1119         vboolHandled.boolVal = false;
1120
1121         // invoke method by name, reverse order for arguments
1122         // VARIANT_BOOL UnpackBufferA(SAFEARRAY * array, UINT * nSize, VARIANT_BOOL * bChanged, UINT * subcode)
1123         HRESULT h = ::safeInvokeW(piScript,     &vboolHandled, L"UnpackBufferA", opFxn[4], 
1124                             viSubcode, vpboolChanged, vpiSize, vparrayBuf);
1125         bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1126         if (bSuccess && changed)
1127         {
1128                 // remove trailing charracters if the array was not resized
1129                 LONG nNewArraySize;
1130                 SafeArrayGetUBound(array.parray, 0, &nNewArraySize);
1131                 ++nNewArraySize;
1132
1133                 if (nNewArraySize != nArraySize)
1134                 {
1135                         SAFEARRAYBOUND sab = {nArraySize, 0};
1136                         SafeArrayRedim(array.parray, &sab);
1137                 }
1138                 nChanged ++;
1139         }
1140
1141         // clear the returned variant
1142         VariantClear(&vboolHandled);
1143
1144         return  (bSuccess);
1145 }
1146
1147 bool InvokePackBuffer(VARIANT & array, int & nChanged, IDispatch *piScript, int subcode)
1148 {
1149         LONG nArraySize;
1150         SafeArrayGetUBound(array.parray, 0, &nArraySize);
1151         ++nArraySize;
1152
1153         // prepare the arguments
1154         // argument file buffer
1155         VARIANT vparrayBuf;
1156         vparrayBuf.vt = VT_BYREF | VT_ARRAY | VT_UI1;
1157         vparrayBuf.pparray = &(array.parray);
1158         // argument buffer size by reference
1159         VARIANT vpiSize;
1160         vpiSize.vt = VT_BYREF | VT_I4;
1161         vpiSize.plVal = (long*) &nArraySize;
1162         // argument flag changed (VT_BOOL is short)
1163         VARIANT_BOOL changed = 0;
1164         VARIANT vpboolChanged;
1165         vpboolChanged.vt = VT_BYREF | VT_BOOL;
1166         vpboolChanged.pboolVal = &changed;
1167         // argument subcode
1168         VARIANT viSubcode;
1169         viSubcode.vt = VT_I4;
1170         viSubcode.lVal = subcode;
1171         // argument return value (VT_BOOL is short)
1172         VARIANT vboolHandled;
1173         vboolHandled.vt = VT_BOOL;
1174         vboolHandled.boolVal = false;
1175
1176         // invoke method by name, reverse order for arguments
1177         // VARIANT_BOOL PackBufferA(SAFEARRAY * array, UINT * nSize, VARIANT_BOOL * bChanged, UINT subcode)
1178         HRESULT h = ::safeInvokeW(piScript,     &vboolHandled, L"PackBufferA", opFxn[4], 
1179                             viSubcode, vpboolChanged, vpiSize, vparrayBuf);
1180         bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1181         if (bSuccess && changed)
1182         {
1183                 // remove trailing charracters if the array was not resized
1184                 LONG nNewArraySize;
1185                 SafeArrayGetUBound(array.parray, 0, &nNewArraySize);
1186                 ++nNewArraySize;
1187
1188                 if (nNewArraySize != nArraySize)
1189                 {
1190                         SAFEARRAYBOUND sab = {nArraySize, 0};
1191                         SafeArrayRedim(array.parray, &sab);
1192                 }
1193         }
1194
1195         // clear the returned variant
1196         VariantClear(&vboolHandled);
1197
1198         return  (bSuccess);
1199 }
1200
1201
1202 bool InvokeUnpackFile(const String& fileSource, const String& fileDest, int & nChanged, IDispatch *piScript, int & subCode)
1203 {
1204         // argument text  
1205         VARIANT vbstrSrc;
1206         vbstrSrc.vt = VT_BSTR;
1207         vbstrSrc.bstrVal = SysAllocString(ucr::toUTF16(fileSource).c_str());
1208         // argument transformed text 
1209         VARIANT vbstrDst;
1210         vbstrDst.vt = VT_BSTR;
1211         vbstrDst.bstrVal = SysAllocString(ucr::toUTF16(fileDest).c_str());
1212         // argument subcode by reference
1213         VARIANT vpiSubcode;
1214         vpiSubcode.vt = VT_BYREF | VT_I4;
1215         vpiSubcode.plVal = (long*) &subCode;
1216         // argument flag changed (VT_BOOL is short)
1217         VARIANT_BOOL changed = 0;
1218         VARIANT vpboolChanged;
1219         vpboolChanged.vt = VT_BYREF | VT_BOOL;
1220         vpboolChanged.pboolVal = &changed;
1221         // argument return value (VT_BOOL is short)
1222         VARIANT vboolHandled;
1223         vboolHandled.vt = VT_BOOL;
1224         vboolHandled.boolVal = false;
1225
1226         // invoke method by name, reverse order for arguments
1227         // VARIANT_BOOL UnpackFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL * bChanged, INT * bSubcode)
1228         HRESULT h = ::safeInvokeW(piScript,     &vboolHandled, L"UnpackFile", opFxn[4], 
1229                             vpiSubcode, vpboolChanged, vbstrDst, vbstrSrc);
1230         bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1231         if (bSuccess && changed)
1232                 nChanged ++;
1233
1234         // clear the returned variant
1235         VariantClear(&vboolHandled);
1236
1237         return  (bSuccess);
1238 }
1239
1240 bool InvokePackFile(const String& fileSource, const String& fileDest, int & nChanged, IDispatch *piScript, int subCode)
1241 {
1242         // argument text  
1243         VARIANT vbstrSrc;
1244         vbstrSrc.vt = VT_BSTR;
1245         vbstrSrc.bstrVal = SysAllocString(ucr::toUTF16(fileSource).c_str());
1246         // argument transformed text 
1247         VARIANT vbstrDst;
1248         vbstrDst.vt = VT_BSTR;
1249         vbstrDst.bstrVal = SysAllocString(ucr::toUTF16(fileDest).c_str());
1250         // argument subcode
1251         VARIANT viSubcode;
1252         viSubcode.vt = VT_I4;
1253         viSubcode.lVal = subCode;
1254         // argument flag changed (VT_BOOL is short)
1255         VARIANT_BOOL changed = 0;
1256         VARIANT vpboolChanged;
1257         vpboolChanged.vt = VT_BYREF | VT_BOOL;
1258         vpboolChanged.pboolVal = &changed;
1259         // argument return value (VT_BOOL is short)
1260         VARIANT vboolHandled;
1261         vboolHandled.vt = VT_BOOL;
1262         vboolHandled.boolVal = false;
1263
1264         // invoke method by name, reverse order for arguments
1265         // VARIANT_BOOL PackFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL * bChanged, INT bSubcode)
1266         HRESULT h = ::safeInvokeW(piScript,     &vboolHandled, L"PackFile", opFxn[4], 
1267                             viSubcode, vpboolChanged, vbstrDst, vbstrSrc);
1268         bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1269         if (bSuccess && changed)
1270                 nChanged ++;
1271
1272         // clear the returned variant
1273         VariantClear(&vboolHandled);
1274
1275         return  (bSuccess);
1276 }
1277
1278 bool InvokePrediffFile(const String& fileSource, const String& fileDest, int & nChanged, IDispatch *piScript)
1279 {
1280         // argument text  
1281         VARIANT vbstrSrc;
1282         vbstrSrc.vt = VT_BSTR;
1283         vbstrSrc.bstrVal = SysAllocString(ucr::toUTF16(fileSource).c_str());
1284         // argument transformed text 
1285         VARIANT vbstrDst;
1286         vbstrDst.vt = VT_BSTR;
1287         vbstrDst.bstrVal = SysAllocString(ucr::toUTF16(fileDest).c_str());
1288         // argument flag changed (VT_BOOL is short)
1289         VARIANT_BOOL changed = 0;
1290         VARIANT vpboolChanged;
1291         vpboolChanged.vt = VT_BYREF | VT_BOOL;
1292         vpboolChanged.pboolVal = &changed;
1293         // argument return value (VT_BOOL is short)
1294         VARIANT vboolHandled;
1295         vboolHandled.vt = VT_BOOL;
1296         vboolHandled.boolVal = false;
1297
1298         // invoke method by name, reverse order for arguments
1299         // VARIANT_BOOL PrediffFile(BSTR fileSrc, BSTR fileDst, VARIANT_BOOL * bChanged)
1300         HRESULT h = ::safeInvokeW(piScript,     &vboolHandled, L"PrediffFile", opFxn[3], 
1301                             vpboolChanged, vbstrDst, vbstrSrc);
1302         bool bSuccess = ! FAILED(h) && vboolHandled.boolVal;
1303         if (bSuccess && changed)
1304                 nChanged ++;
1305
1306         // clear the returned variant
1307         VariantClear(&vboolHandled);
1308
1309         return  (bSuccess);
1310 }
1311
1312
1313 bool InvokeTransformText(String & text, int & changed, IDispatch *piScript, int fncId)
1314 {
1315         // argument text  
1316         VARIANT pvPszBuf;
1317         pvPszBuf.vt = VT_BSTR;
1318         pvPszBuf.bstrVal = SysAllocString(ucr::toUTF16(text).c_str());
1319         // argument transformed text 
1320         VARIANT vTransformed;
1321         vTransformed.vt = VT_BSTR;
1322         vTransformed.bstrVal = NULL;
1323
1324         // invoke method by ordinal
1325         // BSTR customFunction(BSTR text)
1326         HRESULT h = ::safeInvokeA(piScript, &vTransformed, fncId, opFxn[1], pvPszBuf);
1327
1328         if (! FAILED(h))
1329         {
1330                 text = ucr::toTString(vTransformed.bstrVal);
1331                 changed = true;
1332         }
1333         else
1334                 changed = false;
1335
1336         // clear the returned variant
1337         VariantClear(&vTransformed);
1338
1339         return (! FAILED(h));
1340 }