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 5 |
  4. +----------------------------------------------------------------------+
  5. | Copyright (c) 1997-2004 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 "php5activescript.h"
  43. #include "ext/com_dotnet/php_com_dotnet.h"
  44. #include "ext/com_dotnet/php_com_dotnet_internal.h"
  45. }
  46. #include "php_ticks.h"
  47. #include "php5as_scriptengine.h"
  48. #include "php5as_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 TSRMLS_DC);
  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 TSRMLS_CC);
  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 TSRMLS_DC)
  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 TSRMLS_CC);
  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 TSRMLS_CC);
  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_com_variant_from_zval(varResult, retval_ptr, CP_ACP TSRMLS_CC);
  517. zval_ptr_dtor(&retval_ptr);
  518. }
  519. return 1;
  520. }
  521. static void frag_dtor(void *pDest)
  522. {
  523. TSRMLS_FETCH();
  524. code_frag *frag = *(code_frag**)pDest;
  525. free_code_fragment(frag TSRMLS_CC);
  526. }
  527. /* }}} */
  528. /* glue for getting back into the OO land */
  529. static DWORD WINAPI begin_engine_thread(LPVOID param)
  530. {
  531. TPHPScriptingEngine *engine = (TPHPScriptingEngine*)param;
  532. engine->engine_thread_func();
  533. trace("engine thread has really gone away!\n");
  534. return 0;
  535. }
  536. TPHPScriptingEngine::TPHPScriptingEngine()
  537. {
  538. m_scriptstate = SCRIPTSTATE_UNINITIALIZED;
  539. m_pass = NULL;
  540. m_in_main = 0;
  541. m_stop_main = 0;
  542. m_err_trap = NULL;
  543. m_lambda_count = 0;
  544. m_pass_eng = NULL;
  545. m_refcount = 1;
  546. m_basethread = tsrm_thread_id();
  547. m_mutex = tsrm_mutex_alloc();
  548. m_sync_thread_msg = CreateEvent(NULL, TRUE, FALSE, NULL);
  549. TPHPClassFactory::AddToObjectCount();
  550. m_engine_thread_handle = CreateThread(NULL, 0, begin_engine_thread, this, 0, &m_enginethread);
  551. CloseHandle(m_engine_thread_handle);
  552. }
  553. void activescript_run_ticks(int count)
  554. {
  555. MSG msg;
  556. TSRMLS_FETCH();
  557. TPHPScriptingEngine *engine;
  558. trace("ticking %d\n", count);
  559. engine = (TPHPScriptingEngine*)SG(server_context);
  560. /* PostThreadMessage(engine->m_enginethread, PHPSE_DUMMY_TICK, 0, 0); */
  561. while(PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
  562. if (msg.hwnd) {
  563. PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
  564. TranslateMessage(&msg);
  565. DispatchMessage(&msg);
  566. } else {
  567. break;
  568. }
  569. }
  570. }
  571. /* Synchronize with the engine thread */
  572. HRESULT TPHPScriptingEngine::SendThreadMessage(LONG msg, WPARAM wparam, LPARAM lparam)
  573. {
  574. HRESULT ret;
  575. if (m_enginethread == 0)
  576. return E_UNEXPECTED;
  577. trace("I'm waiting for a mutex in SendThreadMessage\n this=%08x ethread=%08x msg=%08x\n",
  578. this, m_enginethread, msg);
  579. tsrm_mutex_lock(m_mutex);
  580. ResetEvent(m_sync_thread_msg);
  581. /* If we call PostThreadMessage before the thread has created the queue, the message
  582. * posting fails. MSDN docs recommend the following course of action */
  583. while (!PostThreadMessage(m_enginethread, msg, wparam, lparam)) {
  584. Sleep(50);
  585. if (m_enginethread == 0) {
  586. tsrm_mutex_unlock(m_mutex);
  587. trace("breaking out of dodgy busy wait\n");
  588. return E_UNEXPECTED;
  589. }
  590. }
  591. /* Wait for the event object to be signalled.
  592. * This is a nice "blocking without blocking" wait; window messages are dispatched
  593. * and everything works out quite nicely */
  594. while(1) {
  595. DWORD result = MsgWaitForMultipleObjects(1, &m_sync_thread_msg, FALSE, 4000, QS_ALLINPUT);
  596. if (result == WAIT_OBJECT_0 + 1) {
  597. /* Dispatch some messages */
  598. MSG msg;
  599. while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
  600. //trace("dispatching message while waiting\n");
  601. TranslateMessage(&msg);
  602. DispatchMessage(&msg);
  603. }
  604. } else if (result == WAIT_TIMEOUT) {
  605. trace("timeout while waiting for thread reply\n");
  606. } else {
  607. /* the event was signalled */
  608. break;
  609. }
  610. }
  611. ret = m_sync_thread_ret;
  612. ResetEvent(m_sync_thread_msg);
  613. tsrm_mutex_unlock(m_mutex);
  614. return ret;
  615. }
  616. TPHPScriptingEngine::~TPHPScriptingEngine()
  617. {
  618. trace("\n\n *** Engine Destructor Called\n\n");
  619. if (m_scriptstate != SCRIPTSTATE_UNINITIALIZED && m_scriptstate != SCRIPTSTATE_CLOSED && m_enginethread)
  620. Close();
  621. PostThreadMessage(m_enginethread, WM_QUIT, 0, 0);
  622. TPHPClassFactory::RemoveFromObjectCount();
  623. tsrm_mutex_free(m_mutex);
  624. }
  625. /* Set some executor globals and execute a zend_op_array.
  626. * The declaration looks wierd because this can be invoked from
  627. * zend_hash_apply_with_argument */
  628. static int execute_main(void *pDest, void *arg TSRMLS_DC)
  629. {
  630. code_frag *frag = *(code_frag**)pDest;
  631. if (frag->fragtype == FRAG_MAIN && !(frag->engine->m_in_main && frag->engine->m_stop_main))
  632. execute_code_fragment(frag, NULL, NULL TSRMLS_CC);
  633. return ZEND_HASH_APPLY_KEEP;
  634. }
  635. static int clone_frags(void *pDest, void *arg TSRMLS_DC)
  636. {
  637. code_frag *frag, *src = *(code_frag**)pDest;
  638. TPHPScriptingEngine *engine = (TPHPScriptingEngine*)arg;
  639. if (src->persistent) {
  640. frag = clone_code_fragment(src, engine TSRMLS_CC);
  641. if (frag)
  642. zend_hash_next_index_insert(&engine->m_frags, &frag, sizeof(code_frag*), NULL);
  643. else
  644. trace("WARNING: clone failed!\n");
  645. }
  646. return ZEND_HASH_APPLY_KEEP;
  647. }
  648. HRESULT TPHPScriptingEngine::engine_thread_handler(LONG msg, WPARAM wparam, LPARAM lParam, int *handled TSRMLS_DC)
  649. {
  650. HRESULT ret = S_OK;
  651. trace("engine_thread_handler: running in thread %08x, should be %08x msg=%08x this=%08x\n",
  652. tsrm_thread_id(), m_enginethread, msg, this);
  653. if (handled)
  654. *handled = 1;
  655. if (m_enginethread == 0)
  656. return E_UNEXPECTED;
  657. switch(msg) {
  658. case PHPSE_ADD_TYPELIB:
  659. {
  660. struct php_active_script_add_tlb_info *info = (struct php_active_script_add_tlb_info*)lParam;
  661. ITypeLib *TypeLib;
  662. if (SUCCEEDED(LoadRegTypeLib(*info->rguidTypeLib, (USHORT)info->dwMajor,
  663. (USHORT)info->dwMinor, LANG_NEUTRAL, &TypeLib))) {
  664. php_com_import_typelib(TypeLib, CONST_CS, CP_ACP TSRMLS_CC);
  665. TypeLib->Release();
  666. }
  667. }
  668. break;
  669. case PHPSE_STATE_CHANGE:
  670. {
  671. /* handle the state change here */
  672. SCRIPTSTATE ss = (SCRIPTSTATE)lParam;
  673. int start_running = 0;
  674. trace("%08x: DoSetScriptState(current=%s, new=%s)\n",
  675. this,
  676. scriptstate_to_string(m_scriptstate),
  677. scriptstate_to_string(ss));
  678. if (m_scriptstate == SCRIPTSTATE_INITIALIZED && (ss == SCRIPTSTATE_STARTED || ss == SCRIPTSTATE_CONNECTED))
  679. start_running = 1;
  680. m_scriptstate = ss;
  681. /* inform host/site of the change */
  682. if (m_pass_eng)
  683. m_pass_eng->OnStateChange(m_scriptstate);
  684. if (start_running) {
  685. /* run "main()", as described in the docs */
  686. if (m_pass_eng)
  687. m_pass_eng->OnEnterScript();
  688. trace("%08x: apply execute main to m_frags\n", this);
  689. m_in_main = 1;
  690. m_stop_main = 0;
  691. zend_hash_apply_with_argument(&m_frags, execute_main, this TSRMLS_CC);
  692. m_in_main = 0;
  693. trace("%08x: --- done execute main\n", this);
  694. if (m_pass_eng)
  695. m_pass_eng->OnLeaveScript();
  696. /* docs are a bit ambiguous here, but it appears that we should
  697. * inform the host that the main script execution has completed,
  698. * and also what the return value is */
  699. VARIANT varRes;
  700. VariantInit(&varRes);
  701. if (m_pass_eng)
  702. m_pass_eng->OnScriptTerminate(&varRes, NULL);
  703. /*
  704. m_scriptstate = SCRIPTSTATE_INITIALIZED;
  705. if (m_pass_eng)
  706. m_pass_eng->OnStateChange(m_scriptstate);
  707. */
  708. }
  709. }
  710. break;
  711. case PHPSE_INIT_NEW:
  712. {
  713. /* Prepare PHP/ZE for use */
  714. trace("%08x: m_frags : INIT NEW\n", this);
  715. zend_hash_init(&m_frags, 0, NULL, frag_dtor, TRUE);
  716. SG(options) |= SAPI_OPTION_NO_CHDIR;
  717. SG(server_context) = this;
  718. /* override the default PHP error callback */
  719. zend_error_cb = activescript_error_handler;
  720. zend_alter_ini_entry("register_argc_argv", 19, "1", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE);
  721. zend_alter_ini_entry("html_errors", 12, "0", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE);
  722. zend_alter_ini_entry("implicit_flush", 15, "1", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE);
  723. zend_alter_ini_entry("max_execution_time", 19, "0", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE);
  724. php_request_startup(TSRMLS_C);
  725. PG(during_request_startup) = 0;
  726. trace("\n\n *** ticks func at %08x %08x ***\n\n\n", activescript_run_ticks, &activescript_run_ticks);
  727. // php_add_tick_function(activescript_run_ticks);
  728. }
  729. break;
  730. case PHPSE_CLOSE:
  731. {
  732. /* Close things down */
  733. trace("%08x: m_frags : CLOSE/DESTROY\n", this);
  734. m_scriptstate = SCRIPTSTATE_CLOSED;
  735. if (m_pass_eng) {
  736. m_pass_eng->OnStateChange(m_scriptstate);
  737. trace("%08x: release site from this side\n", this);
  738. m_pass_eng->Release();
  739. m_pass_eng = NULL;
  740. }
  741. zend_hash_destroy(&m_frags);
  742. php_request_shutdown(NULL);
  743. break;
  744. }
  745. break;
  746. case PHPSE_CLONE:
  747. {
  748. /* Clone the engine state. This is semantically equal to serializing all
  749. * the parsed code from the source and unserializing it in the dest (this).
  750. * IE doesn't appear to use it, but Windows Script Host does. I'd expect
  751. * ASP/ASP.NET to do so also.
  752. *
  753. * FIXME: Probably won't work with IActiveScriptParseProcedure scriplets
  754. * */
  755. TPHPScriptingEngine *src = (TPHPScriptingEngine*)lParam;
  756. trace("%08x: m_frags : CLONE\n", this);
  757. zend_hash_apply_with_argument(&src->m_frags, clone_frags, this TSRMLS_CC);
  758. }
  759. break;
  760. case PHPSE_ADD_SCRIPTLET:
  761. {
  762. /* Parse/compile a chunk of script that will act as an event handler.
  763. * If the host supports IActiveScriptParseProcedure, this code will
  764. * not be called.
  765. * The docs are (typically) vague: AFAICT, once the code has been
  766. * compiled, we are supposed to arrange for an IConnectionPoint
  767. * advisory connection to the item/subitem, once the script
  768. * moves into SCRIPTSTATE_CONNECTED.
  769. * That's a lot of work!
  770. *
  771. * FIXME: this is currently almost useless
  772. * */
  773. struct php_active_script_add_scriptlet_info *info = (struct php_active_script_add_scriptlet_info*)lParam;
  774. TWideString
  775. default_name(info->pstrDefaultName),
  776. code(info->pstrCode),
  777. item_name(info->pstrItemName),
  778. sub_item_name(info->pstrSubItemName),
  779. event_name(info->pstrEventName),
  780. delimiter(info->pstrDelimiter);
  781. /* lets invent a function name for the scriptlet */
  782. char sname[256];
  783. /* should check if the name is already used! */
  784. if (info->pstrDefaultName)
  785. strcpy(sname, default_name.ansi_string());
  786. else {
  787. sname[0] = 0;
  788. strcat(sname, "__");
  789. if (info->pstrItemName) {
  790. strcat(sname, item_name.ansi_string());
  791. strcat(sname, "_");
  792. }
  793. if (info->pstrSubItemName) {
  794. strcat(sname, sub_item_name.ansi_string());
  795. strcat(sname, "_");
  796. }
  797. if (info->pstrEventName)
  798. strcat(sname, event_name.ansi_string());
  799. }
  800. 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",
  801. this, scriptstate_to_string(m_scriptstate),
  802. default_name.safe_ansi_string(), code.safe_ansi_string(), item_name.safe_ansi_string(),
  803. sub_item_name.safe_ansi_string(), event_name.safe_ansi_string(), delimiter.safe_ansi_string(),
  804. info->ulStartingLineNumber);
  805. code_frag *frag = compile_code_fragment(
  806. FRAG_SCRIPTLET,
  807. sname,
  808. info->pstrCode,
  809. info->ulStartingLineNumber,
  810. info->pexcepinfo,
  811. this
  812. TSRMLS_CC);
  813. if (frag) {
  814. frag->persistent = (info->dwFlags & SCRIPTTEXT_ISPERSISTENT);
  815. zend_hash_next_index_insert(&m_frags, &frag, sizeof(code_frag*), NULL);
  816. /*
  817. ScriptProcedureDispatch *disp = new ScriptProcedureDispatch;
  818. disp->AddRef();
  819. disp->m_frag = frag;
  820. disp->m_procflags = info->dwFlags;
  821. disp->m_engine = this;
  822. frag->ptr = disp;
  823. *info->ppdisp = disp;
  824. */
  825. ret = S_OK;
  826. } else {
  827. ret = DISP_E_EXCEPTION;
  828. }
  829. *info->pbstrName = TWideString::bstr_from_ansi(sname);
  830. trace("%08x: done with scriptlet %s\n", this, sname);
  831. }
  832. break;
  833. case PHPSE_GET_DISPATCH:
  834. {
  835. struct php_active_script_get_dispatch_info *info = (struct php_active_script_get_dispatch_info *)lParam;
  836. IDispatch *disp = NULL;
  837. if (info->pstrItemName != NULL) {
  838. zval **tmp;
  839. /* use this rather than php_OLECHAR_to_char because we want to avoid emalloc here */
  840. TWideString itemname(info->pstrItemName);
  841. /* Get that item from the global namespace.
  842. * If it is an object, export it as a dispatchable object.
  843. * */
  844. if (zend_hash_find(&EG(symbol_table), itemname.ansi_string(),
  845. itemname.ansi_len() + 1, (void**)&tmp) == SUCCESS) {
  846. if (Z_TYPE_PP(tmp) == IS_OBJECT) {
  847. /* FIXME: if this causes an allocation (emalloc) and we are
  848. * not in the engine thread, things could get ugly!!! */
  849. disp = php_com_wrapper_export(*tmp TSRMLS_CC);
  850. }
  851. }
  852. } else {
  853. /* This object represents PHP global namespace */
  854. disp = (IDispatch*) new ScriptDispatch;
  855. }
  856. if (disp) {
  857. ret = GIT_put(disp, IID_IDispatch, &info->dispatch);
  858. disp->Release();
  859. } else {
  860. ret = S_FALSE;
  861. }
  862. }
  863. break;
  864. case PHPSE_ADD_NAMED_ITEM:
  865. {
  866. /* The Host uses this to add objects to the global namespace.
  867. * Some objects are intended to have their child properties
  868. * globally visible, so we add those to the global namespace too.
  869. * */
  870. struct php_active_script_add_named_item_info *info = (struct php_active_script_add_named_item_info *)lParam;
  871. TWideString name(info->pstrName);
  872. IDispatch *disp;
  873. if (SUCCEEDED(GIT_get(info->marshal, IID_IDispatch, (void**)&disp)))
  874. add_to_global_namespace(disp, info->dwFlags, name.ansi_string() TSRMLS_CC);
  875. }
  876. break;
  877. case PHPSE_SET_SITE:
  878. {
  879. if (m_pass_eng) {
  880. m_pass_eng->Release();
  881. m_pass_eng = NULL;
  882. }
  883. if (lParam)
  884. GIT_get(lParam, IID_IActiveScriptSite, (void**)&m_pass_eng);
  885. trace("%08x: site (engine-side) is now %08x (base=%08x)\n", this, m_pass_eng, m_pass);
  886. }
  887. break;
  888. case PHPSE_EXEC_PROC:
  889. {
  890. ScriptProcedureDispatch *disp = (ScriptProcedureDispatch *)lParam;
  891. execute_code_fragment(disp->m_frag, NULL, NULL TSRMLS_CC);
  892. }
  893. break;
  894. case PHPSE_PARSE_PROC:
  895. {
  896. /* This is the IActiveScriptParseProcedure implementation.
  897. * IE uses this to request for an IDispatch that it will invoke in
  898. * response to some event, and tells us the code that it wants to
  899. * run.
  900. * We compile the code and pass it back a dispatch object.
  901. * The object will then serialize access to the engine thread and
  902. * execute the opcodes */
  903. struct php_active_script_parse_proc_info *info = (struct php_active_script_parse_proc_info*)lParam;
  904. TWideString
  905. formal_params(info->pstrFormalParams),
  906. procedure_name(info->pstrProcedureName),
  907. item_name(info->pstrItemName),
  908. delimiter(info->pstrDelimiter);
  909. trace("%08x: ParseProc:\n state=%s\nparams=%s\nproc=%s\nitem=%s\n delim=%s\n line=%d\n",
  910. this, scriptstate_to_string(m_scriptstate),
  911. formal_params.ansi_string(), procedure_name.ansi_string(),
  912. item_name.safe_ansi_string(), delimiter.safe_ansi_string(),
  913. info->ulStartingLineNumber);
  914. code_frag *frag = compile_code_fragment(
  915. FRAG_PROCEDURE,
  916. NULL,
  917. info->pstrCode,
  918. info->ulStartingLineNumber,
  919. NULL,
  920. this
  921. TSRMLS_CC);
  922. if (frag) {
  923. frag->persistent = (info->dwFlags & SCRIPTTEXT_ISPERSISTENT);
  924. zend_hash_next_index_insert(&m_frags, &frag, sizeof(code_frag*), NULL);
  925. ScriptProcedureDispatch *disp = new ScriptProcedureDispatch;
  926. disp->m_frag = frag;
  927. disp->m_procflags = info->dwFlags;
  928. disp->m_engine = this;
  929. frag->ptr = disp;
  930. info->dispcookie = disp->m_gitcookie;
  931. } else {
  932. ret = DISP_E_EXCEPTION;
  933. }
  934. }
  935. break;
  936. case PHPSE_PARSE_SCRIPT:
  937. {
  938. struct php_active_script_parse_info *info = (struct php_active_script_parse_info*)lParam;
  939. int doexec;
  940. TWideString
  941. code(info->pstrCode),
  942. item_name(info->pstrItemName),
  943. delimiter(info->pstrDelimiter);
  944. trace("%08x: ParseScriptText:\n state=%s\ncode=%s\n item=%s\n delim=%s\n line=%d\n",
  945. this, scriptstate_to_string(m_scriptstate),
  946. code.safe_ansi_string(), item_name.safe_ansi_string(), delimiter.safe_ansi_string(),
  947. info->ulStartingLineNumber);
  948. code_frag *frag = compile_code_fragment(
  949. FRAG_MAIN,
  950. info->dwFlags & SCRIPTTEXT_ISEXPRESSION ? FRAG_CREATE_FUNC : NULL,
  951. info->pstrCode,
  952. info->ulStartingLineNumber,
  953. info->pexcepinfo,
  954. this
  955. TSRMLS_CC);
  956. doexec = (info->dwFlags & SCRIPTTEXT_ISEXPRESSION) ||
  957. m_scriptstate == SCRIPTSTATE_STARTED ||
  958. m_scriptstate == SCRIPTSTATE_CONNECTED ||
  959. m_scriptstate == SCRIPTSTATE_DISCONNECTED;
  960. if (frag) {
  961. frag->persistent = (info->dwFlags & SCRIPTTEXT_ISPERSISTENT);
  962. ret = S_OK;
  963. if (info->dwFlags & SCRIPTTEXT_ISEXPRESSION) {
  964. if (m_scriptstate == SCRIPTSTATE_INITIALIZED) {
  965. /* not allowed to execute code in this state */
  966. ret = E_UNEXPECTED;
  967. doexec = 0;
  968. }
  969. }
  970. if (doexec) {
  971. /* execute the code as an expression */
  972. if (!execute_code_fragment(frag, info->pvarResult, info->pexcepinfo TSRMLS_CC))
  973. ret = DISP_E_EXCEPTION;
  974. }
  975. zend_hash_next_index_insert(&m_frags, &frag, sizeof(code_frag*), NULL);
  976. } else {
  977. ret = DISP_E_EXCEPTION;
  978. }
  979. if (info->pvarResult) {
  980. VariantInit(info->pvarResult);
  981. }
  982. }
  983. break;
  984. default:
  985. trace("unhandled message type %08x\n", msg);
  986. if (handled)
  987. *handled = 0;
  988. }
  989. return ret;
  990. }
  991. /* The PHP/Zend state actually lives in this thread */
  992. void TPHPScriptingEngine::engine_thread_func(void)
  993. {
  994. TSRMLS_FETCH();
  995. int handled;
  996. int terminated = 0;
  997. MSG msg;
  998. trace("%08x: engine thread started up!\n", this);
  999. CoInitializeEx(0, COINIT_MULTITHREADED);
  1000. m_tsrm_hack = tsrm_ls;
  1001. while(!terminated) {
  1002. DWORD result = MsgWaitForMultipleObjects(0, NULL, FALSE, 4000, QS_ALLINPUT);
  1003. switch(result) {
  1004. case WAIT_OBJECT_0:
  1005. while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
  1006. if (msg.message == WM_QUIT) {
  1007. terminated = 1;
  1008. } else if (msg.hwnd) {
  1009. TranslateMessage(&msg);
  1010. DispatchMessage(&msg);
  1011. } else {
  1012. handled = 1;
  1013. m_sync_thread_ret = engine_thread_handler(msg.message, msg.wParam, msg.lParam, &handled TSRMLS_CC);
  1014. if (handled)
  1015. SetEvent(m_sync_thread_msg);
  1016. }
  1017. }
  1018. break;
  1019. case WAIT_TIMEOUT:
  1020. trace("thread wait timed out\n");
  1021. break;
  1022. default:
  1023. trace("some strange value\n");
  1024. }
  1025. }
  1026. #if 0
  1027. while(GetMessage(&msg, NULL, 0, 0)) {
  1028. if (msg.message == WM_QUIT)
  1029. break;
  1030. handled = 1;
  1031. m_sync_thread_ret = engine_thread_handler(msg.message, msg.wParam, msg.lParam, &handled TSRMLS_CC);
  1032. if (handled)
  1033. SetEvent(m_sync_thread_msg);
  1034. }
  1035. trace("%08x: engine thread exiting!!!!!\n", this);
  1036. #endif
  1037. m_enginethread = 0;
  1038. CoUninitialize();
  1039. }
  1040. /* Only call this in the context of the engine thread, or you'll be sorry.
  1041. *
  1042. * When SCRIPTITEM_GLOBALMEMBERS is set, we're only adding COM objects to the namespace.
  1043. * We could add *all* properties, but I don't like this idea; what if the value changes
  1044. * while the page is running? We'd be left with stale data.
  1045. *
  1046. * TODO: evaluate if it is appropriate to register as an auto_global
  1047. * */
  1048. void TPHPScriptingEngine::add_to_global_namespace(IDispatch *disp, DWORD flags, char *name TSRMLS_DC)
  1049. {
  1050. zval *val;
  1051. ITypeInfo *typ;
  1052. int i;
  1053. unsigned int namelen;
  1054. FUNCDESC *func;
  1055. BSTR funcname;
  1056. TYPEATTR *attr;
  1057. DISPPARAMS dispparams;
  1058. VARIANT vres;
  1059. ITypeInfo *rettyp;
  1060. TYPEATTR *retattr;
  1061. trace("Add %s to global namespace\n", name);
  1062. php_com_wrap_dispatch(val, disp, CP_ACP TSRMLS_CC);
  1063. if (val == NULL) {
  1064. disp->Release();
  1065. return;
  1066. }
  1067. ZEND_SET_SYMBOL(&EG(symbol_table), name, val);
  1068. if ((flags & SCRIPTITEM_GLOBALMEMBERS) == 0) {
  1069. disp->Release();
  1070. return;
  1071. }
  1072. /* Enumerate properties and add those too */
  1073. if (FAILED(disp->GetTypeInfo(0, 0, &typ))) {
  1074. disp->Release();
  1075. return;
  1076. }
  1077. if (SUCCEEDED(typ->GetTypeAttr(&attr))) {
  1078. for (i = 0; i < attr->cFuncs; i++) {
  1079. if (FAILED(typ->GetFuncDesc(i, &func)))
  1080. continue;
  1081. /* Look at its type */
  1082. if (func->invkind == INVOKE_PROPERTYGET
  1083. && VT_PTR == func->elemdescFunc.tdesc.vt
  1084. && VT_USERDEFINED == func->elemdescFunc.tdesc.lptdesc->vt
  1085. && SUCCEEDED(typ->GetRefTypeInfo(func->elemdescFunc.tdesc.lptdesc->hreftype, &rettyp)))
  1086. {
  1087. if (SUCCEEDED(rettyp->GetTypeAttr(&retattr))) {
  1088. if (retattr->typekind == TKIND_DISPATCH) {
  1089. /* It's dispatchable */
  1090. /* get the value */
  1091. dispparams.cArgs = 0;
  1092. dispparams.cNamedArgs = 0;
  1093. VariantInit(&vres);
  1094. if (SUCCEEDED(disp->Invoke(func->memid, IID_NULL, 0, func->invkind,
  1095. &dispparams, &vres, NULL, NULL))) {
  1096. /* Get its dispatch */
  1097. IDispatch *sub = NULL;
  1098. if (V_VT(&vres) == VT_UNKNOWN)
  1099. V_UNKNOWN(&vres)->QueryInterface(IID_IDispatch, (void**)&sub);
  1100. else if (V_VT(&vres) == VT_DISPATCH)
  1101. sub = V_DISPATCH(&vres);
  1102. if (sub) {
  1103. /* find out its name */
  1104. typ->GetDocumentation(func->memid, &funcname, NULL, NULL, NULL);
  1105. name = php_com_olestring_to_string(funcname, &namelen, CP_ACP, 0);
  1106. /* add to namespace */
  1107. zval *subval;
  1108. php_com_wrap_dispatch(subval, sub, CP_ACP TSRMLS_CC);
  1109. if (subval) {
  1110. ZEND_SET_SYMBOL(&EG(symbol_table), name, subval);
  1111. }
  1112. efree(name);
  1113. SysFreeString(funcname);
  1114. }
  1115. VariantClear(&vres);
  1116. }
  1117. }
  1118. rettyp->ReleaseTypeAttr(retattr);
  1119. }
  1120. rettyp->Release();
  1121. }
  1122. typ->ReleaseFuncDesc(func);
  1123. }
  1124. typ->ReleaseTypeAttr(attr);
  1125. }
  1126. disp->Release();
  1127. }
  1128. STDMETHODIMP_(DWORD) TPHPScriptingEngine::AddRef(void)
  1129. {
  1130. return InterlockedIncrement(const_cast<long*> (&m_refcount));
  1131. }
  1132. STDMETHODIMP_(DWORD) TPHPScriptingEngine::Release(void)
  1133. {
  1134. DWORD ret = InterlockedDecrement(const_cast<long*> (&m_refcount));
  1135. if (ret == 0) {
  1136. trace("%08x: Release: zero refcount, destroy the engine!\n", this);
  1137. delete this;
  1138. }
  1139. return ret;
  1140. }
  1141. STDMETHODIMP TPHPScriptingEngine::QueryInterface(REFIID iid, void **ppvObject)
  1142. {
  1143. *ppvObject = NULL;
  1144. if (IsEqualGUID(IID_IActiveScript, iid)) {
  1145. *ppvObject = (IActiveScript*)this;
  1146. } else if (IsEqualGUID(IID_IActiveScriptParse, iid)) {
  1147. *ppvObject = (IActiveScriptParse*)this;
  1148. } else if (IsEqualGUID(IID_IActiveScriptParseProcedure, iid)) {
  1149. *ppvObject = (IActiveScriptParseProcedure*)this;
  1150. } else if (IsEqualGUID(IID_IUnknown, iid)) {
  1151. *ppvObject = this;
  1152. } else {
  1153. LPOLESTR guidw;
  1154. StringFromCLSID(iid, &guidw);
  1155. {
  1156. TWideString guid(guidw);
  1157. trace("%08x: QueryInterface for unsupported %s\n", this, guid.ansi_string());
  1158. }
  1159. CoTaskMemFree(guidw);
  1160. }
  1161. if (*ppvObject) {
  1162. AddRef();
  1163. return S_OK;
  1164. }
  1165. return E_NOINTERFACE;
  1166. }
  1167. /* This is called by the host to set the scrite site.
  1168. * It also defines the base thread. */
  1169. STDMETHODIMP TPHPScriptingEngine::SetScriptSite(IActiveScriptSite *pass)
  1170. {
  1171. TSRMLS_FETCH();
  1172. tsrm_mutex_lock(m_mutex);
  1173. trace("%08x: -----> Base thread is %08x\n", this, tsrm_thread_id());
  1174. if (m_pass) {
  1175. m_pass->Release();
  1176. m_pass = NULL;
  1177. SEND_THREAD_MESSAGE(this, PHPSE_SET_SITE, 0, 0 TSRMLS_CC);
  1178. }
  1179. if (pass == NULL) {
  1180. trace("Closing down site; we should have no references to objects from the host\n"
  1181. " m_pass=%08x\n m_pass_eng=%08x\n What about named items??\n",
  1182. m_pass, m_pass_eng);
  1183. }
  1184. m_pass = pass;
  1185. if (m_pass) {
  1186. m_pass->AddRef();
  1187. DWORD cookie;
  1188. if (SUCCEEDED(GIT_put(m_pass, IID_IActiveScriptSite, &cookie)))
  1189. SEND_THREAD_MESSAGE(this, PHPSE_SET_SITE, 0, cookie TSRMLS_CC);
  1190. if (m_scriptstate == SCRIPTSTATE_UNINITIALIZED)
  1191. SEND_THREAD_MESSAGE(this, PHPSE_STATE_CHANGE, 0, SCRIPTSTATE_INITIALIZED TSRMLS_CC);
  1192. }
  1193. tsrm_mutex_unlock(m_mutex);
  1194. return S_OK;
  1195. }
  1196. STDMETHODIMP TPHPScriptingEngine::GetScriptSite(REFIID riid, void **ppvObject)
  1197. {
  1198. HRESULT ret = S_FALSE;
  1199. trace("%08x: GetScriptSite()\n", this);
  1200. tsrm_mutex_lock(m_mutex);
  1201. if (m_pass)
  1202. ret = m_pass->QueryInterface(riid, ppvObject);
  1203. tsrm_mutex_unlock(m_mutex);
  1204. return ret;
  1205. }
  1206. STDMETHODIMP TPHPScriptingEngine::SetScriptState(SCRIPTSTATE ss)
  1207. {
  1208. TSRMLS_FETCH();
  1209. trace("%08x: SetScriptState(%s)\n", this, scriptstate_to_string(ss));
  1210. return SEND_THREAD_MESSAGE(this, PHPSE_STATE_CHANGE, 0, ss TSRMLS_CC);
  1211. }
  1212. STDMETHODIMP TPHPScriptingEngine::GetScriptState(SCRIPTSTATE *pssState)
  1213. {
  1214. trace("%08x: GetScriptState(current=%s)\n", this, scriptstate_to_string(m_scriptstate));
  1215. tsrm_mutex_lock(m_mutex);
  1216. *pssState = m_scriptstate;
  1217. tsrm_mutex_unlock(m_mutex);
  1218. return S_OK;
  1219. }
  1220. STDMETHODIMP TPHPScriptingEngine::Close(void)
  1221. {
  1222. TSRMLS_FETCH();
  1223. if (m_pass) {
  1224. m_pass->Release();
  1225. m_pass = NULL;
  1226. }
  1227. SEND_THREAD_MESSAGE(this, PHPSE_CLOSE, 0, 0 TSRMLS_CC);
  1228. return S_OK;
  1229. }
  1230. /* Add an item to global namespace.
  1231. * This is called in the context of the base thread (or perhaps some other thread).
  1232. * We want to be able to work with the object in the engine thread, so we marshal
  1233. * it into a stream and let the engine thread deal with it.
  1234. * This works quite nicely when PHP scripts call into the object; threading is
  1235. * handled correctly. */
  1236. STDMETHODIMP TPHPScriptingEngine::AddNamedItem(LPCOLESTR pstrName, DWORD dwFlags)
  1237. {
  1238. struct php_active_script_add_named_item_info info;
  1239. TSRMLS_FETCH();
  1240. info.pstrName = pstrName;
  1241. info.dwFlags = dwFlags;
  1242. m_pass->GetItemInfo(pstrName, SCRIPTINFO_IUNKNOWN, &info.punk, NULL);
  1243. if (SUCCEEDED(GIT_put(info.punk, IID_IDispatch, &info.marshal))) {
  1244. SEND_THREAD_MESSAGE(this, PHPSE_ADD_NAMED_ITEM, 0, (LPARAM)&info TSRMLS_CC);
  1245. }
  1246. info.punk->Release();
  1247. return S_OK;
  1248. }
  1249. /* Bind to a type library */
  1250. STDMETHODIMP TPHPScriptingEngine::AddTypeLib(
  1251. /* [in] */ REFGUID rguidTypeLib,
  1252. /* [in] */ DWORD dwMajor,
  1253. /* [in] */ DWORD dwMinor,
  1254. /* [in] */ DWORD dwFlags)
  1255. {
  1256. struct php_active_script_add_tlb_info info;
  1257. TSRMLS_FETCH();
  1258. info.rguidTypeLib = &rguidTypeLib;
  1259. info.dwMajor = dwMajor;
  1260. info.dwMinor = dwMinor;
  1261. info.dwFlags = dwFlags;
  1262. SEND_THREAD_MESSAGE(this, PHPSE_ADD_TYPELIB, 0, (LPARAM)&info TSRMLS_CC);
  1263. return S_OK;
  1264. }
  1265. /* Returns an object representing the PHP Scripting Engine.
  1266. * Optionally, a client can request a particular item directly.
  1267. * For the moment, we only do the bare minimum amount of work
  1268. * for the engine to work correctly; we can flesh out this part
  1269. * a little later. */
  1270. STDMETHODIMP TPHPScriptingEngine::GetScriptDispatch(
  1271. /* [in] */ LPCOLESTR pstrItemName,
  1272. /* [out] */ IDispatch **ppdisp)
  1273. {
  1274. TSRMLS_FETCH();
  1275. *ppdisp = NULL;
  1276. struct php_active_script_get_dispatch_info info;
  1277. info.pstrItemName = pstrItemName;
  1278. info.dispatch = NULL;
  1279. /* This hack is required because the host is likely to query us
  1280. * for a dispatch if we use any of its objects from PHP script.
  1281. * Since the engine thread will be waiting for the return from
  1282. * a COM call, we need to deliberately poke a hole in thread
  1283. * safety so that it is possible to read the symbol table from
  1284. * outside the engine thread and give it a valid return value.
  1285. * This is "safe" only in this instance, since we are not modifying
  1286. * the engine state by looking up the dispatch (I hope).
  1287. * The scripting engine rules pretty much guarantee that this
  1288. * method is only called in the base thread. */
  1289. if (tsrm_thread_id() != m_enginethread) {
  1290. tsrm_ls = m_tsrm_hack;
  1291. trace("HEY: hacking thread safety!\n");
  1292. }
  1293. if (S_OK == engine_thread_handler(PHPSE_GET_DISPATCH, 0, (LPARAM)&info, NULL TSRMLS_CC)) {
  1294. GIT_get(info.dispatch, IID_IDispatch, (void**)ppdisp);
  1295. }
  1296. if (*ppdisp) {
  1297. return S_OK;
  1298. }
  1299. return S_FALSE;
  1300. }
  1301. STDMETHODIMP TPHPScriptingEngine::GetCurrentScriptThreadID(
  1302. /* [out] */ SCRIPTTHREADID *pstidThread)
  1303. {
  1304. // tsrm_mutex_lock(m_mutex);
  1305. trace("%08x: GetCurrentScriptThreadID()\n", this);
  1306. *pstidThread = m_enginethread;
  1307. // tsrm_mutex_unlock(m_mutex);
  1308. return S_OK;
  1309. }
  1310. STDMETHODIMP TPHPScriptingEngine::GetScriptThreadID(
  1311. /* [in] */ DWORD dwWin32ThreadId,
  1312. /* [out] */ SCRIPTTHREADID *pstidThread)
  1313. {
  1314. // tsrm_mutex_lock(m_mutex);
  1315. trace("%08x: GetScriptThreadID()\n", this);
  1316. *pstidThread = dwWin32ThreadId;
  1317. // tsrm_mutex_unlock(m_mutex);
  1318. return S_OK;
  1319. }
  1320. STDMETHODIMP TPHPScriptingEngine::GetScriptThreadState(
  1321. /* [in] */ SCRIPTTHREADID stidThread,
  1322. /* [out] */ SCRIPTTHREADSTATE *pstsState)
  1323. {
  1324. // tsrm_mutex_lock(m_mutex);
  1325. trace("%08x: GetScriptThreadState()\n", this);
  1326. *pstsState = SCRIPTTHREADSTATE_NOTINSCRIPT;
  1327. switch(stidThread) {
  1328. case SCRIPTTHREADID_BASE:
  1329. stidThread = m_basethread;
  1330. break;
  1331. case SCRIPTTHREADID_CURRENT:
  1332. stidThread = m_enginethread;
  1333. break;
  1334. };
  1335. if (stidThread == m_basethread) {
  1336. *pstsState = SCRIPTTHREADSTATE_NOTINSCRIPT;
  1337. } else if (stidThread == m_enginethread) {
  1338. *pstsState = SCRIPTTHREADSTATE_NOTINSCRIPT;
  1339. }
  1340. // tsrm_mutex_unlock(m_mutex);
  1341. return S_OK;
  1342. }
  1343. STDMETHODIMP TPHPScriptingEngine::InterruptScriptThread(
  1344. /* [in] */ SCRIPTTHREADID stidThread,
  1345. /* [in] */ const EXCEPINFO *pexcepinfo,
  1346. /* [in] */ DWORD dwFlags)
  1347. {
  1348. /* do not serialize this method, or call into the script site */
  1349. trace("%08x: InterruptScriptThread()\n", this);
  1350. return S_OK;
  1351. }
  1352. /* Clone is essential when running under Windows Script Host.
  1353. * It creates an engine to parse the code. Once it is parsed,
  1354. * the host clones other engines from the original and runs those.
  1355. * It is intended to be a fast method of running the same script
  1356. * multiple times in multiple threads. */
  1357. STDMETHODIMP TPHPScriptingEngine::Clone(
  1358. /* [out] */ IActiveScript **ppscript)
  1359. {
  1360. TPHPScriptingEngine *cloned = new TPHPScriptingEngine;
  1361. TSRMLS_FETCH();
  1362. trace("%08x: Clone()\n", this);
  1363. if (ppscript)
  1364. *ppscript = NULL;
  1365. if (cloned) {
  1366. cloned->InitNew();
  1367. SEND_THREAD_MESSAGE(cloned, PHPSE_CLONE, 0, (LPARAM)this TSRMLS_CC);
  1368. trace("%08x: Cloned OK, returning cloned object ptr %08x\n", this, cloned);
  1369. *ppscript = (IActiveScript*)cloned;
  1370. return S_OK;
  1371. }
  1372. return E_FAIL;
  1373. }
  1374. STDMETHODIMP TPHPScriptingEngine::InitNew( void)
  1375. {
  1376. TSRMLS_FETCH();
  1377. SEND_THREAD_MESSAGE(this, PHPSE_INIT_NEW, 0, 0 TSRMLS_CC);
  1378. return S_OK;
  1379. }
  1380. STDMETHODIMP TPHPScriptingEngine::AddScriptlet(
  1381. /* [in] */ LPCOLESTR pstrDefaultName,
  1382. /* [in] */ LPCOLESTR pstrCode,
  1383. /* [in] */ LPCOLESTR pstrItemName,
  1384. /* [in] */ LPCOLESTR pstrSubItemName,
  1385. /* [in] */ LPCOLESTR pstrEventName,
  1386. /* [in] */ LPCOLESTR pstrDelimiter,
  1387. /* [in] */ DWORD dwSourceContextCookie,
  1388. /* [in] */ ULONG ulStartingLineNumber,
  1389. /* [in] */ DWORD dwFlags,
  1390. /* [out] */ BSTR *pbstrName,
  1391. /* [out] */ EXCEPINFO *pexcepinfo)
  1392. {
  1393. struct php_active_script_add_scriptlet_info info;
  1394. TSRMLS_FETCH();
  1395. info.pstrDefaultName = pstrDefaultName;
  1396. info.pstrCode = pstrCode;
  1397. info.pstrItemName = pstrItemName;
  1398. info.pstrSubItemName = pstrSubItemName;
  1399. info.pstrEventName = pstrEventName;
  1400. info.pstrDelimiter = pstrDelimiter;
  1401. info.dwSourceContextCookie = dwSourceContextCookie;
  1402. info.ulStartingLineNumber = ulStartingLineNumber;
  1403. info.dwFlags = dwFlags;
  1404. info.pbstrName = pbstrName;
  1405. info.pexcepinfo = pexcepinfo;
  1406. return SEND_THREAD_MESSAGE(this, PHPSE_ADD_SCRIPTLET, 0, (LPARAM)&info TSRMLS_CC);
  1407. }
  1408. STDMETHODIMP TPHPScriptingEngine::ParseScriptText(
  1409. /* [in] */ LPCOLESTR pstrCode,
  1410. /* [in] */ LPCOLESTR pstrItemName,
  1411. /* [in] */ IUnknown *punkContext,
  1412. /* [in] */ LPCOLESTR pstrDelimiter,
  1413. /* [in] */ DWORD dwSourceContextCookie,
  1414. /* [in] */ ULONG ulStartingLineNumber,
  1415. /* [in] */ DWORD dwFlags,
  1416. /* [out] */ VARIANT *pvarResult,
  1417. /* [out] */ EXCEPINFO *pexcepinfo)
  1418. {
  1419. struct php_active_script_parse_info info;
  1420. TSRMLS_FETCH();
  1421. info.pstrCode = pstrCode;
  1422. info.pstrItemName = pstrItemName;
  1423. info.punkContext = punkContext;
  1424. info.pstrDelimiter = pstrDelimiter;
  1425. info.dwSourceContextCookie = dwSourceContextCookie;
  1426. info.ulStartingLineNumber = ulStartingLineNumber;
  1427. info.dwFlags = dwFlags;
  1428. info.pvarResult = pvarResult;
  1429. info.pexcepinfo = pexcepinfo;
  1430. return SEND_THREAD_MESSAGE(this, PHPSE_PARSE_SCRIPT, 0, (LPARAM)&info TSRMLS_CC);
  1431. }
  1432. STDMETHODIMP TPHPScriptingEngine::ParseProcedureText(
  1433. /* [in] */ LPCOLESTR pstrCode,
  1434. /* [in] */ LPCOLESTR pstrFormalParams,
  1435. /* [in] */ LPCOLESTR pstrProcedureName,
  1436. /* [in] */ LPCOLESTR pstrItemName,
  1437. /* [in] */ IUnknown *punkContext,
  1438. /* [in] */ LPCOLESTR pstrDelimiter,
  1439. /* [in] */ DWORD dwSourceContextCookie,
  1440. /* [in] */ ULONG ulStartingLineNumber,
  1441. /* [in] */ DWORD dwFlags,
  1442. /* [out] */ IDispatch **ppdisp)
  1443. {
  1444. struct php_active_script_parse_proc_info info;
  1445. HRESULT ret;
  1446. TSRMLS_FETCH();
  1447. info.pstrCode = pstrCode;
  1448. info.pstrFormalParams = pstrFormalParams;
  1449. info.pstrProcedureName = pstrProcedureName;
  1450. info.pstrItemName = pstrItemName;
  1451. info.punkContext = punkContext;
  1452. info.pstrDelimiter = pstrDelimiter;
  1453. info.dwSourceContextCookie = dwSourceContextCookie;
  1454. info.ulStartingLineNumber = ulStartingLineNumber;
  1455. info.dwFlags = dwFlags;
  1456. ret = SEND_THREAD_MESSAGE(this, PHPSE_PARSE_PROC, 0, (LPARAM)&info TSRMLS_CC);
  1457. if (ret == S_OK)
  1458. ret = GIT_get(info.dispcookie, IID_IDispatch, (void**)ppdisp);
  1459. trace("ParseProc: ret=%08x disp=%08x\n", ret, *ppdisp);
  1460. return ret;
  1461. }
  1462. extern "C"
  1463. void activescript_error_func(int type, const char *error_msg, ...)
  1464. {
  1465. TSRMLS_FETCH();
  1466. TPHPScriptingEngine *engine = (TPHPScriptingEngine*)SG(server_context);
  1467. }
  1468. class TActiveScriptError:
  1469. public IActiveScriptError
  1470. {
  1471. protected:
  1472. volatile LONG m_refcount;
  1473. public:
  1474. /* IUnknown */
  1475. STDMETHODIMP QueryInterface(REFIID iid, void **ppvObject) {
  1476. *ppvObject = NULL;
  1477. if (IsEqualGUID(IID_IActiveScriptError, iid)) {
  1478. *ppvObject = (IActiveScriptError*)this;
  1479. } else if (IsEqualGUID(IID_IUnknown, iid)) {
  1480. *ppvObject = this;
  1481. }
  1482. if (*ppvObject) {
  1483. AddRef();
  1484. return S_OK;
  1485. }
  1486. return E_NOINTERFACE;
  1487. }
  1488. STDMETHODIMP_(DWORD) AddRef(void) {
  1489. return InterlockedIncrement(const_cast<long*> (&m_refcount));
  1490. }
  1491. STDMETHODIMP_(DWORD) Release(void) {
  1492. DWORD ret = InterlockedDecrement(const_cast<long*> (&m_refcount));
  1493. trace("Release: errobj refcount=%d\n", ret);
  1494. if (ret == 0)
  1495. delete this;
  1496. return ret;
  1497. }
  1498. HRESULT STDMETHODCALLTYPE GetExceptionInfo(
  1499. /* [out] */ EXCEPINFO *pexcepinfo)
  1500. {
  1501. memset(pexcepinfo, 0, sizeof(EXCEPINFO));
  1502. pexcepinfo->bstrDescription = SysAllocString(m_message);
  1503. pexcepinfo->bstrSource = SysAllocString(m_filename);
  1504. pexcepinfo->wCode = 1000;
  1505. return S_OK;
  1506. }
  1507. HRESULT STDMETHODCALLTYPE GetSourcePosition(
  1508. /* [out] */ DWORD *pdwSourceContext,
  1509. /* [out] */ ULONG *pulLineNumber,
  1510. /* [out] */ LONG *plCharacterPosition)
  1511. {
  1512. *pdwSourceContext = 0;
  1513. *pulLineNumber = m_lineno;
  1514. *plCharacterPosition = 0;
  1515. return S_OK;
  1516. }
  1517. HRESULT STDMETHODCALLTYPE GetSourceLineText(
  1518. /* [out] */ BSTR *pbstrSourceLine)
  1519. {
  1520. *pbstrSourceLine = NULL;
  1521. return E_FAIL;
  1522. }
  1523. BSTR m_filename, m_message;
  1524. UINT m_lineno;
  1525. TActiveScriptError(const char *filename, const uint lineno, const char *message)
  1526. {
  1527. m_refcount = 0; /* start with zero refcount because this object is passed
  1528. * directly to the script site; it will call addref */
  1529. m_filename = TWideString::bstr_from_ansi((char*)filename);
  1530. m_message = TWideString::bstr_from_ansi((char*)message);
  1531. m_lineno = lineno;
  1532. }
  1533. ~TActiveScriptError()
  1534. {
  1535. trace("%08x: cleaning up error object\n", this);
  1536. SysFreeString(m_filename);
  1537. SysFreeString(m_message);
  1538. }
  1539. };
  1540. extern "C"
  1541. void activescript_error_handler(int type, const char *error_filename,
  1542. const uint error_lineno, const char *format, va_list args)
  1543. {
  1544. TSRMLS_FETCH();
  1545. char *buf;
  1546. int buflen;
  1547. TPHPScriptingEngine *engine = (TPHPScriptingEngine*)SG(server_context);
  1548. buflen = vspprintf(&buf, PG(log_errors_max_len), format, args);
  1549. trace("%08x: Error: %s\n", engine, buf);
  1550. /* if it's a fatal error, report it using IActiveScriptError. */
  1551. switch(type) {
  1552. case E_ERROR:
  1553. case E_CORE_ERROR:
  1554. case E_COMPILE_ERROR:
  1555. case E_USER_ERROR:
  1556. case E_PARSE:
  1557. /* trigger an error in the host */
  1558. TActiveScriptError *eobj = new TActiveScriptError(error_filename, error_lineno, buf);
  1559. trace("raising error object!\n");
  1560. if (engine->m_pass_eng)
  1561. engine->m_pass_eng->OnScriptError(eobj);
  1562. /* now throw the exception to abort execution */
  1563. if (engine->m_err_trap)
  1564. longjmp(*engine->m_err_trap, 1);
  1565. break;
  1566. }
  1567. efree(buf);
  1568. }