diff options
author | Wez Furlong <wez@php.net> | 2002-05-20 01:35:29 +0000 |
---|---|---|
committer | Wez Furlong <wez@php.net> | 2002-05-20 01:35:29 +0000 |
commit | e754361405395908abd687b1403215da2bd98b92 (patch) | |
tree | e695f256b31e3a721667dba65199d34869455948 /sapi/activescript/scriptengine.cpp | |
parent | 50f7427e69741acf9094a0f63c35503bfa4afca6 (diff) | |
download | php-git-e754361405395908abd687b1403215da2bd98b92.tar.gz |
Implement ActiveScript interfaces.
This allows use of PHP in:
Client-side script in Internet Explorer
Windows Scripting Host
ASP and ASP.NET pages
It's mostly working... give it a go.
You will need to regsvr32 the php4activescript.dll manually.
Diffstat (limited to 'sapi/activescript/scriptengine.cpp')
-rw-r--r-- | sapi/activescript/scriptengine.cpp | 1813 |
1 files changed, 1813 insertions, 0 deletions
diff --git a/sapi/activescript/scriptengine.cpp b/sapi/activescript/scriptengine.cpp new file mode 100644 index 0000000000..b0dc40dcb5 --- /dev/null +++ b/sapi/activescript/scriptengine.cpp @@ -0,0 +1,1813 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 4 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2002 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.02 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available at through the world-wide-web at | + | http://www.php.net/license/2_02.txt. | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Wez Furlong <wez@thebrainroom.com> | + +----------------------------------------------------------------------+ + */ +/* $Id$ */ + +/* Implementation Notes: + * + * PHP stores scripting engine state in thread-local storage. That means + * that we need to create a dedicated thread per-engine so that a host can + * use more than one engine object per thread. + * + * There are some interesting synchronization issues: Anything to do with + * running script in the PHP/Zend engine must take place on the engine + * thread. Likewise, calling back to the host must take place on the base + * thread - the thread that set the script site. + * + * For talking to the site from engine thread, we use an invisible window: + * the window processing is guaranteed to occur in the correct thread, + * and the message queue provides a useful synchronization device. + * + * For talking to the engine from any other thread, the engine thread waits + * for messages to arrive at it's message queue. Since the only API for + * dealing with thread messages is asynchronous, we use a mutex to ensure + * that only one thread can talk to the engine at a time, and an event + * object to signal to it that the processing is complete. + * + * */ + +#define _WIN32_DCOM + +#include "php.h" +extern "C" { +#include "php_main.h" +#include "SAPI.h" +#include "zend.h" +#include "zend_execute.h" +#include "zend_compile.h" +#include "php_globals.h" +#include "php_variables.h" +#include "php_ini.h" +#include "php4activescript.h" +#include "ext/com/com.h" +#include "ext/com/php_COM.h" +#include "ext/com/conversion.h" +} +#include "php4as_scriptengine.h" +#include "php4as_classfactory.h" +#include <objbase.h> + +enum fragtype { + FRAG_MAIN, + FRAG_SCRIPTLET, + FRAG_PROCEDURE +}; + +typedef struct { + enum fragtype fragtype; + zend_op_array *opcodes; + char *code; + int persistent; /* should be retained for Clone */ + int executed; /* for "main" */ + char *functionname; + unsigned int codelen; + unsigned int starting_line; + TPHPScriptingEngine *engine; + void *ptr; +} code_frag; + +#define FRAG_CREATE_FUNC (char*)-1 +static code_frag *compile_code_fragment( + enum fragtype fragtype, + char *functionname, + LPCOLESTR code, + ULONG starting_line, + EXCEPINFO *excepinfo, + TPHPScriptingEngine *engine + TSRMLS_DC); + +static int execute_code_fragment(code_frag *frag, + VARIANT *varResult, + EXCEPINFO *excepinfo + TSRMLS_DC); +static void free_code_fragment(code_frag *frag); +static code_frag *clone_code_fragment(code_frag *frag, TPHPScriptingEngine *engine TSRMLS_DC); + +/* Magic for handling threading correctly */ +static inline HRESULT SEND_THREAD_MESSAGE(TPHPScriptingEngine *engine, LONG msg, WPARAM wparam, LPARAM lparam TSRMLS_DC) +{ + if (engine->m_enginethread == 0) + return E_UNEXPECTED; + if (tsrm_thread_id() == (engine)->m_enginethread) + return (engine)->engine_thread_handler((msg), (wparam), (lparam), NULL TSRMLS_CC); + return (engine)->SendThreadMessage((msg), (wparam), (lparam)); +} + + + +/* {{{ scriptstate_to_string */ +static const char *scriptstate_to_string(SCRIPTSTATE ss) +{ + switch(ss) { + case SCRIPTSTATE_UNINITIALIZED: return "SCRIPTSTATE_UNINITIALIZED"; + case SCRIPTSTATE_INITIALIZED: return "SCRIPTSTATE_INITIALIZED"; + case SCRIPTSTATE_STARTED: return "SCRIPTSTATE_STARTED"; + case SCRIPTSTATE_CONNECTED: return "SCRIPTSTATE_CONNECTED"; + case SCRIPTSTATE_DISCONNECTED: return "SCRIPTSTATE_DISCONNECTED"; + case SCRIPTSTATE_CLOSED: return "SCRIPTSTATE_CLOSED"; + default: + return "unknown"; + } +} +/* }}} */ + +/* {{{ trace */ +static inline void trace(char *fmt, ...) +{ + va_list ap; + char buf[4096]; + + sprintf(buf, "T=%08x ", tsrm_thread_id()); + OutputDebugString(buf); + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + + OutputDebugString(buf); + + va_end(ap); +} +/* }}} */ + +/* {{{ TWideString */ +/* This class helps manipulate strings from OLE. + * It does not use emalloc, so it is better suited for passing pointers + * between threads. */ +class TWideString { + public: + LPOLESTR m_ole; + char *m_ansi; + int m_ansi_strlen; + + TWideString(LPOLESTR olestr) { + m_ole = olestr; + m_ansi = NULL; + } + TWideString(LPCOLESTR olestr) { + m_ole = (LPOLESTR)olestr; + m_ansi = NULL; + } + + ~TWideString() { + if (m_ansi) { + CoTaskMemFree(m_ansi); + } + m_ansi = NULL; + } + + char *safe_ansi_string() { + char *ret = ansi_string(); + if (ret == NULL) + return "<NULL>"; + return ret; + } + + int ansi_len(void) { return m_ansi_strlen; } + + static BSTR bstr_from_ansi(char *ansi) { + OLECHAR *ole = NULL; + BSTR bstr = NULL; + + int req = MultiByteToWideChar(CP_ACP, 0, ansi, -1, NULL, 0); + if (req) { + ole = (OLECHAR*)CoTaskMemAlloc((req + 1) * sizeof(OLECHAR)); + if (ole) { + req = MultiByteToWideChar(CP_ACP, 0, ansi, -1, ole, req); + req--; + ole[req] = 0; + + bstr = SysAllocString(ole); + CoTaskMemFree(ole); + } + } + return bstr; + } + + char *ansi_string(void) + { + if (m_ansi) + return m_ansi; + + if (m_ole == NULL) + return NULL; + + int bufrequired = WideCharToMultiByte(CP_ACP, 0, m_ole, -1, NULL, 0, NULL, NULL); + if (bufrequired) { + + m_ansi = (char*)CoTaskMemAlloc(bufrequired + 1); + if (m_ansi) { + m_ansi_strlen = WideCharToMultiByte(CP_ACP, 0, m_ole, -1, m_ansi, bufrequired + 1, NULL, NULL); + + if (m_ansi_strlen) { + m_ansi_strlen--; + m_ansi[m_ansi_strlen] = 0; + + } else { + trace("conversion failed with return code %08x\n", GetLastError()); + } + } + } + return m_ansi; + } +}; +/* }}} */ + +/* {{{ A generic stupid IDispatch implementation */ +class IDispatchImpl: + public IDispatch +{ +protected: + volatile LONG m_refcount; +public: + /* IUnknown */ + STDMETHODIMP QueryInterface(REFIID iid, void **ppvObject) { + *ppvObject = NULL; + + if (IsEqualGUID(IID_IDispatch, iid)) { + *ppvObject = (IDispatch*)this; + } else if (IsEqualGUID(IID_IUnknown, iid)) { + *ppvObject = this; + } + if (*ppvObject) { + AddRef(); + return S_OK; + } + return E_NOINTERFACE; + } + + STDMETHODIMP_(DWORD) AddRef(void) { + return InterlockedIncrement(&m_refcount); + } + + STDMETHODIMP_(DWORD) Release(void) { + DWORD ret = InterlockedDecrement(&m_refcount); + trace("%08x: IDispatchImpl: release ref count is now %d\n", this, ret); + if (ret == 0) + delete this; + return ret; + } + /* IDispatch */ + STDMETHODIMP GetTypeInfoCount(unsigned int * pctinfo) { + *pctinfo = 0; + trace("%08x: IDispatchImpl: GetTypeInfoCount\n", this); + return S_OK; + } + STDMETHODIMP GetTypeInfo( unsigned int iTInfo, LCID lcid, ITypeInfo **ppTInfo) { + trace("%08x: IDispatchImpl: GetTypeInfo\n", this); + return DISP_E_BADINDEX; + } + STDMETHODIMP GetIDsOfNames( REFIID riid, OLECHAR **rgszNames, unsigned int cNames, LCID lcid, DISPID *rgDispId) + { + unsigned int i; + trace("%08x: IDispatchImpl: GetIDsOfNames: \n", this); + for (i = 0; i < cNames; i++) { + TWideString name(rgszNames[i]); + trace(" %s\n", name.ansi_string()); + } + trace("----\n"); + return DISP_E_UNKNOWNNAME; + } + STDMETHODIMP Invoke( DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, + DISPPARAMS FAR* pDispParams, VARIANT FAR* pVarResult, EXCEPINFO FAR* pExcepInfo, + unsigned int FAR* puArgErr) + { + trace("%08x: IDispatchImpl: Invoke dispid %08x\n", this, dispIdMember); + return S_OK; + } + + + IDispatchImpl() { + m_refcount = 1; + } + + virtual ~IDispatchImpl() { + } +}; +/* }}} */ + +/* {{{ This object represents the PHP engine to the scripting host. + * Although the docs say it's implementation is optional, I found that + * the Windows Script host would crash if we did not provide it. */ +class ScriptDispatch: + public IDispatchImpl +{ +public: + ScriptDispatch() { + m_refcount = 1; + } +}; +/* }}} */ + +/* {{{ This object is used in conjunction with IActiveScriptParseProcedure to + * allow scriptlets to be bound to events. IE uses this for declaring + * event handlers such as onclick="...". + * The compiled code is stored in this object; IE will call + * IDispatch::Invoke when the element is clicked. + * */ +class ScriptProcedureDispatch: + public IDispatchImpl +{ +public: + code_frag *m_frag; + DWORD m_procflags; + TPHPScriptingEngine *m_engine; + + STDMETHODIMP Invoke( DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, + DISPPARAMS FAR* pDispParams, VARIANT FAR* pVarResult, EXCEPINFO FAR* pExcepInfo, + unsigned int FAR* puArgErr) + { + TSRMLS_FETCH(); + + if (m_frag) { + trace("%08x: Procedure Dispatch: Invoke dispid %08x\n", this, dispIdMember); + SEND_THREAD_MESSAGE(m_engine, PHPSE_EXEC_PROC, 0, (LPARAM)this TSRMLS_CC); + } + return S_OK; + } + ScriptProcedureDispatch() { + m_refcount = 1; + } +}; +/* }}} */ + +/* {{{ code fragment management */ +static code_frag *compile_code_fragment( + enum fragtype fragtype, + char *functionname, + LPCOLESTR code, + ULONG starting_line, + EXCEPINFO *excepinfo, + TPHPScriptingEngine *engine + TSRMLS_DC) +{ + zval pv; + int code_offs = 0; + char namebuf[256]; + + code_frag *frag = (code_frag*)CoTaskMemAlloc(sizeof(code_frag)); + memset(frag, 0, sizeof(code_frag)); + + frag->engine = engine; + + /* handle the function name */ + if (functionname) { + int namelen; + if (functionname == FRAG_CREATE_FUNC) { + ULONG n = ++engine->m_lambda_count; + + sprintf(namebuf, "__frag_%08x_%u", engine, n); + functionname = namebuf; + } + + namelen = strlen(functionname); + code_offs = namelen + sizeof("function (){"); + + frag->functionname = (char*)CoTaskMemAlloc((namelen + 1) * sizeof(char)); + memcpy(frag->functionname, functionname, namelen+1); + } + + frag->functionname = functionname; + +trace("%08x: COMPILED FRAG\n", frag); + + frag->codelen = WideCharToMultiByte(CP_ACP, 0, code, -1, NULL, 0, NULL, NULL); + frag->code = (char*)CoTaskMemAlloc(sizeof(char) * (frag->codelen + code_offs + 1)); + + if (functionname) { + sprintf(frag->code, "function %s(){ ", functionname); + } + + frag->codelen = WideCharToMultiByte(CP_ACP, 0, code, -1, frag->code + code_offs, frag->codelen, NULL, NULL) - 1; + + if (functionname) { + frag->codelen += code_offs + 1; + frag->code[frag->codelen-1] = '}'; + frag->code[frag->codelen] = 0; + } + +trace("code to compile is:\ncode_offs=%d func=%s\n%s\n", code_offs, functionname, frag->code); + + frag->fragtype = fragtype; + frag->starting_line = starting_line; + + pv.type = IS_STRING; + pv.value.str.val = frag->code; + pv.value.str.len = frag->codelen; + + frag->opcodes = compile_string(&pv, "fragment" TSRMLS_CC); + + if (frag->opcodes == NULL) { + free_code_fragment(frag); + + if (excepinfo) { + memset(excepinfo, 0, sizeof(EXCEPINFO)); + excepinfo->wCode = 1000; + excepinfo->bstrSource = TWideString::bstr_from_ansi("fragment"); + excepinfo->bstrDescription = TWideString::bstr_from_ansi("Problem while parsing/compiling"); + } + + return NULL; + } + + return frag; +} + +static void free_code_fragment(code_frag *frag) +{ + switch(frag->fragtype) { + case FRAG_PROCEDURE: + if (frag->ptr) { + ScriptProcedureDispatch *disp = (ScriptProcedureDispatch*)frag->ptr; + disp->Release(); + CoDisconnectObject((IUnknown*)disp, 0); + frag->ptr = NULL; + } + break; + } + + if (frag->opcodes) + destroy_op_array(frag->opcodes); + if (frag->functionname) + CoTaskMemFree(frag->functionname); + CoTaskMemFree(frag->code); + CoTaskMemFree(frag); +} + +static code_frag *clone_code_fragment(code_frag *frag, TPHPScriptingEngine *engine TSRMLS_DC) +{ + zval pv; + code_frag *newfrag = (code_frag*)CoTaskMemAlloc(sizeof(code_frag)); + memset(newfrag, 0, sizeof(code_frag)); + + newfrag->engine = engine; +trace("%08x: CLONED FRAG\n", newfrag); + + newfrag->persistent = frag->persistent; + newfrag->codelen = frag->codelen; + newfrag->code = (char*)CoTaskMemAlloc(sizeof(char) * frag->codelen + 1); + memcpy(newfrag->code, frag->code, frag->codelen + 1); + + if (frag->functionname) { + int namelen = strlen(frag->functionname); + newfrag->functionname = (char*)CoTaskMemAlloc(sizeof(char) * (namelen + 1)); + memcpy(newfrag->functionname, frag->functionname, namelen+1); + } else { + newfrag->functionname = NULL; + } + + newfrag->fragtype = frag->fragtype; + newfrag->starting_line = frag->starting_line; + + pv.type = IS_STRING; + pv.value.str.val = newfrag->code; + pv.value.str.len = newfrag->codelen; + + newfrag->opcodes = compile_string(&pv, "fragment" TSRMLS_CC); + + if (newfrag->opcodes == NULL) { + free_code_fragment(newfrag); +/* + if (excepinfo) { + memset(excepinfo, 0, sizeof(EXCEPINFO)); + excepinfo->wCode = 1000; + excepinfo->bstrSource = TWideString::bstr_from_ansi("fragment"); + excepinfo->bstrDescription = TWideString::bstr_from_ansi("Problem while parsing/compiling"); + } +*/ + return NULL; + } + + return newfrag; + +} + +static int execute_code_fragment(code_frag *frag, + VARIANT *varResult, + EXCEPINFO *excepinfo + TSRMLS_DC) +{ + zval *retval_ptr = NULL; + jmp_buf *orig_jmpbuf; + jmp_buf err_trap; + + if (frag->fragtype == FRAG_MAIN && frag->executed) + return 1; + + orig_jmpbuf = frag->engine->m_err_trap; + frag->engine->m_err_trap = &err_trap; + + if (setjmp(err_trap) == 0) { + trace("*** Executing code in thread %08x\n", tsrm_thread_id()); + + if (frag->functionname) { + + zval fname; + + fname.type = IS_STRING; + fname.value.str.val = frag->functionname; + fname.value.str.len = strlen(frag->functionname); + + call_user_function_ex(CG(function_table), NULL, &fname, &retval_ptr, 0, NULL, 1, NULL TSRMLS_CC); + + } else { + zend_op_array *active_op_array = EG(active_op_array); + zend_function_state *function_state_ptr = EG(function_state_ptr); + zval **return_value_ptr_ptr = EG(return_value_ptr_ptr); + zend_op **opline_ptr = EG(opline_ptr); + + EG(return_value_ptr_ptr) = &retval_ptr; + EG(active_op_array) = frag->opcodes; + EG(no_extensions) = 1; + + zend_execute(frag->opcodes TSRMLS_CC); + + EG(no_extensions) = 0; + EG(opline_ptr) = opline_ptr; + EG(active_op_array) = active_op_array; + EG(function_state_ptr) = function_state_ptr; + EG(return_value_ptr_ptr) = return_value_ptr_ptr; + } + } else { + trace("*** --> caught error while executing\n"); + if (frag->engine->m_in_main) + frag->engine->m_stop_main = 1; + } + + frag->engine->m_err_trap = orig_jmpbuf; + + if (frag->fragtype == FRAG_MAIN) + frag->executed = 1; + + if (varResult) + VariantInit(varResult); + + if (retval_ptr) { + if (varResult) + php_pval_to_variant(retval_ptr, varResult, CP_ACP TSRMLS_CC); + zval_ptr_dtor(&retval_ptr); + } + + return 1; +} + +static void frag_dtor(void *pDest) +{ + code_frag *frag = *(code_frag**)pDest; + free_code_fragment(frag); +} +/* }}} */ + +/* glue for getting back into the OO land */ +static DWORD WINAPI begin_engine_thread(LPVOID param) +{ + TPHPScriptingEngine *engine = (TPHPScriptingEngine*)param; + engine->engine_thread_func(); + trace("engine thread has really gone away!\n"); + return 0; +} + +TPHPScriptingEngine::TPHPScriptingEngine() +{ + m_scriptstate = SCRIPTSTATE_UNINITIALIZED; + m_pass = NULL; + m_in_main = 0; + m_stop_main = 0; + m_err_trap = NULL; + m_lambda_count = 0; + m_pass_eng = NULL; + m_refcount = 1; + m_basethread = tsrm_thread_id(); + m_mutex = tsrm_mutex_alloc(); + m_sync_thread_msg = CreateEvent(NULL, TRUE, FALSE, NULL); + TPHPClassFactory::AddToObjectCount(); + + m_engine_thread_handle = CreateThread(NULL, 0, begin_engine_thread, this, 0, &m_enginethread); + CloseHandle(m_engine_thread_handle); +} + +/* Synchronize with the engine thread */ +HRESULT TPHPScriptingEngine::SendThreadMessage(LONG msg, WPARAM wparam, LPARAM lparam) +{ + HRESULT ret; + + if (m_enginethread == 0) + return E_UNEXPECTED; + + trace("I'm waiting for a mutex in SendThreadMessage\n this=%08x ethread=%08x msg=%08x\n", + this, m_enginethread, msg); + + tsrm_mutex_lock(m_mutex); + ResetEvent(m_sync_thread_msg); + + /* If we call PostThreadMessage before the thread has created the queue, the message + * posting fails. MSDN docs recommend the following course of action */ + while (!PostThreadMessage(m_enginethread, msg, wparam, lparam)) { + Sleep(50); + if (m_enginethread == 0) { + tsrm_mutex_unlock(m_mutex); + trace("breaking out of dodgy busy wait\n"); + return E_UNEXPECTED; + } + } + + /* Wait for the event object to be signalled. + * This is a nice "blocking without blocking" wait; window messages are dispatched + * and everything works out quite nicely */ + while(1) { + DWORD result = MsgWaitForMultipleObjects(1, &m_sync_thread_msg, FALSE, 4000, QS_ALLINPUT); + + if (result == WAIT_OBJECT_0 + 1) { + /* Dispatch some messages */ + MSG msg; + while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + //trace("dispatching message while waiting\n"); + DispatchMessage(&msg); + } + } else if (result == WAIT_TIMEOUT) { + trace("timeout while waiting for thread reply\n"); + + } else { + /* the event was signalled */ + break; + } + } + ret = m_sync_thread_ret; + ResetEvent(m_sync_thread_msg); + tsrm_mutex_unlock(m_mutex); + return ret; +} + +TPHPScriptingEngine::~TPHPScriptingEngine() +{ + trace("\n\n *** Engine Destructor Called\n\n"); + if (m_scriptstate != SCRIPTSTATE_UNINITIALIZED && m_scriptstate != SCRIPTSTATE_CLOSED && m_enginethread) + Close(); + + PostThreadMessage(m_enginethread, WM_QUIT, 0, 0); + + TPHPClassFactory::RemoveFromObjectCount(); + tsrm_mutex_free(m_mutex); +} + +/* Set some executor globals and execute a zend_op_array. + * The declaration looks wierd because this can be invoked from + * zend_hash_apply_with_argument */ +static int execute_main(void *pDest, void *arg TSRMLS_DC) +{ + code_frag *frag = *(code_frag**)pDest; + + if (frag->fragtype == FRAG_MAIN && !(frag->engine->m_in_main && frag->engine->m_stop_main)) + execute_code_fragment(frag, NULL, NULL TSRMLS_CC); + + return ZEND_HASH_APPLY_KEEP; +} + +static int clone_frags(void *pDest, void *arg TSRMLS_DC) +{ + code_frag *frag, *src = *(code_frag**)pDest; + TPHPScriptingEngine *engine = (TPHPScriptingEngine*)arg; + + if (src->persistent) { + frag = clone_code_fragment(src, engine TSRMLS_CC); + if (frag) + zend_hash_next_index_insert(&engine->m_frags, &frag, sizeof(code_frag*), NULL); + else + trace("WARNING: clone failed!\n"); + } + + return ZEND_HASH_APPLY_KEEP; +} + +HRESULT TPHPScriptingEngine::engine_thread_handler(LONG msg, WPARAM wparam, LPARAM lParam, int *handled TSRMLS_DC) +{ + HRESULT ret = S_OK; + + trace("engine_thread_handler: running in thread %08x, should be %08x msg=%08x this=%08x\n", + tsrm_thread_id(), m_enginethread, msg, this); + + if (handled) + *handled = 1; + + if (m_enginethread == 0) + return E_UNEXPECTED; + + switch(msg) { + case PHPSE_ADD_TYPELIB: + { + struct php_active_script_add_tlb_info *info = (struct php_active_script_add_tlb_info*)lParam; + ITypeLib *TypeLib; + + if (SUCCEEDED(LoadRegTypeLib(*info->rguidTypeLib, (USHORT)info->dwMajor, + (USHORT)info->dwMinor, LANG_NEUTRAL, &TypeLib))) { + php_COM_load_typelib(TypeLib, CONST_CS TSRMLS_CC); + TypeLib->Release(); + } + } + break; + case PHPSE_STATE_CHANGE: + { + /* handle the state change here */ + SCRIPTSTATE ss = (SCRIPTSTATE)lParam; + int start_running = 0; + trace("%08x: DoSetScriptState(current=%s, new=%s)\n", + this, + scriptstate_to_string(m_scriptstate), + scriptstate_to_string(ss)); + + if (m_scriptstate == SCRIPTSTATE_INITIALIZED && (ss == SCRIPTSTATE_STARTED || ss == SCRIPTSTATE_CONNECTED)) + start_running = 1; + + m_scriptstate = ss; + + /* inform host/site of the change */ + if (m_pass_eng) + m_pass_eng->OnStateChange(m_scriptstate); + + if (start_running) { + /* run "main()", as described in the docs */ + if (m_pass_eng) + m_pass_eng->OnEnterScript(); + trace("%08x: apply execute main to m_frags\n", this); + m_in_main = 1; + m_stop_main = 0; + zend_hash_apply_with_argument(&m_frags, execute_main, this TSRMLS_CC); + m_in_main = 0; + trace("%08x: --- done execute main\n", this); + if (m_pass_eng) + m_pass_eng->OnLeaveScript(); + + /* docs are a bit ambiguous here, but it appears that we should + * inform the host that the main script execution has completed, + * and also what the return value is */ + VARIANT varRes; + + VariantInit(&varRes); + if (m_pass_eng) + m_pass_eng->OnScriptTerminate(&varRes, NULL); + + /* + m_scriptstate = SCRIPTSTATE_INITIALIZED; + if (m_pass_eng) + m_pass_eng->OnStateChange(m_scriptstate); + */ + } + } + break; + case PHPSE_INIT_NEW: + { + /* Prepare PHP/ZE for use */ + + trace("%08x: m_frags : INIT NEW\n", this); + zend_hash_init(&m_frags, 0, NULL, frag_dtor, TRUE); + + SG(options) |= SAPI_OPTION_NO_CHDIR; + SG(server_context) = this; + /* override the default PHP error callback */ + zend_error_cb = activescript_error_handler; + + zend_alter_ini_entry("register_argc_argv", 19, "1", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); + zend_alter_ini_entry("html_errors", 12, "0", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); + zend_alter_ini_entry("implicit_flush", 15, "1", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); + zend_alter_ini_entry("max_execution_time", 19, "0", 1, PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE); + + php_request_startup(TSRMLS_C); + PG(during_request_startup) = 0; + + } + break; + case PHPSE_CLOSE: + { + /* Close things down */ + trace("%08x: m_frags : CLOSE/DESTROY\n", this); + m_scriptstate = SCRIPTSTATE_CLOSED; + if (m_pass_eng) { + m_pass_eng->OnStateChange(m_scriptstate); + trace("%08x: release site from this side\n", this); + m_pass_eng->Release(); + m_pass_eng = NULL; + } + zend_hash_destroy(&m_frags); + php_request_shutdown(NULL); + + break; + } + break; + case PHPSE_CLONE: + { + /* Clone the engine state. This is semantically equal to serializing all + * the parsed code from the source and unserializing it in the dest (this). + * IE doesn't appear to use it, but Windows Script Host does. I'd expect + * ASP/ASP.NET to do so also. + * + * FIXME: Probably won't work with IActiveScriptParseProcedure scriplets + * */ + + TPHPScriptingEngine *src = (TPHPScriptingEngine*)lParam; + + trace("%08x: m_frags : CLONE\n", this); + zend_hash_apply_with_argument(&src->m_frags, clone_frags, this TSRMLS_CC); + + } + break; + case PHPSE_ADD_SCRIPTLET: + { + /* Parse/compile a chunk of script that will act as an event handler. + * If the host supports IActiveScriptParseProcedure, this code will + * not be called. + * The docs are (typically) vague: AFAICT, once the code has been + * compiled, we are supposed to arrange for an IConnectionPoint + * advisory connection to the item/subitem, once the script + * moves into SCRIPTSTATE_CONNECTED. + * That's a lot of work! + * + * FIXME: this is currently almost useless + * */ + + struct php_active_script_add_scriptlet_info *info = (struct php_active_script_add_scriptlet_info*)lParam; + + TWideString + default_name(info->pstrDefaultName), + code(info->pstrCode), + item_name(info->pstrItemName), + sub_item_name(info->pstrSubItemName), + event_name(info->pstrEventName), + delimiter(info->pstrDelimiter); + + /* lets invent a function name for the scriptlet */ + char sname[256]; + + /* should check if the name is already used! */ + if (info->pstrDefaultName) + strcpy(sname, default_name.ansi_string()); + else { + sname[0] = 0; + strcat(sname, "__"); + if (info->pstrItemName) { + strcat(sname, item_name.ansi_string()); + strcat(sname, "_"); + } + if (info->pstrSubItemName) { + strcat(sname, sub_item_name.ansi_string()); + strcat(sname, "_"); + } + if (info->pstrEventName) + strcat(sname, event_name.ansi_string()); + } + + + 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", + this, scriptstate_to_string(m_scriptstate), + default_name.safe_ansi_string(), code.safe_ansi_string(), item_name.safe_ansi_string(), + sub_item_name.safe_ansi_string(), event_name.safe_ansi_string(), delimiter.safe_ansi_string(), + info->ulStartingLineNumber); + + + code_frag *frag = compile_code_fragment( + FRAG_SCRIPTLET, + sname, + info->pstrCode, + info->ulStartingLineNumber, + info->pexcepinfo, + this + TSRMLS_CC); + + if (frag) { + + frag->persistent = (info->dwFlags & SCRIPTTEXT_ISPERSISTENT); + + zend_hash_next_index_insert(&m_frags, &frag, sizeof(code_frag*), NULL); + + /* + ScriptProcedureDispatch *disp = new ScriptProcedureDispatch; + + disp->AddRef(); + disp->m_frag = frag; + disp->m_procflags = info->dwFlags; + disp->m_engine = this; + frag->ptr = disp; + + *info->ppdisp = disp; + */ + ret = S_OK; + } else { + ret = DISP_E_EXCEPTION; + } + + *info->pbstrName = TWideString::bstr_from_ansi(sname); + + trace("%08x: done with scriptlet %s\n", this, sname); + + } + break; + case PHPSE_GET_DISPATCH: + { + struct php_active_script_get_dispatch_info *info = (struct php_active_script_get_dispatch_info *)lParam; + IDispatch *disp = NULL; + char *itemname; + unsigned int itemlen; + + if (info->pstrItemName != NULL) { + zval **tmp; + + itemname = php_OLECHAR_to_char((OLECHAR*)info->pstrItemName, &itemlen, CP_ACP TSRMLS_CC); + + /* Get that item from the global namespace. + * If it is an object, export it as a dispatchable object. + * */ + + if (zend_hash_find(&EG(symbol_table), itemname, itemlen+1, (void**)&tmp) == SUCCESS) { + if (Z_TYPE_PP(tmp) == IS_OBJECT) { + disp = php_COM_export_object(*tmp TSRMLS_CC); + } + } + trace("%08x: GetScriptDispatch(%s --> %08x)\n", this, itemname, disp); + + efree(itemname); + + } else { +#if 0 + zval *obj; + + MAKE_STD_ZVAL(obj); + object_init(obj); + disp = php_COM_export_object(obj TSRMLS_CC); +#else + + disp = (IDispatch*) new ScriptDispatch; +#endif + trace("%08x: GetScriptDispatch(NULL --> %08x)\n", this, disp); + } + + if (disp) { + trace("--- Marshaling to stream\n"); + ret = CoMarshalInterThreadInterfaceInStream(IID_IDispatch, disp, &info->dispatch); + disp->Release(); + } else { + ret = S_FALSE; + } + } + break; + case PHPSE_ADD_NAMED_ITEM: + { + /* The Host uses this to add objects to the global namespace. + * Some objects are intended to have their child properties + * globally visible, so we add those to the global namespace too. + * */ + struct php_active_script_add_named_item_info *info = (struct php_active_script_add_named_item_info *)lParam; + + TWideString name(info->pstrName); + IDispatch *disp; + + if (SUCCEEDED(CoGetInterfaceAndReleaseStream(info->marshal, IID_IDispatch, (void**)&disp))) + add_to_global_namespace(disp, info->dwFlags, name.ansi_string() TSRMLS_CC); + + } + break; + case PHPSE_SET_SITE: + { + LPSTREAM stream = (LPSTREAM)lParam; + + if (m_pass_eng) { + m_pass_eng->Release(); + m_pass_eng = NULL; + } + + if (stream) + CoGetInterfaceAndReleaseStream(stream, IID_IActiveScriptSite, (void**)&m_pass_eng); + + trace("%08x: site (engine-side) is now %08x (base=%08x)\n", this, m_pass_eng, m_pass); + + } + break; + case PHPSE_EXEC_PROC: + { + ScriptProcedureDispatch *disp = (ScriptProcedureDispatch *)lParam; + execute_code_fragment(disp->m_frag, NULL, NULL TSRMLS_CC); + } + break; + case PHPSE_PARSE_PROC: + { + /* This is the IActiveScriptParseProcedure implementation. + * IE uses this to request for an IDispatch that it will invoke in + * response to some event, and tells us the code that it wants to + * run. + * We compile the code and pass it back a dispatch object. + * The object will then serialize access to the engine thread and + * execute the opcodes */ + struct php_active_script_parse_proc_info *info = (struct php_active_script_parse_proc_info*)lParam; + TWideString + code(info->pstrCode), + formal_params(info->pstrFormalParams), + procedure_name(info->pstrProcedureName), + item_name(info->pstrItemName), + delimiter(info->pstrDelimiter); + + trace("%08x: ParseProc:\n state=%s\ncode=%s\n params=%s\nproc=%s\nitem=%s\n delim=%s\n line=%d\n", + this, scriptstate_to_string(m_scriptstate), + code.safe_ansi_string(), formal_params.ansi_string(), procedure_name.ansi_string(), + item_name.safe_ansi_string(), delimiter.safe_ansi_string(), + info->ulStartingLineNumber); + + + code_frag *frag = compile_code_fragment( + FRAG_PROCEDURE, + NULL, + info->pstrCode, + info->ulStartingLineNumber, + NULL, + this + TSRMLS_CC); + + if (frag) { + + frag->persistent = (info->dwFlags & SCRIPTTEXT_ISPERSISTENT); + zend_hash_next_index_insert(&m_frags, &frag, sizeof(code_frag*), NULL); + + ScriptProcedureDispatch *disp = new ScriptProcedureDispatch; + + disp->AddRef(); + disp->m_frag = frag; + disp->m_procflags = info->dwFlags; + disp->m_engine = this; + frag->ptr = disp; + + *info->ppdisp = disp; + /* + ret = CoMarshalInterThreadInterfaceInStream(IID_IDispatch, disp, &info->disp); + disp->Release(); + */ + } else { + ret = DISP_E_EXCEPTION; + } + + } + break; + + case PHPSE_PARSE_SCRIPT: + { + struct php_active_script_parse_info *info = (struct php_active_script_parse_info*)lParam; + int doexec; + + TWideString + code(info->pstrCode), + item_name(info->pstrItemName), + delimiter(info->pstrDelimiter); + + trace("%08x: ParseScriptText:\n state=%s\ncode=%s\n item=%s\n delim=%s\n line=%d\n", + this, scriptstate_to_string(m_scriptstate), + code.safe_ansi_string(), item_name.safe_ansi_string(), delimiter.safe_ansi_string(), + info->ulStartingLineNumber); + + code_frag *frag = compile_code_fragment( + FRAG_MAIN, + info->dwFlags & SCRIPTTEXT_ISEXPRESSION ? FRAG_CREATE_FUNC : NULL, + info->pstrCode, + info->ulStartingLineNumber, + info->pexcepinfo, + this + TSRMLS_CC); + + doexec = (info->dwFlags & SCRIPTTEXT_ISEXPRESSION) || + m_scriptstate == SCRIPTSTATE_STARTED || + m_scriptstate == SCRIPTSTATE_CONNECTED || + m_scriptstate == SCRIPTSTATE_DISCONNECTED; + + if (frag) { + frag->persistent = (info->dwFlags & SCRIPTTEXT_ISPERSISTENT); + ret = S_OK; + + if (info->dwFlags & SCRIPTTEXT_ISEXPRESSION) { + if (m_scriptstate == SCRIPTSTATE_INITIALIZED) { + /* not allowed to execute code in this state */ + ret = E_UNEXPECTED; + doexec = 0; + } + } + + if (doexec) { + /* execute the code as an expression */ + if (!execute_code_fragment(frag, info->pvarResult, info->pexcepinfo TSRMLS_CC)) + ret = DISP_E_EXCEPTION; + } + + zend_hash_next_index_insert(&m_frags, &frag, sizeof(code_frag*), NULL); + } else { + ret = DISP_E_EXCEPTION; + } + + if (info->pvarResult) { + VariantInit(info->pvarResult); + } + + + } + break; + default: + trace("unhandled message type %08x\n", msg); + if (handled) + *handled = 0; + } + return ret; +} + +/* The PHP/Zend state actually lives in this thread */ +void TPHPScriptingEngine::engine_thread_func(void) +{ + TSRMLS_FETCH(); + int handled; + int terminated = 0; + MSG msg; + + trace("%08x: engine thread started up!\n", this); + + CoInitializeEx(0, COINIT_MULTITHREADED); + + m_tsrm_hack = tsrm_ls; + + while(!terminated) { + DWORD result = MsgWaitForMultipleObjects(0, NULL, FALSE, 4000, QS_ALLINPUT); + + switch(result) { + case WAIT_OBJECT_0: + while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + + if (msg.message == WM_QUIT) { + terminated = 1; + } else { + + handled = 1; + m_sync_thread_ret = engine_thread_handler(msg.message, msg.wParam, msg.lParam, &handled TSRMLS_CC); + if (handled) + SetEvent(m_sync_thread_msg); + } + + } + break; + case WAIT_TIMEOUT: + trace("thread wait timed out\n"); + break; + default: + trace("some strange value\n"); + } + } + +#if 0 + while(GetMessage(&msg, NULL, 0, 0)) { + + if (msg.message == WM_QUIT) + break; + + handled = 1; + m_sync_thread_ret = engine_thread_handler(msg.message, msg.wParam, msg.lParam, &handled TSRMLS_CC); + if (handled) + SetEvent(m_sync_thread_msg); + } + trace("%08x: engine thread exiting!!!!!\n", this); +#endif + m_enginethread = 0; + + CoUninitialize(); +} + +/* Only call this in the context of the engine thread, or you'll be sorry. + * + * When SCRIPTITEM_GLOBALMEMBERS is set, we're only adding COM objects to the namespace. + * We could add *all* properties, but I don't like this idea; what if the value changes + * while the page is running? We'd be left with stale data. + * */ +void TPHPScriptingEngine::add_to_global_namespace(IDispatch *disp, DWORD flags, char *name TSRMLS_DC) +{ + zval *val; + ITypeInfo *typ; + int i; + unsigned int namelen; + FUNCDESC *func; + BSTR funcname; + TYPEATTR *attr; + DISPPARAMS dispparams; + VARIANT vres; + ITypeInfo *rettyp; + TYPEATTR *retattr; + +trace("Add %s to global namespace\n", name); + + val = php_COM_object_from_dispatch(disp, NULL TSRMLS_CC); + + if (val == NULL) { + disp->Release(); + return; + } + + ZEND_SET_SYMBOL(&EG(symbol_table), name, val); + + if (flags & SCRIPTITEM_GLOBALMEMBERS == 0) { + disp->Release(); + return; + } + + /* Enumerate properties and add those too */ + if (FAILED(disp->GetTypeInfo(0, 0, &typ))) { + disp->Release(); + return; + } + + if (SUCCEEDED(typ->GetTypeAttr(&attr))) { + for (i = 0; i < attr->cFuncs; i++) { + if (FAILED(typ->GetFuncDesc(i, &func))) + continue; + + /* Look at it's type */ + if (func->invkind == INVOKE_PROPERTYGET + && VT_PTR == func->elemdescFunc.tdesc.vt + && VT_USERDEFINED == func->elemdescFunc.tdesc.lptdesc->vt + && SUCCEEDED(typ->GetRefTypeInfo(func->elemdescFunc.tdesc.lptdesc->hreftype, &rettyp))) + { + if (SUCCEEDED(rettyp->GetTypeAttr(&retattr))) { + if (retattr->typekind == TKIND_DISPATCH) { + /* It's dispatchable */ + + /* get the value */ + dispparams.cArgs = 0; + dispparams.cNamedArgs = 0; + VariantInit(&vres); + + if (SUCCEEDED(disp->Invoke(func->memid, IID_NULL, 0, func->invkind, + &dispparams, &vres, NULL, NULL))) { + + /* Get it's dispatch */ + IDispatch *sub = NULL; + + if (V_VT(&vres) == VT_UNKNOWN) + V_UNKNOWN(&vres)->QueryInterface(IID_IDispatch, (void**)&sub); + else if (V_VT(&vres) == VT_DISPATCH) + sub = V_DISPATCH(&vres); + + if (sub) { + /* find out it's name */ + typ->GetDocumentation(func->memid, &funcname, NULL, NULL, NULL); + name = php_OLECHAR_to_char(funcname, &namelen, CP_ACP TSRMLS_CC); + + /* add to namespace */ + zval *subval = php_COM_object_from_dispatch(sub, NULL TSRMLS_CC); + if (subval) { + ZEND_SET_SYMBOL(&EG(symbol_table), name, subval); + } + + efree(name); + SysFreeString(funcname); + } + VariantClear(&vres); + } + } + rettyp->ReleaseTypeAttr(retattr); + } + rettyp->Release(); + } + typ->ReleaseFuncDesc(func); + } + typ->ReleaseTypeAttr(attr); + } + disp->Release(); +} + +STDMETHODIMP_(DWORD) TPHPScriptingEngine::AddRef(void) +{ + return InterlockedIncrement(&m_refcount); +} + +STDMETHODIMP_(DWORD) TPHPScriptingEngine::Release(void) +{ + DWORD ret = InterlockedDecrement(&m_refcount); + if (ret == 0) { + trace("%08x: Release: zero refcount, destroy the engine!\n", this); + delete this; + } + return ret; +} + +STDMETHODIMP TPHPScriptingEngine::QueryInterface(REFIID iid, void **ppvObject) +{ + *ppvObject = NULL; + + if (IsEqualGUID(IID_IActiveScript, iid)) { + *ppvObject = (IActiveScript*)this; + } else if (IsEqualGUID(IID_IActiveScriptParse32, iid)) { + *ppvObject = (IActiveScriptParse32*)this; + } else if (IsEqualGUID(IID_IActiveScriptParseProcedure32, iid)) { + *ppvObject = (IActiveScriptParseProcedure*)this; + } else if (IsEqualGUID(IID_IUnknown, iid)) { + *ppvObject = this; + } else { + LPOLESTR guidw; + StringFromCLSID(iid, &guidw); + { + TWideString guid(guidw); + trace("%08x: QueryInterface for unsupported %s\n", this, guid.ansi_string()); + } + CoTaskMemFree(guidw); + } + if (*ppvObject) { + AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +/* This is called by the host to set the scrite site. + * It also defines the base thread. */ +STDMETHODIMP TPHPScriptingEngine::SetScriptSite(IActiveScriptSite *pass) +{ + TSRMLS_FETCH(); + + tsrm_mutex_lock(m_mutex); + + trace("%08x: -----> Base thread is %08x\n", this, tsrm_thread_id()); + + if (m_pass) { + m_pass->Release(); + m_pass = NULL; + SEND_THREAD_MESSAGE(this, PHPSE_SET_SITE, 0, 0 TSRMLS_CC); + } + + if (pass == NULL) { + trace("Closing down site; we should have no references to objects from the host\n" + " m_pass=%08x\n m_pass_eng=%08x\n What about named items??\n", + m_pass, m_pass_eng); + } + + m_pass = pass; + if (m_pass) { + m_pass->AddRef(); + + LPSTREAM stream; + if (SUCCEEDED(CoMarshalInterThreadInterfaceInStream(IID_IActiveScriptSite, m_pass, &stream))) + SEND_THREAD_MESSAGE(this, PHPSE_SET_SITE, 0, (LPARAM)stream TSRMLS_CC); + + if (m_scriptstate == SCRIPTSTATE_UNINITIALIZED) + SEND_THREAD_MESSAGE(this, PHPSE_STATE_CHANGE, 0, SCRIPTSTATE_INITIALIZED TSRMLS_CC); + } + + tsrm_mutex_unlock(m_mutex); + return S_OK; +} + +STDMETHODIMP TPHPScriptingEngine::GetScriptSite(REFIID riid, void **ppvObject) +{ + HRESULT ret = S_FALSE; + + trace("%08x: GetScriptSite()\n", this); + tsrm_mutex_lock(m_mutex); + + + if (m_pass) + ret = m_pass->QueryInterface(riid, ppvObject); + + tsrm_mutex_unlock(m_mutex); + return ret; +} + +STDMETHODIMP TPHPScriptingEngine::SetScriptState(SCRIPTSTATE ss) +{ + TSRMLS_FETCH(); + trace("%08x: SetScriptState(%s)\n", this, scriptstate_to_string(ss)); + return SEND_THREAD_MESSAGE(this, PHPSE_STATE_CHANGE, 0, ss TSRMLS_CC); +} + +STDMETHODIMP TPHPScriptingEngine::GetScriptState(SCRIPTSTATE *pssState) +{ + trace("%08x: GetScriptState(current=%s)\n", this, scriptstate_to_string(m_scriptstate)); + tsrm_mutex_lock(m_mutex); + *pssState = m_scriptstate; + tsrm_mutex_unlock(m_mutex); + return S_OK; +} + +STDMETHODIMP TPHPScriptingEngine::Close(void) +{ + TSRMLS_FETCH(); + + if (m_pass) { + m_pass->Release(); + m_pass = NULL; + } + SEND_THREAD_MESSAGE(this, PHPSE_CLOSE, 0, 0 TSRMLS_CC); + return S_OK; +} + +/* Add an item to global namespace. + * This is called in the context of the base thread (or perhaps some other thread). + * We want to be able to work with the object in the engine thread, so we marshal + * it into a stream and let the engine thread deal with it. + * This works quite nicely when PHP scripts call into the object; threading is + * handled correctly. */ +STDMETHODIMP TPHPScriptingEngine::AddNamedItem(LPCOLESTR pstrName, DWORD dwFlags) +{ + struct php_active_script_add_named_item_info info; + TSRMLS_FETCH(); + + info.pstrName = pstrName; + info.dwFlags = dwFlags; + + m_pass->GetItemInfo(pstrName, SCRIPTINFO_IUNKNOWN, &info.punk, NULL); + if (SUCCEEDED(CoMarshalInterThreadInterfaceInStream(IID_IDispatch, info.punk, &info.marshal))) { + SEND_THREAD_MESSAGE(this, PHPSE_ADD_NAMED_ITEM, 0, (LPARAM)&info TSRMLS_CC); + } + info.punk->Release(); + + return S_OK; +} + +/* Bind to a type library */ +STDMETHODIMP TPHPScriptingEngine::AddTypeLib( + /* [in] */ REFGUID rguidTypeLib, + /* [in] */ DWORD dwMajor, + /* [in] */ DWORD dwMinor, + /* [in] */ DWORD dwFlags) +{ + struct php_active_script_add_tlb_info info; + TSRMLS_FETCH(); + + info.rguidTypeLib = &rguidTypeLib; + info.dwMajor = dwMajor; + info.dwMinor = dwMinor; + info.dwFlags = dwFlags; + + SEND_THREAD_MESSAGE(this, PHPSE_ADD_TYPELIB, 0, (LPARAM)&info TSRMLS_CC); + + return S_OK; +} + +/* Returns an object representing the PHP Scripting Engine. + * Optionally, a client can request a particular item directly. + * For the moment, we only do the bare minimum amount of work + * for the engine to work correctly; we can flesh out this part + * a little later. */ +STDMETHODIMP TPHPScriptingEngine::GetScriptDispatch( + /* [in] */ LPCOLESTR pstrItemName, + /* [out] */ IDispatch **ppdisp) +{ + TSRMLS_FETCH(); + *ppdisp = NULL; + struct php_active_script_get_dispatch_info info; + + info.pstrItemName = pstrItemName; + info.dispatch = NULL; + + /* This hack is required because the host is likely to query us + * for a dispatch if we use any of it's objects from PHP script. + * Since the engine thread will be waiting for the return from + * a COM call, we need to deliberately poke a hole in thread + * safety so that it is possible to read the symbol table from + * outside the engine thread and give it a valid return value. + * This is "safe" only in this instance, since we are not modifying + * the engine state by looking up the dispatch (I hope). + * The scripting engine rules pretty much guarantee that this + * method is only called in the base thread. */ + + if (tsrm_thread_id() != m_enginethread) { + tsrm_ls = m_tsrm_hack; + trace("HEY: hacking thread safety!\n"); + } + + if (S_OK == engine_thread_handler(PHPSE_GET_DISPATCH, 0, (LPARAM)&info, NULL TSRMLS_CC)) { + CoGetInterfaceAndReleaseStream(info.dispatch, IID_IDispatch, (void**)ppdisp); + } + + if (*ppdisp) { + return S_OK; + } + return S_FALSE; +} + +STDMETHODIMP TPHPScriptingEngine::GetCurrentScriptThreadID( + /* [out] */ SCRIPTTHREADID *pstidThread) +{ +// tsrm_mutex_lock(m_mutex); + trace("%08x: GetCurrentScriptThreadID()\n", this); + *pstidThread = m_enginethread; +// tsrm_mutex_unlock(m_mutex); + return S_OK; +} + +STDMETHODIMP TPHPScriptingEngine::GetScriptThreadID( + /* [in] */ DWORD dwWin32ThreadId, + /* [out] */ SCRIPTTHREADID *pstidThread) +{ +// tsrm_mutex_lock(m_mutex); + trace("%08x: GetScriptThreadID()\n", this); + *pstidThread = dwWin32ThreadId; +// tsrm_mutex_unlock(m_mutex); + return S_OK; +} + +STDMETHODIMP TPHPScriptingEngine::GetScriptThreadState( + /* [in] */ SCRIPTTHREADID stidThread, + /* [out] */ SCRIPTTHREADSTATE *pstsState) +{ +// tsrm_mutex_lock(m_mutex); + + trace("%08x: GetScriptThreadState()\n", this); + *pstsState = SCRIPTTHREADSTATE_NOTINSCRIPT; + + switch(stidThread) { + case SCRIPTTHREADID_BASE: + stidThread = m_basethread; + break; + case SCRIPTTHREADID_CURRENT: + stidThread = m_enginethread; + break; + }; + if (stidThread == m_basethread) { + *pstsState = SCRIPTTHREADSTATE_NOTINSCRIPT; + } else if (stidThread == m_enginethread) { + *pstsState = SCRIPTTHREADSTATE_NOTINSCRIPT; + } +// tsrm_mutex_unlock(m_mutex); + return S_OK; +} + +STDMETHODIMP TPHPScriptingEngine::InterruptScriptThread( + /* [in] */ SCRIPTTHREADID stidThread, + /* [in] */ const EXCEPINFO *pexcepinfo, + /* [in] */ DWORD dwFlags) +{ + /* do not serialize this method, or call into the script site */ + trace("%08x: InterruptScriptThread()\n", this); + return S_OK; +} + +/* Clone is essential when running under Windows Script Host. + * It creates an engine to parse the code. Once it is parsed, + * the host clones other engines from the original and runs those. + * It is intended to be a fast method of running the same script + * multiple times in multiple threads. */ +STDMETHODIMP TPHPScriptingEngine::Clone( + /* [out] */ IActiveScript **ppscript) +{ + TPHPScriptingEngine *cloned = new TPHPScriptingEngine; + TSRMLS_FETCH(); + + trace("%08x: Clone()\n", this); + + if (ppscript) + *ppscript = NULL; + + if (cloned) { + cloned->InitNew(); + SEND_THREAD_MESSAGE(cloned, PHPSE_CLONE, 0, (LPARAM)this TSRMLS_CC); + trace("%08x: Cloned OK, returning cloned object ptr %08x\n", this, cloned); + *ppscript = (IActiveScript*)cloned; + return S_OK; + + } + + return E_FAIL; +} + + +STDMETHODIMP TPHPScriptingEngine::InitNew( void) +{ + TSRMLS_FETCH(); + SEND_THREAD_MESSAGE(this, PHPSE_INIT_NEW, 0, 0 TSRMLS_CC); + return S_OK; +} + +STDMETHODIMP TPHPScriptingEngine::AddScriptlet( + /* [in] */ LPCOLESTR pstrDefaultName, + /* [in] */ LPCOLESTR pstrCode, + /* [in] */ LPCOLESTR pstrItemName, + /* [in] */ LPCOLESTR pstrSubItemName, + /* [in] */ LPCOLESTR pstrEventName, + /* [in] */ LPCOLESTR pstrDelimiter, + /* [in] */ DWORD dwSourceContextCookie, + /* [in] */ ULONG ulStartingLineNumber, + /* [in] */ DWORD dwFlags, + /* [out] */ BSTR *pbstrName, + /* [out] */ EXCEPINFO *pexcepinfo) +{ + struct php_active_script_add_scriptlet_info info; + TSRMLS_FETCH(); + + info.pstrDefaultName = pstrDefaultName; + info.pstrCode = pstrCode; + info.pstrItemName = pstrItemName; + info.pstrSubItemName = pstrSubItemName; + info.pstrEventName = pstrEventName; + info.pstrDelimiter = pstrDelimiter; + info.dwSourceContextCookie = dwSourceContextCookie; + info.ulStartingLineNumber = ulStartingLineNumber; + info.dwFlags = dwFlags; + info.pbstrName = pbstrName; + info.pexcepinfo = pexcepinfo; + + return SEND_THREAD_MESSAGE(this, PHPSE_ADD_SCRIPTLET, 0, (LPARAM)&info TSRMLS_CC); +} + +STDMETHODIMP TPHPScriptingEngine::ParseScriptText( + /* [in] */ LPCOLESTR pstrCode, + /* [in] */ LPCOLESTR pstrItemName, + /* [in] */ IUnknown *punkContext, + /* [in] */ LPCOLESTR pstrDelimiter, + /* [in] */ DWORD dwSourceContextCookie, + /* [in] */ ULONG ulStartingLineNumber, + /* [in] */ DWORD dwFlags, + /* [out] */ VARIANT *pvarResult, + /* [out] */ EXCEPINFO *pexcepinfo) +{ + struct php_active_script_parse_info info; + TSRMLS_FETCH(); + + info.pstrCode = pstrCode; + info.pstrItemName = pstrItemName; + info.punkContext = punkContext; + info.pstrDelimiter = pstrDelimiter; + info.dwSourceContextCookie = dwSourceContextCookie; + info.ulStartingLineNumber = ulStartingLineNumber; + info.dwFlags = dwFlags; + info.pvarResult = pvarResult; + info.pexcepinfo = pexcepinfo; + + return SEND_THREAD_MESSAGE(this, PHPSE_PARSE_SCRIPT, 0, (LPARAM)&info TSRMLS_CC); +} + +STDMETHODIMP TPHPScriptingEngine::ParseProcedureText( + /* [in] */ LPCOLESTR pstrCode, + /* [in] */ LPCOLESTR pstrFormalParams, + /* [in] */ LPCOLESTR pstrProcedureName, + /* [in] */ LPCOLESTR pstrItemName, + /* [in] */ IUnknown *punkContext, + /* [in] */ LPCOLESTR pstrDelimiter, + /* [in] */ DWORD dwSourceContextCookie, + /* [in] */ ULONG ulStartingLineNumber, + /* [in] */ DWORD dwFlags, + /* [out] */ IDispatch **ppdisp) +{ + struct php_active_script_parse_proc_info info; + HRESULT ret; + TSRMLS_FETCH(); + + info.pstrCode = pstrCode; + info.pstrFormalParams = pstrFormalParams; + info.pstrProcedureName = pstrProcedureName; + info.pstrItemName = pstrItemName; + info.punkContext = punkContext; + info.pstrDelimiter = pstrDelimiter; + info.dwSourceContextCookie = dwSourceContextCookie; + info.ulStartingLineNumber = ulStartingLineNumber; + info.dwFlags = dwFlags; + info.ppdisp = ppdisp; + + ret = SEND_THREAD_MESSAGE(this, PHPSE_PARSE_PROC, 0, (LPARAM)&info TSRMLS_CC); + + /* + if (ret == S_OK) { + ret = CoGetInterfaceAndReleaseStream(info.disp, IID_IDispatch, (void**)ppdisp); + + } + */ + trace("ParseProc: ret=%08x disp=%08x\n", ret, *ppdisp); + return ret; +} + +extern "C" +void activescript_error_func(int type, const char *error_msg, ...) +{ + TSRMLS_FETCH(); + + TPHPScriptingEngine *engine = (TPHPScriptingEngine*)SG(server_context); + + +} + +class TActiveScriptError: + public IActiveScriptError +{ +protected: + volatile LONG m_refcount; +public: + /* IUnknown */ + STDMETHODIMP QueryInterface(REFIID iid, void **ppvObject) { + *ppvObject = NULL; + + if (IsEqualGUID(IID_IActiveScriptError, iid)) { + *ppvObject = (IActiveScriptError*)this; + } else if (IsEqualGUID(IID_IUnknown, iid)) { + *ppvObject = this; + } + if (*ppvObject) { + AddRef(); + return S_OK; + } + return E_NOINTERFACE; + } + + STDMETHODIMP_(DWORD) AddRef(void) { + return InterlockedIncrement(&m_refcount); + } + + STDMETHODIMP_(DWORD) Release(void) { + DWORD ret = InterlockedDecrement(&m_refcount); + trace("Release: errobj refcount=%d\n", ret); + if (ret == 0) + delete this; + return ret; + } + + HRESULT STDMETHODCALLTYPE GetExceptionInfo( + /* [out] */ EXCEPINFO *pexcepinfo) + { + memset(pexcepinfo, 0, sizeof(EXCEPINFO)); + pexcepinfo->bstrDescription = SysAllocString(m_message); + pexcepinfo->bstrSource = SysAllocString(m_filename); + pexcepinfo->wCode = 1000; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE GetSourcePosition( + /* [out] */ DWORD *pdwSourceContext, + /* [out] */ ULONG *pulLineNumber, + /* [out] */ LONG *plCharacterPosition) + { + *pdwSourceContext = 0; + *pulLineNumber = m_lineno; + *plCharacterPosition = 0; + return S_OK; + } + + HRESULT STDMETHODCALLTYPE GetSourceLineText( + /* [out] */ BSTR *pbstrSourceLine) + { + *pbstrSourceLine = NULL; + return E_FAIL; + } + + BSTR m_filename, m_message; + UINT m_lineno; + + TActiveScriptError(const char *filename, const uint lineno, const char *message) + { + m_refcount = 0; /* start with zero refcount because this object is passed + * directly to the script site; it will call addref */ + m_filename = TWideString::bstr_from_ansi((char*)filename); + m_message = TWideString::bstr_from_ansi((char*)message); + m_lineno = lineno; + } + + ~TActiveScriptError() + { + trace("%08x: cleaning up error object\n", this); + SysFreeString(m_filename); + SysFreeString(m_message); + } +}; + +extern "C" +void activescript_error_handler(int type, const char *error_filename, + const uint error_lineno, const char *format, va_list args) +{ + TSRMLS_FETCH(); + char *buf; + int buflen; + TPHPScriptingEngine *engine = (TPHPScriptingEngine*)SG(server_context); + + buflen = vspprintf(&buf, PG(log_errors_max_len), format, args); + trace("%08x: Error: %s\n", engine, buf); + + /* if it's a fatal error, report it using IActiveScriptError. */ + + switch(type) { + case E_ERROR: + case E_CORE_ERROR: + case E_COMPILE_ERROR: + case E_USER_ERROR: + case E_PARSE: + /* trigger an error in the host */ + TActiveScriptError *eobj = new TActiveScriptError(error_filename, error_lineno, buf); + trace("raising error object!\n"); + if (engine->m_pass_eng) + engine->m_pass_eng->OnScriptError(eobj); + + /* now throw the exception to abort execution */ + if (engine->m_err_trap) + longjmp(*engine->m_err_trap, 1); + + break; + } + efree(buf); +} + |