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.

1877 lines
51 KiB

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