You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1872 lines
50 KiB

  1. /*
  2. +----------------------------------------------------------------------+
  3. | PHP Version 4 |
  4. +----------------------------------------------------------------------+
  5. | Copyright (c) 1997-2002 The PHP Group |
  6. +----------------------------------------------------------------------+
  7. | This source file is subject to version 2.02 of the PHP license, |
  8. | that is bundled with this package in the file LICENSE, and is |
  9. | available at through the world-wide-web at |
  10. | http://www.php.net/license/2_02.txt. |
  11. | If you did not receive a copy of the PHP license and are unable to |
  12. | obtain it through the world-wide-web, please send a note to |
  13. | license@php.net so we can mail you a copy immediately. |
  14. +----------------------------------------------------------------------+
  15. | Authors: Wez Furlong <wez@thebrainroom.com> |
  16. +----------------------------------------------------------------------+
  17. */
  18. /* $Id$ */
  19. /* Implementation Notes:
  20. *
  21. * PHP stores scripting engine state in thread-local storage. That means
  22. * that we need to create a dedicated thread per-engine so that a host can
  23. * use more than one engine object per thread.
  24. *
  25. * There are some interesting synchronization issues: Anything to do with
  26. * running script in the PHP/Zend engine must take place on the engine
  27. * thread. Likewise, calling back to the host must take place on the base
  28. * thread - the thread that set the script site.
  29. * */
  30. #define _WIN32_DCOM
  31. #include "php.h"
  32. extern "C" {
  33. #include "php_main.h"
  34. #include "SAPI.h"
  35. #include "zend.h"
  36. #include "zend_execute.h"
  37. #include "zend_compile.h"
  38. #include "php_globals.h"
  39. #include "php_variables.h"
  40. #include "php_ini.h"
  41. #include "php4activescript.h"
  42. #include "ext/com/com.h"
  43. #include "ext/com/php_COM.h"
  44. #include "ext/com/conversion.h"
  45. }
  46. #include "php_ticks.h"
  47. #include "php4as_scriptengine.h"
  48. #include "php4as_classfactory.h"
  49. #include <objbase.h>
  50. /* {{{ trace */
  51. static inline void trace(char *fmt, ...)
  52. {
  53. va_list ap;
  54. char buf[4096];
  55. sprintf(buf, "T=%08x ", tsrm_thread_id());
  56. OutputDebugString(buf);
  57. va_start(ap, fmt);
  58. vsnprintf(buf, sizeof(buf), fmt, ap);
  59. OutputDebugString(buf);
  60. va_end(ap);
  61. }
  62. /* }}} */
  63. /* {{{ scriptstate_to_string */
  64. static const char *scriptstate_to_string(SCRIPTSTATE ss)
  65. {
  66. switch(ss) {
  67. case SCRIPTSTATE_UNINITIALIZED: return "SCRIPTSTATE_UNINITIALIZED";
  68. case SCRIPTSTATE_INITIALIZED: return "SCRIPTSTATE_INITIALIZED";
  69. case SCRIPTSTATE_STARTED: return "SCRIPTSTATE_STARTED";
  70. case SCRIPTSTATE_CONNECTED: return "SCRIPTSTATE_CONNECTED";
  71. case SCRIPTSTATE_DISCONNECTED: return "SCRIPTSTATE_DISCONNECTED";
  72. case SCRIPTSTATE_CLOSED: return "SCRIPTSTATE_CLOSED";
  73. default:
  74. return "unknown";
  75. }
  76. }
  77. /* }}} */
  78. /* {{{ TWideString */
  79. /* This class helps manipulate strings from OLE.
  80. * It does not use emalloc, so it is better suited for passing pointers
  81. * between threads. */
  82. class TWideString {
  83. public:
  84. LPOLESTR m_ole;
  85. char *m_ansi;
  86. int m_ansi_strlen;
  87. TWideString(LPOLESTR olestr) {
  88. m_ole = olestr;
  89. m_ansi = NULL;
  90. }
  91. TWideString(LPCOLESTR olestr) {
  92. m_ole = (LPOLESTR)olestr;
  93. m_ansi = NULL;
  94. }
  95. ~TWideString() {
  96. if (m_ansi) {
  97. CoTaskMemFree(m_ansi);
  98. }
  99. m_ansi = NULL;
  100. }
  101. char *safe_ansi_string() {
  102. char *ret = ansi_string();
  103. if (ret == NULL)
  104. return "<NULL>";
  105. return ret;
  106. }
  107. int ansi_len(void) {
  108. /* force conversion if it has not already occurred */
  109. if (m_ansi == NULL)
  110. ansi_string();
  111. return m_ansi_strlen;
  112. }
  113. static BSTR bstr_from_ansi(char *ansi) {
  114. OLECHAR *ole = NULL;
  115. BSTR bstr = NULL;
  116. int req = MultiByteToWideChar(CP_ACP, 0, ansi, -1, NULL, 0);
  117. if (req) {
  118. ole = (OLECHAR*)CoTaskMemAlloc((req + 1) * sizeof(OLECHAR));
  119. if (ole) {
  120. req = MultiByteToWideChar(CP_ACP, 0, ansi, -1, ole, req);
  121. req--;
  122. ole[req] = 0;
  123. bstr = SysAllocString(ole);
  124. CoTaskMemFree(ole);
  125. }
  126. }
  127. return bstr;
  128. }
  129. char *ansi_string(void)
  130. {
  131. if (m_ansi)
  132. return m_ansi;
  133. if (m_ole == NULL)
  134. return NULL;
  135. int bufrequired = WideCharToMultiByte(CP_ACP, 0, m_ole, -1, NULL, 0, NULL, NULL);
  136. if (bufrequired) {
  137. m_ansi = (char*)CoTaskMemAlloc(bufrequired + 1);
  138. if (m_ansi) {
  139. m_ansi_strlen = WideCharToMultiByte(CP_ACP, 0, m_ole, -1, m_ansi, bufrequired + 1, NULL, NULL);
  140. if (m_ansi_strlen) {
  141. m_ansi_strlen--;
  142. m_ansi[m_ansi_strlen] = 0;
  143. } else {
  144. trace("conversion failed with return code %08x\n", GetLastError());
  145. }
  146. }
  147. }
  148. return m_ansi;
  149. }
  150. };
  151. /* }}} */
  152. /* {{{ code fragment structures */
  153. enum fragtype {
  154. FRAG_MAIN,
  155. FRAG_SCRIPTLET,
  156. FRAG_PROCEDURE
  157. };
  158. typedef struct {
  159. enum fragtype fragtype;
  160. zend_op_array *opcodes;
  161. char *code;
  162. int persistent; /* should be retained for Clone */
  163. int executed; /* for "main" */
  164. char *functionname;
  165. unsigned int codelen;
  166. unsigned int starting_line;
  167. TPHPScriptingEngine *engine;
  168. void *ptr;
  169. } code_frag;
  170. #define FRAG_CREATE_FUNC (char*)-1
  171. static code_frag *compile_code_fragment(
  172. enum fragtype fragtype,
  173. char *functionname,
  174. LPCOLESTR code,
  175. ULONG starting_line,
  176. EXCEPINFO *excepinfo,
  177. TPHPScriptingEngine *engine
  178. TSRMLS_DC);
  179. static int execute_code_fragment(code_frag *frag,
  180. VARIANT *varResult,
  181. EXCEPINFO *excepinfo
  182. TSRMLS_DC);
  183. static void free_code_fragment(code_frag *frag);
  184. static code_frag *clone_code_fragment(code_frag *frag, TPHPScriptingEngine *engine TSRMLS_DC);
  185. /* }}} */
  186. /* Magic for handling threading correctly */
  187. static inline HRESULT SEND_THREAD_MESSAGE(TPHPScriptingEngine *engine, LONG msg, WPARAM wparam, LPARAM lparam TSRMLS_DC)
  188. {
  189. if (engine->m_enginethread == 0)
  190. return E_UNEXPECTED;
  191. if (tsrm_thread_id() == (engine)->m_enginethread)
  192. return (engine)->engine_thread_handler((msg), (wparam), (lparam), NULL TSRMLS_CC);
  193. return (engine)->SendThreadMessage((msg), (wparam), (lparam));
  194. }
  195. /* These functions do some magic so that interfaces can be
  196. * used across threads without worrying about marshalling
  197. * or not marshalling, as appropriate.
  198. * Win95 without DCOM 1.1, and NT SP 2 or lower do not have
  199. * the GIT; so we emulate the GIT using other means.
  200. * If you trace problems back to this code, installing the relevant
  201. * SP should solve them.
  202. * */
  203. static inline HRESULT GIT_get(DWORD cookie, REFIID riid, void **obj)
  204. {
  205. IGlobalInterfaceTable *git;
  206. HRESULT ret;
  207. if (SUCCEEDED(CoCreateInstance(CLSID_StdGlobalInterfaceTable, NULL,
  208. CLSCTX_INPROC_SERVER, IID_IGlobalInterfaceTable,
  209. (void**)&git))) {
  210. ret = git->GetInterfaceFromGlobal(cookie, riid, obj);
  211. git->Release();
  212. return ret;
  213. }
  214. return CoGetInterfaceAndReleaseStream((LPSTREAM)cookie, riid, obj);
  215. }
  216. static inline HRESULT GIT_put(IUnknown *unk, REFIID riid, DWORD *cookie)
  217. {
  218. IGlobalInterfaceTable *git;
  219. HRESULT ret;
  220. if (SUCCEEDED(CoCreateInstance(CLSID_StdGlobalInterfaceTable, NULL,
  221. CLSCTX_INPROC_SERVER, IID_IGlobalInterfaceTable,
  222. (void**)&git))) {
  223. ret = git->RegisterInterfaceInGlobal(unk, riid, cookie);
  224. git->Release();
  225. return ret;
  226. }
  227. return CoMarshalInterThreadInterfaceInStream(riid, unk, (LPSTREAM*)cookie);
  228. }
  229. static inline HRESULT GIT_revoke(DWORD cookie, IUnknown *unk)
  230. {
  231. IGlobalInterfaceTable *git;
  232. HRESULT ret;
  233. if (SUCCEEDED(CoCreateInstance(CLSID_StdGlobalInterfaceTable, NULL,
  234. CLSCTX_INPROC_SERVER, IID_IGlobalInterfaceTable,
  235. (void**)&git))) {
  236. ret = git->RevokeInterfaceFromGlobal(cookie);
  237. git->Release();
  238. }
  239. /* Kill remote clients */
  240. return CoDisconnectObject(unk, 0);
  241. }
  242. /* {{{ A generic stupid IDispatch implementation */
  243. class IDispatchImpl:
  244. public IDispatch
  245. {
  246. protected:
  247. volatile LONG m_refcount;
  248. public:
  249. /* IUnknown */
  250. STDMETHODIMP QueryInterface(REFIID iid, void **ppvObject) {
  251. *ppvObject = NULL;
  252. if (IsEqualGUID(IID_IDispatch, iid)) {
  253. *ppvObject = (IDispatch*)this;
  254. } else if (IsEqualGUID(IID_IUnknown, iid)) {
  255. *ppvObject = this;
  256. }
  257. if (*ppvObject) {
  258. AddRef();
  259. return S_OK;
  260. }
  261. return E_NOINTERFACE;
  262. }
  263. STDMETHODIMP_(DWORD) AddRef(void) {
  264. return InterlockedIncrement(const_cast<long*> (&m_refcount));
  265. }
  266. STDMETHODIMP_(DWORD) Release(void) {
  267. DWORD ret = InterlockedDecrement(const_cast<long*> (&m_refcount));
  268. trace("%08x: IDispatchImpl: release ref count is now %d\n", this, ret);
  269. if (ret == 0)
  270. delete this;
  271. return ret;
  272. }
  273. /* IDispatch */
  274. STDMETHODIMP GetTypeInfoCount(unsigned int * pctinfo) {
  275. *pctinfo = 0;
  276. trace("%08x: IDispatchImpl: GetTypeInfoCount\n", this);
  277. return S_OK;
  278. }
  279. STDMETHODIMP GetTypeInfo( unsigned int iTInfo, LCID lcid, ITypeInfo **ppTInfo) {
  280. trace("%08x: IDispatchImpl: GetTypeInfo\n", this);
  281. return DISP_E_BADINDEX;
  282. }
  283. STDMETHODIMP GetIDsOfNames( REFIID riid, OLECHAR **rgszNames, unsigned int cNames, LCID lcid, DISPID *rgDispId)
  284. {
  285. unsigned int i;
  286. trace("%08x: IDispatchImpl: GetIDsOfNames: \n", this);
  287. for (i = 0; i < cNames; i++) {
  288. TWideString name(rgszNames[i]);
  289. trace(" %s\n", name.ansi_string());
  290. }
  291. trace("----\n");
  292. return DISP_E_UNKNOWNNAME;
  293. }
  294. STDMETHODIMP Invoke( DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags,
  295. DISPPARAMS FAR* pDispParams, VARIANT FAR* pVarResult, EXCEPINFO FAR* pExcepInfo,
  296. unsigned int FAR* puArgErr)
  297. {
  298. trace("%08x: IDispatchImpl: Invoke dispid %08x\n", this, dispIdMember);
  299. return S_OK;
  300. }
  301. IDispatchImpl() {
  302. m_refcount = 1;
  303. }
  304. virtual ~IDispatchImpl() {
  305. }
  306. };
  307. /* }}} */
  308. /* {{{ This object represents the PHP engine to the scripting host.
  309. * Although the docs say it's implementation is optional, I found that
  310. * the Windows Script host would crash if we did not provide it. */
  311. class ScriptDispatch:
  312. public IDispatchImpl
  313. {
  314. public:
  315. ScriptDispatch() {
  316. m_refcount = 1;
  317. }
  318. };
  319. /* }}} */
  320. /* {{{ This object is used in conjunction with IActiveScriptParseProcedure to
  321. * allow scriptlets to be bound to events. IE uses this for declaring
  322. * event handlers such as onclick="...".
  323. * The compiled code is stored in this object; IE will call
  324. * IDispatch::Invoke when the element is clicked.
  325. * */
  326. class ScriptProcedureDispatch:
  327. public IDispatchImpl
  328. {
  329. public:
  330. code_frag *m_frag;
  331. DWORD m_procflags;
  332. TPHPScriptingEngine *m_engine;
  333. DWORD m_gitcookie;
  334. STDMETHODIMP Invoke( DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags,
  335. DISPPARAMS FAR* pDispParams, VARIANT FAR* pVarResult, EXCEPINFO FAR* pExcepInfo,
  336. unsigned int FAR* puArgErr)
  337. {
  338. TSRMLS_FETCH();
  339. if (m_frag) {
  340. trace("%08x: Procedure Dispatch: Invoke dispid %08x\n", this, dispIdMember);
  341. SEND_THREAD_MESSAGE(m_engine, PHPSE_EXEC_PROC, 0, (LPARAM)this TSRMLS_CC);
  342. }
  343. return S_OK;
  344. }
  345. ScriptProcedureDispatch() {
  346. m_refcount = 1;
  347. GIT_put((IDispatch*)this, IID_IDispatch, &m_gitcookie);
  348. }
  349. };
  350. /* }}} */
  351. /* {{{ code fragment management */
  352. static code_frag *compile_code_fragment(
  353. enum fragtype fragtype,
  354. char *functionname,
  355. LPCOLESTR code,
  356. ULONG starting_line,
  357. EXCEPINFO *excepinfo,
  358. TPHPScriptingEngine *engine
  359. TSRMLS_DC)
  360. {
  361. zval pv;
  362. int code_offs = 0;
  363. char namebuf[256];
  364. code_frag *frag = (code_frag*)CoTaskMemAlloc(sizeof(code_frag));
  365. memset(frag, 0, sizeof(code_frag));
  366. frag->engine = engine;
  367. /* handle the function name */
  368. if (functionname) {
  369. int namelen;
  370. if (functionname == FRAG_CREATE_FUNC) {
  371. ULONG n = ++engine->m_lambda_count;
  372. sprintf(namebuf, "__frag_%08x_%u", engine, n);
  373. functionname = namebuf;
  374. }
  375. namelen = strlen(functionname);
  376. code_offs = namelen + sizeof("function (){");
  377. frag->functionname = (char*)CoTaskMemAlloc((namelen + 1) * sizeof(char));
  378. memcpy(frag->functionname, functionname, namelen+1);
  379. }
  380. frag->functionname = functionname;
  381. trace("%08x: COMPILED FRAG\n", frag);
  382. frag->codelen = WideCharToMultiByte(CP_ACP, 0, code, -1, NULL, 0, NULL, NULL);
  383. frag->code = (char*)CoTaskMemAlloc(sizeof(char) * (frag->codelen + code_offs + 1));
  384. if (functionname) {
  385. sprintf(frag->code, "function %s(){ ", functionname);
  386. }
  387. frag->codelen = WideCharToMultiByte(CP_ACP, 0, code, -1, frag->code + code_offs, frag->codelen, NULL, NULL) - 1;
  388. if (functionname) {
  389. frag->codelen += code_offs + 1;
  390. frag->code[frag->codelen-1] = '}';
  391. frag->code[frag->codelen] = 0;
  392. }
  393. trace("code to compile is:\ncode_offs=%d func=%s\n%s\n", code_offs, functionname, frag->code);
  394. frag->fragtype = fragtype;
  395. frag->starting_line = starting_line;
  396. pv.type = IS_STRING;
  397. pv.value.str.val = frag->code;
  398. pv.value.str.len = frag->codelen;
  399. frag->opcodes = compile_string(&pv, "fragment" TSRMLS_CC);
  400. if (frag->opcodes == NULL) {
  401. free_code_fragment(frag);
  402. if (excepinfo) {
  403. memset(excepinfo, 0, sizeof(EXCEPINFO));
  404. excepinfo->wCode = 1000;
  405. excepinfo->bstrSource = TWideString::bstr_from_ansi("fragment");
  406. excepinfo->bstrDescription = TWideString::bstr_from_ansi("Problem while parsing/compiling");
  407. }
  408. return NULL;
  409. }
  410. return frag;
  411. }
  412. static void free_code_fragment(code_frag *frag)
  413. {
  414. switch(frag->fragtype) {
  415. case FRAG_PROCEDURE:
  416. if (frag->ptr) {
  417. ScriptProcedureDispatch *disp = (ScriptProcedureDispatch*)frag->ptr;
  418. disp->Release();
  419. GIT_revoke(disp->m_gitcookie, (IDispatch*)disp);
  420. frag->ptr = NULL;
  421. }
  422. break;
  423. }
  424. if (frag->opcodes)
  425. destroy_op_array(frag->opcodes);
  426. if (frag->functionname)
  427. CoTaskMemFree(frag->functionname);
  428. CoTaskMemFree(frag->code);
  429. CoTaskMemFree(frag);
  430. }
  431. static code_frag *clone_code_fragment(code_frag *frag, TPHPScriptingEngine *engine TSRMLS_DC)
  432. {
  433. zval pv;
  434. code_frag *newfrag = (code_frag*)CoTaskMemAlloc(sizeof(code_frag));
  435. memset(newfrag, 0, sizeof(code_frag));
  436. newfrag->engine = engine;
  437. trace("%08x: CLONED FRAG\n", newfrag);
  438. newfrag->persistent = frag->persistent;
  439. newfrag->codelen = frag->codelen;
  440. newfrag->code = (char*)CoTaskMemAlloc(sizeof(char) * frag->codelen + 1);
  441. memcpy(newfrag->code, frag->code, frag->codelen + 1);
  442. if (frag->functionname) {
  443. int namelen = strlen(frag->functionname);
  444. newfrag->functionname = (char*)CoTaskMemAlloc(sizeof(char) * (namelen + 1));
  445. memcpy(newfrag->functionname, frag->functionname, namelen+1);
  446. } else {
  447. newfrag->functionname = NULL;
  448. }
  449. newfrag->fragtype = frag->fragtype;
  450. newfrag->starting_line = frag->starting_line;
  451. pv.type = IS_STRING;
  452. pv.value.str.val = newfrag->code;
  453. pv.value.str.len = newfrag->codelen;
  454. newfrag->opcodes = compile_string(&pv, "fragment" TSRMLS_CC);
  455. if (newfrag->opcodes == NULL) {
  456. free_code_fragment(newfrag);
  457. /*
  458. if (excepinfo) {
  459. memset(excepinfo, 0, sizeof(EXCEPINFO));
  460. excepinfo->wCode = 1000;
  461. excepinfo->bstrSource = TWideString::bstr_from_ansi("fragment");
  462. excepinfo->bstrDescription = TWideString::bstr_from_ansi("Problem while parsing/compiling");
  463. }
  464. */
  465. return NULL;
  466. }
  467. return newfrag;
  468. }
  469. static int execute_code_fragment(code_frag *frag,
  470. VARIANT *varResult,
  471. EXCEPINFO *excepinfo
  472. TSRMLS_DC)
  473. {
  474. zval *retval_ptr = NULL;
  475. jmp_buf *orig_jmpbuf;
  476. jmp_buf err_trap;
  477. if (frag->fragtype == FRAG_MAIN && frag->executed)
  478. return 1;
  479. orig_jmpbuf = frag->engine->m_err_trap;
  480. frag->engine->m_err_trap = &err_trap;
  481. if (setjmp(err_trap) == 0) {
  482. trace("*** Executing code in thread %08x\n", tsrm_thread_id());
  483. if (frag->functionname) {
  484. zval fname;
  485. fname.type = IS_STRING;
  486. fname.value.str.val = frag->functionname;
  487. fname.value.str.len = strlen(frag->functionname);
  488. call_user_function_ex(CG(function_table), NULL, &fname, &retval_ptr, 0, NULL, 1, NULL TSRMLS_CC);
  489. } else {
  490. zend_op_array *active_op_array = EG(active_op_array);
  491. zend_function_state *function_state_ptr = EG(function_state_ptr);
  492. zval **return_value_ptr_ptr = EG(return_value_ptr_ptr);
  493. zend_op **opline_ptr = EG(opline_ptr);
  494. EG(return_value_ptr_ptr) = &retval_ptr;
  495. EG(active_op_array) = frag->opcodes;
  496. EG(no_extensions) = 1;
  497. zend_execute(frag->opcodes TSRMLS_CC);
  498. EG(no_extensions) = 0;
  499. EG(opline_ptr) = opline_ptr;
  500. EG(active_op_array) = active_op_array;
  501. EG(function_state_ptr) = function_state_ptr;
  502. EG(return_value_ptr_ptr) = return_value_ptr_ptr;
  503. }
  504. } else {
  505. trace("*** --> caught error while executing\n");
  506. if (frag->engine->m_in_main)
  507. frag->engine->m_stop_main = 1;
  508. }
  509. frag->engine->m_err_trap = orig_jmpbuf;
  510. if (frag->fragtype == FRAG_MAIN)
  511. frag->executed = 1;
  512. if (varResult)
  513. VariantInit(varResult);
  514. if (retval_ptr) {
  515. if (varResult)
  516. php_pval_to_variant(retval_ptr, varResult, CP_ACP TSRMLS_CC);
  517. zval_ptr_dtor(&retval_ptr);
  518. }
  519. return 1;
  520. }
  521. static void frag_dtor(void *pDest)
  522. {
  523. code_frag *frag = *(code_frag**)pDest;
  524. free_code_fragment(frag);
  525. }
  526. /* }}} */
  527. /* glue for getting back into the OO land */
  528. static DWORD WINAPI begin_engine_thread(LPVOID param)
  529. {
  530. TPHPScriptingEngine *engine = (TPHPScriptingEngine*)param;
  531. engine->engine_thread_func();
  532. trace("engine thread has really gone away!\n");
  533. return 0;
  534. }
  535. TPHPScriptingEngine::TPHPScriptingEngine()
  536. {
  537. m_scriptstate = SCRIPTSTATE_UNINITIALIZED;
  538. m_pass = NULL;
  539. m_in_main = 0;
  540. m_stop_main = 0;
  541. m_err_trap = NULL;
  542. m_lambda_count = 0;
  543. m_pass_eng = NULL;
  544. m_refcount = 1;
  545. m_basethread = tsrm_thread_id();
  546. m_mutex = tsrm_mutex_alloc();
  547. m_sync_thread_msg = CreateEvent(NULL, TRUE, FALSE, NULL);
  548. TPHPClassFactory::AddToObjectCount();
  549. m_engine_thread_handle = CreateThread(NULL, 0, begin_engine_thread, this, 0, &m_enginethread);
  550. CloseHandle(m_engine_thread_handle);
  551. }
  552. void activescript_run_ticks(int count)
  553. {
  554. MSG msg;
  555. TSRMLS_FETCH();
  556. TPHPScriptingEngine *engine;
  557. trace("ticking %d\n", count);
  558. engine = (TPHPScriptingEngine*)SG(server_context);
  559. /* PostThreadMessage(engine->m_enginethread, PHPSE_DUMMY_TICK, 0, 0); */
  560. while(PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
  561. if (msg.hwnd) {
  562. PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
  563. TranslateMessage(&msg);
  564. DispatchMessage(&msg);
  565. } else {
  566. break;
  567. }
  568. }
  569. }
  570. /* Synchronize with the engine thread */
  571. HRESULT TPHPScriptingEngine::SendThreadMessage(LONG msg, WPARAM wparam, LPARAM lparam)
  572. {
  573. HRESULT ret;
  574. if (m_enginethread == 0)
  575. return E_UNEXPECTED;
  576. trace("I'm waiting for a mutex in SendThreadMessage\n this=%08x ethread=%08x msg=%08x\n",
  577. this, m_enginethread, msg);
  578. tsrm_mutex_lock(m_mutex);
  579. ResetEvent(m_sync_thread_msg);
  580. /* If we call PostThreadMessage before the thread has created the queue, the message
  581. * posting fails. MSDN docs recommend the following course of action */
  582. while (!PostThreadMessage(m_enginethread, msg, wparam, lparam)) {
  583. Sleep(50);
  584. if (m_enginethread == 0) {
  585. tsrm_mutex_unlock(m_mutex);
  586. trace("breaking out of dodgy busy wait\n");
  587. return E_UNEXPECTED;
  588. }
  589. }
  590. /* Wait for the event object to be signalled.
  591. * This is a nice "blocking without blocking" wait; window messages are dispatched
  592. * and everything works out quite nicely */
  593. while(1) {
  594. DWORD result = MsgWaitForMultipleObjects(1, &m_sync_thread_msg, FALSE, 4000, QS_ALLINPUT);
  595. if (result == WAIT_OBJECT_0 + 1) {
  596. /* Dispatch some messages */
  597. MSG msg;
  598. while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
  599. //trace("dispatching message while waiting\n");
  600. TranslateMessage(&msg);
  601. DispatchMessage(&msg);
  602. }
  603. } else if (result == WAIT_TIMEOUT) {
  604. trace("timeout while waiting for thread reply\n");
  605. } else {
  606. /* the event was signalled */
  607. break;
  608. }
  609. }
  610. ret = m_sync_thread_ret;
  611. ResetEvent(m_sync_thread_msg);
  612. tsrm_mutex_unlock(m_mutex);
  613. return ret;
  614. }
  615. TPHPScriptingEngine::~TPHPScriptingEngine()
  616. {
  617. trace("\n\n *** Engine Destructor Called\n\n");
  618. if (m_scriptstate != SCRIPTSTATE_UNINITIALIZED && m_scriptstate != SCRIPTSTATE_CLOSED && m_enginethread)
  619. Close();
  620. PostThreadMessage(m_enginethread, WM_QUIT, 0, 0);
  621. TPHPClassFactory::RemoveFromObjectCount();
  622. tsrm_mutex_free(m_mutex);
  623. }
  624. /* Set some executor globals and execute a zend_op_array.
  625. * The declaration looks wierd because this can be invoked from
  626. * zend_hash_apply_with_argument */
  627. static int execute_main(void *pDest, void *arg TSRMLS_DC)
  628. {
  629. code_frag *frag = *(code_frag**)pDest;
  630. if (frag->fragtype == FRAG_MAIN && !(frag->engine->m_in_main && frag->engine->m_stop_main))
  631. execute_code_fragment(frag, NULL, NULL TSRMLS_CC);
  632. return ZEND_HASH_APPLY_KEEP;
  633. }
  634. static int clone_frags(void *pDest, void *arg TSRMLS_DC)
  635. {
  636. code_frag *frag, *src = *(code_frag**)pDest;
  637. TPHPScriptingEngine *engine = (TPHPScriptingEngine*)arg;
  638. if (src->persistent) {
  639. frag = clone_code_fragment(src, engine TSRMLS_CC);
  640. if (frag)
  641. zend_hash_next_index_insert(&engine->m_frags, &frag, sizeof(code_frag*), NULL);
  642. else
  643. trace("WARNING: clone failed!\n");
  644. }
  645. return ZEND_HASH_APPLY_KEEP;
  646. }
  647. HRESULT TPHPScriptingEngine::engine_thread_handler(LONG msg, WPARAM wparam, LPARAM lParam, int *handled TSRMLS_DC)
  648. {
  649. HRESULT ret = S_OK;
  650. trace("engine_thread_handler: running in thread %08x, should be %08x msg=%08x this=%08x\n",
  651. tsrm_thread_id(), m_enginethread, msg, this);
  652. if (handled)
  653. *handled = 1;
  654. if (m_enginethread == 0)
  655. return E_UNEXPECTED;
  656. switch(msg) {
  657. case PHPSE_ADD_TYPELIB:
  658. {
  659. struct php_active_script_add_tlb_info *info = (struct php_active_script_add_tlb_info*)lParam;
  660. ITypeLib *TypeLib;
  661. if (SUCCEEDED(LoadRegTypeLib(*info->rguidTypeLib, (USHORT)info->dwMajor,
  662. (USHORT)info->dwMinor, LANG_NEUTRAL, &TypeLib))) {
  663. php_COM_load_typelib(TypeLib, CONST_CS TSRMLS_CC);
  664. TypeLib->Release();
  665. }
  666. }
  667. break;
  668. case PHPSE_STATE_CHANGE:
  669. {
  670. /* handle the state change here */
  671. SCRIPTSTATE ss = (SCRIPTSTATE)lParam;
  672. int start_running = 0;
  673. trace("%08x: DoSetScriptState(current=%s, new=%s)\n",
  674. this,
  675. scriptstate_to_string(m_scriptstate),
  676. scriptstate_to_string(ss));
  677. if (m_scriptstate == SCRIPTSTATE_INITIALIZED && (ss == SCRIPTSTATE_STARTED || ss == SCRIPTSTATE_CONNECTED))
  678. start_running = 1;
  679. m_scriptstate = ss;
  680. /* inform host/site of the change */
  681. if (m_pass_eng)
  682. m_pass_eng->OnStateChange(m_scriptstate);
  683. if (start_running) {
  684. /* run "main()", as described in the docs */
  685. if (m_pass_eng)
  686. m_pass_eng->OnEnterScript();
  687. trace("%08x: apply execute main to m_frags\n", this);
  688. m_in_main = 1;
  689. m_stop_main = 0;
  690. zend_hash_apply_with_argument(&m_frags, execute_main, this TSRMLS_CC);
  691. m_in_main = 0;
  692. trace("%08x: --- done execute main\n", this);
  693. if (m_pass_eng)
  694. m_pass_eng->OnLeaveScript();
  695. /* docs are a bit ambiguous here, but it appears that we should
  696. * inform the host that the main script execution has completed,
  697. * and also what the return value is */
  698. VARIANT varRes;
  699. VariantInit(&varRes);
  700. if (m_pass_eng)
  701. m_pass_eng->OnScriptTerminate(&varRes, NULL);
  702. /*
  703. m_scriptstate = SCRIPTSTATE_INITIALIZED;
  704. if (m_pass_eng)
  705. m_pass_eng->OnStateChange(m_scriptstate);
  706. */
  707. }
  708. }
  709. break;
  710. case PHPSE_INIT_NEW:
  711. {
  712. /* Prepare PHP/ZE for use */
  713. trace("%08x: m_frags : INIT NEW\n", this);
  714. zend_hash_init(&m_frags, 0, NULL, frag_dtor, TRUE);
  715. SG(options) |= SAPI_OPTION_NO_CHDIR;
  716. SG(server_context) = this;
  717. /* override the default PHP error callback */
  718. zend_error_cb = activescript_error_handler;
  719. zend_alter_ini_entry("register_argc_argv", 19, "1", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE);
  720. zend_alter_ini_entry("html_errors", 12, "0", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE);
  721. zend_alter_ini_entry("implicit_flush", 15, "1", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE);
  722. zend_alter_ini_entry("max_execution_time", 19, "0", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE);
  723. php_request_startup(TSRMLS_C);
  724. PG(during_request_startup) = 0;
  725. trace("\n\n *** ticks func at %08x %08x ***\n\n\n", activescript_run_ticks, &activescript_run_ticks);
  726. // php_add_tick_function(activescript_run_ticks);
  727. }
  728. break;
  729. case PHPSE_CLOSE:
  730. {
  731. /* Close things down */
  732. trace("%08x: m_frags : CLOSE/DESTROY\n", this);
  733. m_scriptstate = SCRIPTSTATE_CLOSED;
  734. if (m_pass_eng) {
  735. m_pass_eng->OnStateChange(m_scriptstate);
  736. trace("%08x: release site from this side\n", this);
  737. m_pass_eng->Release();
  738. m_pass_eng = NULL;
  739. }
  740. zend_hash_destroy(&m_frags);
  741. php_request_shutdown(NULL);
  742. break;
  743. }
  744. break;
  745. case PHPSE_CLONE:
  746. {
  747. /* Clone the engine state. This is semantically equal to serializing all
  748. * the parsed code from the source and unserializing it in the dest (this).
  749. * IE doesn't appear to use it, but Windows Script Host does. I'd expect
  750. * ASP/ASP.NET to do so also.
  751. *
  752. * FIXME: Probably won't work with IActiveScriptParseProcedure scriplets
  753. * */
  754. TPHPScriptingEngine *src = (TPHPScriptingEngine*)lParam;
  755. trace("%08x: m_frags : CLONE\n", this);
  756. zend_hash_apply_with_argument(&src->m_frags, clone_frags, this TSRMLS_CC);
  757. }
  758. break;
  759. case PHPSE_ADD_SCRIPTLET:
  760. {
  761. /* Parse/compile a chunk of script that will act as an event handler.
  762. * If the host supports IActiveScriptParseProcedure, this code will
  763. * not be called.
  764. * The docs are (typically) vague: AFAICT, once the code has been
  765. * compiled, we are supposed to arrange for an IConnectionPoint
  766. * advisory connection to the item/subitem, once the script
  767. * moves into SCRIPTSTATE_CONNECTED.
  768. * That's a lot of work!
  769. *
  770. * FIXME: this is currently almost useless
  771. * */
  772. struct php_active_script_add_scriptlet_info *info = (struct php_active_script_add_scriptlet_info*)lParam;
  773. TWideString
  774. default_name(info->pstrDefaultName),
  775. code(info->pstrCode),
  776. item_name(info->pstrItemName),
  777. sub_item_name(info->pstrSubItemName),
  778. event_name(info->pstrEventName),
  779. delimiter(info->pstrDelimiter);
  780. /* lets invent a function name for the scriptlet */
  781. char sname[256];
  782. /* should check if the name is already used! */
  783. if (info->pstrDefaultName)
  784. strcpy(sname, default_name.ansi_string());
  785. else {
  786. sname[0] = 0;
  787. strcat(sname, "__");
  788. if (info->pstrItemName) {
  789. strcat(sname, item_name.ansi_string());
  790. strcat(sname, "_");
  791. }
  792. if (info->pstrSubItemName) {
  793. strcat(sname, sub_item_name.ansi_string());
  794. strcat(sname, "_");
  795. }
  796. if (info->pstrEventName)
  797. strcat(sname, event_name.ansi_string());
  798. }
  799. trace("%08x: AddScriptlet:\n state=%s\n name=%s\n code=%s\n item=%s\n subitem=%s\n event=%s\n delim=%s\n line=%d\n",
  800. this, scriptstate_to_string(m_scriptstate),
  801. default_name.safe_ansi_string(), code.safe_ansi_string(), item_name.safe_ansi_string(),
  802. sub_item_name.safe_ansi_string(), event_name.safe_ansi_string(), delimiter.safe_ansi_string(),
  803. info->ulStartingLineNumber);
  804. code_frag *frag = compile_code_fragment(
  805. FRAG_SCRIPTLET,
  806. sname,
  807. info->pstrCode,
  808. info->ulStartingLineNumber,
  809. info->pexcepinfo,
  810. this
  811. TSRMLS_CC);
  812. if (frag) {
  813. frag->persistent = (info->dwFlags & SCRIPTTEXT_ISPERSISTENT);
  814. zend_hash_next_index_insert(&m_frags, &frag, sizeof(code_frag*), NULL);
  815. /*
  816. ScriptProcedureDispatch *disp = new ScriptProcedureDispatch;
  817. disp->AddRef();
  818. disp->m_frag = frag;
  819. disp->m_procflags = info->dwFlags;
  820. disp->m_engine = this;
  821. frag->ptr = disp;
  822. *info->ppdisp = disp;
  823. */
  824. ret = S_OK;
  825. } else {
  826. ret = DISP_E_EXCEPTION;
  827. }
  828. *info->pbstrName = TWideString::bstr_from_ansi(sname);
  829. trace("%08x: done with scriptlet %s\n", this, sname);
  830. }
  831. break;
  832. case PHPSE_GET_DISPATCH:
  833. {
  834. struct php_active_script_get_dispatch_info *info = (struct php_active_script_get_dispatch_info *)lParam;
  835. IDispatch *disp = NULL;
  836. if (info->pstrItemName != NULL) {
  837. zval **tmp;
  838. /* use this rather than php_OLECHAR_to_char because we want to avoid emalloc here */
  839. TWideString itemname(info->pstrItemName);
  840. /* Get that item from the global namespace.
  841. * If it is an object, export it as a dispatchable object.
  842. * */
  843. if (zend_hash_find(&EG(symbol_table), itemname.ansi_string(),
  844. itemname.ansi_len() + 1, (void**)&tmp) == SUCCESS) {
  845. if (Z_TYPE_PP(tmp) == IS_OBJECT) {
  846. /* FIXME: if this causes an allocation (emalloc) and we are
  847. * not in the engine thread, things could get ugly!!! */
  848. disp = php_COM_export_object(*tmp TSRMLS_CC);
  849. }
  850. }
  851. } else {
  852. /* This object represents PHP global namespace */
  853. disp = (IDispatch*) new ScriptDispatch;
  854. }
  855. if (disp) {
  856. ret = GIT_put(disp, IID_IDispatch, &info->dispatch);
  857. disp->Release();
  858. } else {
  859. ret = S_FALSE;
  860. }
  861. }
  862. break;
  863. case PHPSE_ADD_NAMED_ITEM:
  864. {
  865. /* The Host uses this to add objects to the global namespace.
  866. * Some objects are intended to have their child properties
  867. * globally visible, so we add those to the global namespace too.
  868. * */
  869. struct php_active_script_add_named_item_info *info = (struct php_active_script_add_named_item_info *)lParam;
  870. TWideString name(info->pstrName);
  871. IDispatch *disp;
  872. if (SUCCEEDED(GIT_get(info->marshal, IID_IDispatch, (void**)&disp)))
  873. add_to_global_namespace(disp, info->dwFlags, name.ansi_string() TSRMLS_CC);
  874. }
  875. break;
  876. case PHPSE_SET_SITE:
  877. {
  878. if (m_pass_eng) {
  879. m_pass_eng->Release();
  880. m_pass_eng = NULL;
  881. }
  882. if (lParam)
  883. GIT_get(lParam, IID_IActiveScriptSite, (void**)&m_pass_eng);
  884. trace("%08x: site (engine-side) is now %08x (base=%08x)\n", this, m_pass_eng, m_pass);
  885. }
  886. break;
  887. case PHPSE_EXEC_PROC:
  888. {
  889. ScriptProcedureDispatch *disp = (ScriptProcedureDispatch *)lParam;
  890. execute_code_fragment(disp->m_frag, NULL, NULL TSRMLS_CC);
  891. }
  892. break;
  893. case PHPSE_PARSE_PROC:
  894. {
  895. /* This is the IActiveScriptParseProcedure implementation.
  896. * IE uses this to request for an IDispatch that it will invoke in
  897. * response to some event, and tells us the code that it wants to
  898. * run.
  899. * We compile the code and pass it back a dispatch object.
  900. * The object will then serialize access to the engine thread and
  901. * execute the opcodes */
  902. struct php_active_script_parse_proc_info *info = (struct php_active_script_parse_proc_info*)lParam;
  903. TWideString
  904. formal_params(info->pstrFormalParams),
  905. procedure_name(info->pstrProcedureName),
  906. item_name(info->pstrItemName),
  907. delimiter(info->pstrDelimiter);
  908. trace("%08x: ParseProc:\n state=%s\nparams=%s\nproc=%s\nitem=%s\n delim=%s\n line=%d\n",
  909. this, scriptstate_to_string(m_scriptstate),
  910. formal_params.ansi_string(), procedure_name.ansi_string(),
  911. item_name.safe_ansi_string(), delimiter.safe_ansi_string(),
  912. info->ulStartingLineNumber);
  913. code_frag *frag = compile_code_fragment(
  914. FRAG_PROCEDURE,
  915. NULL,
  916. info->pstrCode,
  917. info->ulStartingLineNumber,
  918. NULL,
  919. this
  920. TSRMLS_CC);
  921. if (frag) {
  922. frag->persistent = (info->dwFlags & SCRIPTTEXT_ISPERSISTENT);
  923. zend_hash_next_index_insert(&m_frags, &frag, sizeof(code_frag*), NULL);
  924. ScriptProcedureDispatch *disp = new ScriptProcedureDispatch;
  925. disp->m_frag = frag;
  926. disp->m_procflags = info->dwFlags;
  927. disp->m_engine = this;
  928. frag->ptr = disp;
  929. info->dispcookie = disp->m_gitcookie;
  930. } else {
  931. ret = DISP_E_EXCEPTION;
  932. }
  933. }
  934. break;
  935. case PHPSE_PARSE_SCRIPT:
  936. {
  937. struct php_active_script_parse_info *info = (struct php_active_script_parse_info*)lParam;
  938. int doexec;
  939. TWideString
  940. code(info->pstrCode),
  941. item_name(info->pstrItemName),
  942. delimiter(info->pstrDelimiter);
  943. trace("%08x: ParseScriptText:\n state=%s\ncode=%s\n item=%s\n delim=%s\n line=%d\n",
  944. this, scriptstate_to_string(m_scriptstate),
  945. code.safe_ansi_string(), item_name.safe_ansi_string(), delimiter.safe_ansi_string(),
  946. info->ulStartingLineNumber);
  947. code_frag *frag = compile_code_fragment(
  948. FRAG_MAIN,
  949. info->dwFlags & SCRIPTTEXT_ISEXPRESSION ? FRAG_CREATE_FUNC : NULL,
  950. info->pstrCode,
  951. info->ulStartingLineNumber,
  952. info->pexcepinfo,
  953. this
  954. TSRMLS_CC);
  955. doexec = (info->dwFlags & SCRIPTTEXT_ISEXPRESSION) ||
  956. m_scriptstate == SCRIPTSTATE_STARTED ||
  957. m_scriptstate == SCRIPTSTATE_CONNECTED ||
  958. m_scriptstate == SCRIPTSTATE_DISCONNECTED;
  959. if (frag) {
  960. frag->persistent = (info->dwFlags & SCRIPTTEXT_ISPERSISTENT);
  961. ret = S_OK;
  962. if (info->dwFlags & SCRIPTTEXT_ISEXPRESSION) {
  963. if (m_scriptstate == SCRIPTSTATE_INITIALIZED) {
  964. /* not allowed to execute code in this state */
  965. ret = E_UNEXPECTED;
  966. doexec = 0;
  967. }
  968. }
  969. if (doexec) {
  970. /* execute the code as an expression */
  971. if (!execute_code_fragment(frag, info->pvarResult, info->pexcepinfo TSRMLS_CC))
  972. ret = DISP_E_EXCEPTION;
  973. }
  974. zend_hash_next_index_insert(&m_frags, &frag, sizeof(code_frag*), NULL);
  975. } else {
  976. ret = DISP_E_EXCEPTION;
  977. }
  978. if (info->pvarResult) {
  979. VariantInit(info->pvarResult);
  980. }
  981. }
  982. break;
  983. default:
  984. trace("unhandled message type %08x\n", msg);
  985. if (handled)
  986. *handled = 0;
  987. }
  988. return ret;
  989. }
  990. /* The PHP/Zend state actually lives in this thread */
  991. void TPHPScriptingEngine::engine_thread_func(void)
  992. {
  993. TSRMLS_FETCH();
  994. int handled;
  995. int terminated = 0;
  996. MSG msg;
  997. trace("%08x: engine thread started up!\n", this);
  998. CoInitializeEx(0, COINIT_MULTITHREADED);
  999. m_tsrm_hack = tsrm_ls;
  1000. while(!terminated) {
  1001. DWORD result = MsgWaitForMultipleObjects(0, NULL, FALSE, 4000, QS_ALLINPUT);
  1002. switch(result) {
  1003. case WAIT_OBJECT_0:
  1004. while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
  1005. if (msg.message == WM_QUIT) {
  1006. terminated = 1;
  1007. } else if (msg.hwnd) {
  1008. TranslateMessage(&msg);
  1009. DispatchMessage(&msg);
  1010. } else {
  1011. handled = 1;
  1012. m_sync_thread_ret = engine_thread_handler(msg.message, msg.wParam, msg.lParam, &handled TSRMLS_CC);
  1013. if (handled)
  1014. SetEvent(m_sync_thread_msg);
  1015. }
  1016. }
  1017. break;
  1018. case WAIT_TIMEOUT:
  1019. trace("thread wait timed out\n");
  1020. break;
  1021. default:
  1022. trace("some strange value\n");
  1023. }
  1024. }
  1025. #if 0
  1026. while(GetMessage(&msg, NULL, 0, 0)) {
  1027. if (msg.message == WM_QUIT)
  1028. break;
  1029. handled = 1;
  1030. m_sync_thread_ret = engine_thread_handler(msg.message, msg.wParam, msg.lParam, &handled TSRMLS_CC);
  1031. if (handled)
  1032. SetEvent(m_sync_thread_msg);
  1033. }
  1034. trace("%08x: engine thread exiting!!!!!\n", this);
  1035. #endif
  1036. m_enginethread = 0;
  1037. CoUninitialize();
  1038. }
  1039. /* Only call this in the context of the engine thread, or you'll be sorry.
  1040. *
  1041. * When SCRIPTITEM_GLOBALMEMBERS is set, we're only adding COM objects to the namespace.
  1042. * We could add *all* properties, but I don't like this idea; what if the value changes
  1043. * while the page is running? We'd be left with stale data.
  1044. * */
  1045. void TPHPScriptingEngine::add_to_global_namespace(IDispatch *disp, DWORD flags, char *name TSRMLS_DC)
  1046. {
  1047. zval *val;
  1048. ITypeInfo *typ;
  1049. int i;
  1050. unsigned int namelen;
  1051. FUNCDESC *func;
  1052. BSTR funcname;
  1053. TYPEATTR *attr;
  1054. DISPPARAMS dispparams;
  1055. VARIANT vres;
  1056. ITypeInfo *rettyp;
  1057. TYPEATTR *retattr;
  1058. trace("Add %s to global namespace\n", name);
  1059. val = php_COM_object_from_dispatch(disp, NULL TSRMLS_CC);
  1060. if (val == NULL) {
  1061. disp->Release();
  1062. return;
  1063. }
  1064. ZEND_SET_SYMBOL(&EG(symbol_table), name, val);
  1065. if (flags & SCRIPTITEM_GLOBALMEMBERS == 0) {
  1066. disp->Release();
  1067. return;
  1068. }
  1069. /* Enumerate properties and add those too */
  1070. if (FAILED(disp->GetTypeInfo(0, 0, &typ))) {
  1071. disp->Release();
  1072. return;
  1073. }
  1074. if (SUCCEEDED(typ->GetTypeAttr(&attr))) {
  1075. for (i = 0; i < attr->cFuncs; i++) {
  1076. if (FAILED(typ->GetFuncDesc(i, &func)))
  1077. continue;
  1078. /* Look at it's type */
  1079. if (func->invkind == INVOKE_PROPERTYGET
  1080. && VT_PTR == func->elemdescFunc.tdesc.vt
  1081. && VT_USERDEFINED == func->elemdescFunc.tdesc.lptdesc->vt
  1082. && SUCCEEDED(typ->GetRefTypeInfo(func->elemdescFunc.tdesc.lptdesc->hreftype, &rettyp)))
  1083. {
  1084. if (SUCCEEDED(rettyp->GetTypeAttr(&retattr))) {
  1085. if (retattr->typekind == TKIND_DISPATCH) {
  1086. /* It's dispatchable */
  1087. /* get the value */
  1088. dispparams.cArgs = 0;
  1089. dispparams.cNamedArgs = 0;
  1090. VariantInit(&vres);
  1091. if (SUCCEEDED(disp->Invoke(func->memid, IID_NULL, 0, func->invkind,
  1092. &dispparams, &vres, NULL, NULL))) {
  1093. /* Get it's dispatch */
  1094. IDispatch *sub = NULL;
  1095. if (V_VT(&vres) == VT_UNKNOWN)
  1096. V_UNKNOWN(&vres)->QueryInterface(IID_IDispatch, (void**)&sub);
  1097. else if (V_VT(&vres) == VT_DISPATCH)
  1098. sub = V_DISPATCH(&vres);
  1099. if (sub) {
  1100. /* find out it's name */
  1101. typ->GetDocumentation(func->memid, &funcname, NULL, NULL, NULL);
  1102. name = php_OLECHAR_to_char(funcname, &namelen, CP_ACP TSRMLS_CC);
  1103. /* add to namespace */
  1104. zval *subval = php_COM_object_from_dispatch(sub, NULL TSRMLS_CC);
  1105. if (subval) {
  1106. ZEND_SET_SYMBOL(&EG(symbol_table), name, subval);
  1107. }
  1108. efree(name);
  1109. SysFreeString(funcname);
  1110. }
  1111. VariantClear(&vres);
  1112. }
  1113. }
  1114. rettyp->ReleaseTypeAttr(retattr);
  1115. }
  1116. rettyp->Release();
  1117. }
  1118. typ->ReleaseFuncDesc(func);
  1119. }
  1120. typ->ReleaseTypeAttr(attr);
  1121. }
  1122. disp->Release();
  1123. }
  1124. STDMETHODIMP_(DWORD) TPHPScriptingEngine::AddRef(void)
  1125. {
  1126. return InterlockedIncrement(const_cast<long*> (&m_refcount));
  1127. }
  1128. STDMETHODIMP_(DWORD) TPHPScriptingEngine::Release(void)
  1129. {
  1130. DWORD ret = InterlockedDecrement(const_cast<long*> (&m_refcount));
  1131. if (ret == 0) {
  1132. trace("%08x: Release: zero refcount, destroy the engine!\n", this);
  1133. delete this;
  1134. }
  1135. return ret;
  1136. }
  1137. STDMETHODIMP TPHPScriptingEngine::QueryInterface(REFIID iid, void **ppvObject)
  1138. {
  1139. *ppvObject = NULL;
  1140. if (IsEqualGUID(IID_IActiveScript, iid)) {
  1141. *ppvObject = (IActiveScript*)this;
  1142. } else if (IsEqualGUID(IID_IActiveScriptParse, iid)) {
  1143. *ppvObject = (IActiveScriptParse*)this;
  1144. } else if (IsEqualGUID(IID_IActiveScriptParseProcedure, iid)) {
  1145. *ppvObject = (IActiveScriptParseProcedure*)this;
  1146. } else if (IsEqualGUID(IID_IUnknown, iid)) {
  1147. *ppvObject = this;
  1148. } else {
  1149. LPOLESTR guidw;
  1150. StringFromCLSID(iid, &guidw);
  1151. {
  1152. TWideString guid(guidw);
  1153. trace("%08x: QueryInterface for unsupported %s\n", this, guid.ansi_string());
  1154. }
  1155. CoTaskMemFree(guidw);
  1156. }
  1157. if (*ppvObject) {
  1158. AddRef();
  1159. return S_OK;
  1160. }
  1161. return E_NOINTERFACE;
  1162. }
  1163. /* This is called by the host to set the scrite site.
  1164. * It also defines the base thread. */
  1165. STDMETHODIMP TPHPScriptingEngine::SetScriptSite(IActiveScriptSite *pass)
  1166. {
  1167. TSRMLS_FETCH();
  1168. tsrm_mutex_lock(m_mutex);
  1169. trace("%08x: -----> Base thread is %08x\n", this, tsrm_thread_id());
  1170. if (m_pass) {
  1171. m_pass->Release();
  1172. m_pass = NULL;
  1173. SEND_THREAD_MESSAGE(this, PHPSE_SET_SITE, 0, 0 TSRMLS_CC);
  1174. }
  1175. if (pass == NULL) {
  1176. trace("Closing down site; we should have no references to objects from the host\n"
  1177. " m_pass=%08x\n m_pass_eng=%08x\n What about named items??\n",
  1178. m_pass, m_pass_eng);
  1179. }
  1180. m_pass = pass;
  1181. if (m_pass) {
  1182. m_pass->AddRef();
  1183. DWORD cookie;
  1184. if (SUCCEEDED(GIT_put(m_pass, IID_IActiveScriptSite, &cookie)))
  1185. SEND_THREAD_MESSAGE(this, PHPSE_SET_SITE, 0, cookie TSRMLS_CC);
  1186. if (m_scriptstate == SCRIPTSTATE_UNINITIALIZED)
  1187. SEND_THREAD_MESSAGE(this, PHPSE_STATE_CHANGE, 0, SCRIPTSTATE_INITIALIZED TSRMLS_CC);
  1188. }
  1189. tsrm_mutex_unlock(m_mutex);
  1190. return S_OK;
  1191. }
  1192. STDMETHODIMP TPHPScriptingEngine::GetScriptSite(REFIID riid, void **ppvObject)
  1193. {
  1194. HRESULT ret = S_FALSE;
  1195. trace("%08x: GetScriptSite()\n", this);
  1196. tsrm_mutex_lock(m_mutex);
  1197. if (m_pass)
  1198. ret = m_pass->QueryInterface(riid, ppvObject);
  1199. tsrm_mutex_unlock(m_mutex);
  1200. return ret;
  1201. }
  1202. STDMETHODIMP TPHPScriptingEngine::SetScriptState(SCRIPTSTATE ss)
  1203. {
  1204. TSRMLS_FETCH();
  1205. trace("%08x: SetScriptState(%s)\n", this, scriptstate_to_string(ss));
  1206. return SEND_THREAD_MESSAGE(this, PHPSE_STATE_CHANGE, 0, ss TSRMLS_CC);
  1207. }
  1208. STDMETHODIMP TPHPScriptingEngine::GetScriptState(SCRIPTSTATE *pssState)
  1209. {
  1210. trace("%08x: GetScriptState(current=%s)\n", this, scriptstate_to_string(m_scriptstate));
  1211. tsrm_mutex_lock(m_mutex);
  1212. *pssState = m_scriptstate;
  1213. tsrm_mutex_unlock(m_mutex);
  1214. return S_OK;
  1215. }
  1216. STDMETHODIMP TPHPScriptingEngine::Close(void)
  1217. {
  1218. TSRMLS_FETCH();
  1219. if (m_pass) {
  1220. m_pass->Release();
  1221. m_pass = NULL;
  1222. }
  1223. SEND_THREAD_MESSAGE(this, PHPSE_CLOSE, 0, 0 TSRMLS_CC);
  1224. return S_OK;
  1225. }
  1226. /* Add an item to global namespace.
  1227. * This is called in the context of the base thread (or perhaps some other thread).
  1228. * We want to be able to work with the object in the engine thread, so we marshal
  1229. * it into a stream and let the engine thread deal with it.
  1230. * This works quite nicely when PHP scripts call into the object; threading is
  1231. * handled correctly. */
  1232. STDMETHODIMP TPHPScriptingEngine::AddNamedItem(LPCOLESTR pstrName, DWORD dwFlags)
  1233. {
  1234. struct php_active_script_add_named_item_info info;
  1235. TSRMLS_FETCH();
  1236. info.pstrName = pstrName;
  1237. info.dwFlags = dwFlags;
  1238. m_pass->GetItemInfo(pstrName, SCRIPTINFO_IUNKNOWN, &info.punk, NULL);
  1239. if (SUCCEEDED(GIT_put(info.punk, IID_IDispatch, &info.marshal))) {
  1240. SEND_THREAD_MESSAGE(this, PHPSE_ADD_NAMED_ITEM, 0, (LPARAM)&info TSRMLS_CC);
  1241. }
  1242. info.punk->Release();
  1243. return S_OK;
  1244. }
  1245. /* Bind to a type library */
  1246. STDMETHODIMP TPHPScriptingEngine::AddTypeLib(
  1247. /* [in] */ REFGUID rguidTypeLib,
  1248. /* [in] */ DWORD dwMajor,
  1249. /* [in] */ DWORD dwMinor,
  1250. /* [in] */ DWORD dwFlags)
  1251. {
  1252. struct php_active_script_add_tlb_info info;
  1253. TSRMLS_FETCH();
  1254. info.rguidTypeLib = &rguidTypeLib;
  1255. info.dwMajor = dwMajor;
  1256. info.dwMinor = dwMinor;
  1257. info.dwFlags = dwFlags;
  1258. SEND_THREAD_MESSAGE(this, PHPSE_ADD_TYPELIB, 0, (LPARAM)&info TSRMLS_CC);
  1259. return S_OK;
  1260. }
  1261. /* Returns an object representing the PHP Scripting Engine.
  1262. * Optionally, a client can request a particular item directly.
  1263. * For the moment, we only do the bare minimum amount of work
  1264. * for the engine to work correctly; we can flesh out this part
  1265. * a little later. */
  1266. STDMETHODIMP TPHPScriptingEngine::GetScriptDispatch(
  1267. /* [in] */ LPCOLESTR pstrItemName,
  1268. /* [out] */ IDispatch **ppdisp)
  1269. {
  1270. TSRMLS_FETCH();
  1271. *ppdisp = NULL;
  1272. struct php_active_script_get_dispatch_info info;
  1273. info.pstrItemName = pstrItemName;
  1274. info.dispatch = NULL;
  1275. /* This hack is required because the host is likely to query us
  1276. * for a dispatch if we use any of it's objects from PHP script.
  1277. * Since the engine thread will be waiting for the return from
  1278. * a COM call, we need to deliberately poke a hole in thread
  1279. * safety so that it is possible to read the symbol table from
  1280. * outside the engine thread and give it a valid return value.
  1281. * This is "safe" only in this instance, since we are not modifying
  1282. * the engine state by looking up the dispatch (I hope).
  1283. * The scripting engine rules pretty much guarantee that this
  1284. * method is only called in the base thread. */
  1285. if (tsrm_thread_id() != m_enginethread) {
  1286. tsrm_ls = m_tsrm_hack;
  1287. trace("HEY: hacking thread safety!\n");
  1288. }
  1289. if (S_OK == engine_thread_handler(PHPSE_GET_DISPATCH, 0, (LPARAM)&info, NULL TSRMLS_CC)) {
  1290. GIT_get(info.dispatch, IID_IDispatch, (void**)ppdisp);
  1291. }
  1292. if (*ppdisp) {
  1293. return S_OK;
  1294. }
  1295. return S_FALSE;
  1296. }
  1297. STDMETHODIMP TPHPScriptingEngine::GetCurrentScriptThreadID(
  1298. /* [out] */ SCRIPTTHREADID *pstidThread)
  1299. {
  1300. // tsrm_mutex_lock(m_mutex);
  1301. trace("%08x: GetCurrentScriptThreadID()\n", this);
  1302. *pstidThread = m_enginethread;
  1303. // tsrm_mutex_unlock(m_mutex);
  1304. return S_OK;
  1305. }
  1306. STDMETHODIMP TPHPScriptingEngine::GetScriptThreadID(
  1307. /* [in] */ DWORD dwWin32ThreadId,
  1308. /* [out] */ SCRIPTTHREADID *pstidThread)
  1309. {
  1310. // tsrm_mutex_lock(m_mutex);
  1311. trace("%08x: GetScriptThreadID()\n", this);
  1312. *pstidThread = dwWin32ThreadId;
  1313. // tsrm_mutex_unlock(m_mutex);
  1314. return S_OK;
  1315. }
  1316. STDMETHODIMP TPHPScriptingEngine::GetScriptThreadState(
  1317. /* [in] */ SCRIPTTHREADID stidThread,
  1318. /* [out] */ SCRIPTTHREADSTATE *pstsState)
  1319. {
  1320. // tsrm_mutex_lock(m_mutex);
  1321. trace("%08x: GetScriptThreadState()\n", this);
  1322. *pstsState = SCRIPTTHREADSTATE_NOTINSCRIPT;
  1323. switch(stidThread) {
  1324. case SCRIPTTHREADID_BASE:
  1325. stidThread = m_basethread;
  1326. break;
  1327. case SCRIPTTHREADID_CURRENT:
  1328. stidThread = m_enginethread;
  1329. break;
  1330. };
  1331. if (stidThread == m_basethread) {
  1332. *pstsState = SCRIPTTHREADSTATE_NOTINSCRIPT;
  1333. } else if (stidThread == m_enginethread) {
  1334. *pstsState = SCRIPTTHREADSTATE_NOTINSCRIPT;
  1335. }
  1336. // tsrm_mutex_unlock(m_mutex);
  1337. return S_OK;
  1338. }
  1339. STDMETHODIMP TPHPScriptingEngine::InterruptScriptThread(
  1340. /* [in] */ SCRIPTTHREADID stidThread,
  1341. /* [in] */ const EXCEPINFO *pexcepinfo,
  1342. /* [in] */ DWORD dwFlags)
  1343. {
  1344. /* do not serialize this method, or call into the script site */
  1345. trace("%08x: InterruptScriptThread()\n", this);
  1346. return S_OK;
  1347. }
  1348. /* Clone is essential when running under Windows Script Host.
  1349. * It creates an engine to parse the code. Once it is parsed,
  1350. * the host clones other engines from the original and runs those.
  1351. * It is intended to be a fast method of running the same script
  1352. * multiple times in multiple threads. */
  1353. STDMETHODIMP TPHPScriptingEngine::Clone(
  1354. /* [out] */ IActiveScript **ppscript)
  1355. {
  1356. TPHPScriptingEngine *cloned = new TPHPScriptingEngine;
  1357. TSRMLS_FETCH();
  1358. trace("%08x: Clone()\n", this);
  1359. if (ppscript)
  1360. *ppscript = NULL;
  1361. if (cloned) {
  1362. cloned->InitNew();
  1363. SEND_THREAD_MESSAGE(cloned, PHPSE_CLONE, 0, (LPARAM)this TSRMLS_CC);
  1364. trace("%08x: Cloned OK, returning cloned object ptr %08x\n", this, cloned);
  1365. *ppscript = (IActiveScript*)cloned;
  1366. return S_OK;
  1367. }
  1368. return E_FAIL;
  1369. }
  1370. STDMETHODIMP TPHPScriptingEngine::InitNew( void)
  1371. {
  1372. TSRMLS_FETCH();
  1373. SEND_THREAD_MESSAGE(this, PHPSE_INIT_NEW, 0, 0 TSRMLS_CC);
  1374. return S_OK;
  1375. }
  1376. STDMETHODIMP TPHPScriptingEngine::AddScriptlet(
  1377. /* [in] */ LPCOLESTR pstrDefaultName,
  1378. /* [in] */ LPCOLESTR pstrCode,
  1379. /* [in] */ LPCOLESTR pstrItemName,
  1380. /* [in] */ LPCOLESTR pstrSubItemName,
  1381. /* [in] */ LPCOLESTR pstrEventName,
  1382. /* [in] */ LPCOLESTR pstrDelimiter,
  1383. /* [in] */ DWORD dwSourceContextCookie,
  1384. /* [in] */ ULONG ulStartingLineNumber,
  1385. /* [in] */ DWORD dwFlags,
  1386. /* [out] */ BSTR *pbstrName,
  1387. /* [out] */ EXCEPINFO *pexcepinfo)
  1388. {
  1389. struct php_active_script_add_scriptlet_info info;
  1390. TSRMLS_FETCH();
  1391. info.pstrDefaultName = pstrDefaultName;
  1392. info.pstrCode = pstrCode;
  1393. info.pstrItemName = pstrItemName;
  1394. info.pstrSubItemName = pstrSubItemName;
  1395. info.pstrEventName = pstrEventName;
  1396. info.pstrDelimiter = pstrDelimiter;
  1397. info.dwSourceContextCookie = dwSourceContextCookie;
  1398. info.ulStartingLineNumber = ulStartingLineNumber;
  1399. info.dwFlags = dwFlags;
  1400. info.pbstrName = pbstrName;
  1401. info.pexcepinfo = pexcepinfo;
  1402. return SEND_THREAD_MESSAGE(this, PHPSE_ADD_SCRIPTLET, 0, (LPARAM)&info TSRMLS_CC);
  1403. }
  1404. STDMETHODIMP TPHPScriptingEngine::ParseScriptText(
  1405. /* [in] */ LPCOLESTR pstrCode,
  1406. /* [in] */ LPCOLESTR pstrItemName,
  1407. /* [in] */ IUnknown *punkContext,
  1408. /* [in] */ LPCOLESTR pstrDelimiter,
  1409. /* [in] */ DWORD dwSourceContextCookie,
  1410. /* [in] */ ULONG ulStartingLineNumber,
  1411. /* [in] */ DWORD dwFlags,
  1412. /* [out] */ VARIANT *pvarResult,
  1413. /* [out] */ EXCEPINFO *pexcepinfo)
  1414. {
  1415. struct php_active_script_parse_info info;
  1416. TSRMLS_FETCH();
  1417. info.pstrCode = pstrCode;
  1418. info.pstrItemName = pstrItemName;
  1419. info.punkContext = punkContext;
  1420. info.pstrDelimiter = pstrDelimiter;
  1421. info.dwSourceContextCookie = dwSourceContextCookie;
  1422. info.ulStartingLineNumber = ulStartingLineNumber;
  1423. info.dwFlags = dwFlags;
  1424. info.pvarResult = pvarResult;
  1425. info.pexcepinfo = pexcepinfo;
  1426. return SEND_THREAD_MESSAGE(this, PHPSE_PARSE_SCRIPT, 0, (LPARAM)&info TSRMLS_CC);
  1427. }
  1428. STDMETHODIMP TPHPScriptingEngine::ParseProcedureText(
  1429. /* [in] */ LPCOLESTR pstrCode,
  1430. /* [in] */ LPCOLESTR pstrFormalParams,
  1431. /* [in] */ LPCOLESTR pstrProcedureName,
  1432. /* [in] */ LPCOLESTR pstrItemName,
  1433. /* [in] */ IUnknown *punkContext,
  1434. /* [in] */ LPCOLESTR pstrDelimiter,
  1435. /* [in] */ DWORD dwSourceContextCookie,
  1436. /* [in] */ ULONG ulStartingLineNumber,
  1437. /* [in] */ DWORD dwFlags,
  1438. /* [out] */ IDispatch **ppdisp)
  1439. {
  1440. struct php_active_script_parse_proc_info info;
  1441. HRESULT ret;
  1442. TSRMLS_FETCH();
  1443. info.pstrCode = pstrCode;
  1444. info.pstrFormalParams = pstrFormalParams;
  1445. info.pstrProcedureName = pstrProcedureName;
  1446. info.pstrItemName = pstrItemName;
  1447. info.punkContext = punkContext;
  1448. info.pstrDelimiter = pstrDelimiter;
  1449. info.dwSourceContextCookie = dwSourceContextCookie;
  1450. info.ulStartingLineNumber = ulStartingLineNumber;
  1451. info.dwFlags = dwFlags;
  1452. ret = SEND_THREAD_MESSAGE(this, PHPSE_PARSE_PROC, 0, (LPARAM)&info TSRMLS_CC);
  1453. if (ret == S_OK)
  1454. ret = GIT_get(info.dispcookie, IID_IDispatch, (void**)ppdisp);
  1455. trace("ParseProc: ret=%08x disp=%08x\n", ret, *ppdisp);
  1456. return ret;
  1457. }
  1458. extern "C"
  1459. void activescript_error_func(int type, const char *error_msg, ...)
  1460. {
  1461. TSRMLS_FETCH();
  1462. TPHPScriptingEngine *engine = (TPHPScriptingEngine*)SG(server_context);
  1463. }
  1464. class TActiveScriptError:
  1465. public IActiveScriptError
  1466. {
  1467. protected:
  1468. volatile LONG m_refcount;
  1469. public:
  1470. /* IUnknown */
  1471. STDMETHODIMP QueryInterface(REFIID iid, void **ppvObject) {
  1472. *ppvObject = NULL;
  1473. if (IsEqualGUID(IID_IActiveScriptError, iid)) {
  1474. *ppvObject = (IActiveScriptError*)this;
  1475. } else if (IsEqualGUID(IID_IUnknown, iid)) {
  1476. *ppvObject = this;
  1477. }
  1478. if (*ppvObject) {
  1479. AddRef();
  1480. return S_OK;
  1481. }
  1482. return E_NOINTERFACE;
  1483. }
  1484. STDMETHODIMP_(DWORD) AddRef(void) {
  1485. return InterlockedIncrement(const_cast<long*> (&m_refcount));
  1486. }
  1487. STDMETHODIMP_(DWORD) Release(void) {
  1488. DWORD ret = InterlockedDecrement(const_cast<long*> (&m_refcount));
  1489. trace("Release: errobj refcount=%d\n", ret);
  1490. if (ret == 0)
  1491. delete this;
  1492. return ret;
  1493. }
  1494. HRESULT STDMETHODCALLTYPE GetExceptionInfo(
  1495. /* [out] */ EXCEPINFO *pexcepinfo)
  1496. {
  1497. memset(pexcepinfo, 0, sizeof(EXCEPINFO));
  1498. pexcepinfo->bstrDescription = SysAllocString(m_message);
  1499. pexcepinfo->bstrSource = SysAllocString(m_filename);
  1500. pexcepinfo->wCode = 1000;
  1501. return S_OK;
  1502. }
  1503. HRESULT STDMETHODCALLTYPE GetSourcePosition(
  1504. /* [out] */ DWORD *pdwSourceContext,
  1505. /* [out] */ ULONG *pulLineNumber,
  1506. /* [out] */ LONG *plCharacterPosition)
  1507. {
  1508. *pdwSourceContext = 0;
  1509. *pulLineNumber = m_lineno;
  1510. *plCharacterPosition = 0;
  1511. return S_OK;
  1512. }
  1513. HRESULT STDMETHODCALLTYPE GetSourceLineText(
  1514. /* [out] */ BSTR *pbstrSourceLine)
  1515. {
  1516. *pbstrSourceLine = NULL;
  1517. return E_FAIL;
  1518. }
  1519. BSTR m_filename, m_message;
  1520. UINT m_lineno;
  1521. TActiveScriptError(const char *filename, const uint lineno, const char *message)
  1522. {
  1523. m_refcount = 0; /* start with zero refcount because this object is passed
  1524. * directly to the script site; it will call addref */
  1525. m_filename = TWideString::bstr_from_ansi((char*)filename);
  1526. m_message = TWideString::bstr_from_ansi((char*)message);
  1527. m_lineno = lineno;
  1528. }
  1529. ~TActiveScriptError()
  1530. {
  1531. trace("%08x: cleaning up error object\n", this);
  1532. SysFreeString(m_filename);
  1533. SysFreeString(m_message);
  1534. }
  1535. };
  1536. extern "C"
  1537. void activescript_error_handler(int type, const char *error_filename,
  1538. const uint error_lineno, const char *format, va_list args)
  1539. {
  1540. TSRMLS_FETCH();
  1541. char *buf;
  1542. int buflen;
  1543. TPHPScriptingEngine *engine = (TPHPScriptingEngine*)SG(server_context);
  1544. buflen = vspprintf(&buf, PG(log_errors_max_len), format, args);
  1545. trace("%08x: Error: %s\n", engine, buf);
  1546. /* if it's a fatal error, report it using IActiveScriptError. */
  1547. switch(type) {
  1548. case E_ERROR:
  1549. case E_CORE_ERROR:
  1550. case E_COMPILE_ERROR:
  1551. case E_USER_ERROR:
  1552. case E_PARSE:
  1553. /* trigger an error in the host */
  1554. TActiveScriptError *eobj = new TActiveScriptError(error_filename, error_lineno, buf);
  1555. trace("raising error object!\n");
  1556. if (engine->m_pass_eng)
  1557. engine->m_pass_eng->OnScriptError(eobj);
  1558. /* now throw the exception to abort execution */
  1559. if (engine->m_err_trap)
  1560. longjmp(*engine->m_err_trap, 1);
  1561. break;
  1562. }
  1563. efree(buf);
  1564. }