From: Wez Furlong Date: Tue, 27 Jul 2004 03:57:31 +0000 (+0000) Subject: Major re-jig. X-Git-Tag: PRE_ZEND_VM_DISPATCH_PATCH~354 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=ac878007602a8fb06d17de5daa559a31fabf85cb;p=php Major re-jig. With thanks to Rob Richards for tracking down a couple of big bugs caused by teeny bits of code. --- diff --git a/sapi/activescript/README b/sapi/activescript/README index 39066f5783..fc889d9e1b 100644 --- a/sapi/activescript/README +++ b/sapi/activescript/README @@ -4,7 +4,6 @@ This is the ActiveScript SAPI for PHP. Once registered on your system (using regsvr32), you will be able to use PHP script in any ActiveScript compliant host. The list includes: -o. Client-side script in Internet Explorer o. Windows Script Host o. ASP and ASP.NET o. Windows Script Components / Behaviours @@ -24,12 +23,6 @@ Build and install it somewhere; then register the engine like this: Usage. ====== -o. Client-side script in Internet Explorer - - - o. Windows Script Host Create a .wsf file like this: @@ -38,7 +31,7 @@ o. Windows Script Host - + o. ASP and ASP.NET diff --git a/sapi/activescript/classfactory.cpp b/sapi/activescript/classfactory.cpp index bd0416d7ed..5fa37c9f03 100644 --- a/sapi/activescript/classfactory.cpp +++ b/sapi/activescript/classfactory.cpp @@ -98,11 +98,15 @@ STDMETHODIMP TPHPClassFactory::LockServer(BOOL fLock) STDMETHODIMP TPHPClassFactory::CreateInstance(IUnknown *pUnkOuter, REFIID iid, void **ppvObject) { - TPHPScriptingEngine *engine = new TPHPScriptingEngine; + IUnknown *punk = create_scripting_engine(NULL); + HRESULT ret; - HRESULT ret = engine->QueryInterface(iid, ppvObject); - - engine->Release(); + if (punk) { + ret = punk->QueryInterface(iid, ppvObject); + punk->Release(); + } else { + ret = E_UNEXPECTED; + } return ret; } @@ -161,7 +165,13 @@ static const GUID *script_engine_categories[] = { }; static const struct reg_class classes_to_register[] = { - { &CLSID_PHPActiveScriptEngine, "PHP Active Script Engine", "Both", engine_entries, script_engine_categories }, + { &CLSID_PHPActiveScriptEngine, "PHP Active Script Engine", +#if ACTIVEPHP_THREADING_MODE == COINIT_MULTITHREADED + "Both", +#else + "Apartment", +#endif + engine_entries, script_engine_categories }, { NULL, NULL, NULL, 0, NULL } }; /* }}} */ diff --git a/sapi/activescript/config.w32 b/sapi/activescript/config.w32 index 401f8663a6..0dbfc5851c 100644 --- a/sapi/activescript/config.w32 +++ b/sapi/activescript/config.w32 @@ -8,6 +8,6 @@ if (PHP_ACTIVESCRIPT == "yes") { ERROR("ActiveScript module requires an --enable-zts build of PHP"); } - SAPI('activescript', 'classfactory.cpp php5activescript.c scriptengine.cpp', 'php' + PHP_VERSION + 'activescript.dll', '/D PHP5ISAPI_EXPORTS /D ACTIVEPHP_OBJECT_SAFETY=1'); + SAPI('activescript', 'classfactory.cpp php5activescript.c scriptengine.cpp marshal.cpp', 'php' + PHP_VERSION + 'activescript.dll', '/D PHP5ISAPI_EXPORTS /D ACTIVEPHP_OBJECT_SAFETY=1'); ADD_FLAG('LDFLAGS_ACTIVESCRIPT', 'oleaut32.lib ole32.lib user32.lib advapi32.lib /DEF:' + configure_module_dirname + '\\php5activescript.def'); } diff --git a/sapi/activescript/marshal.cpp b/sapi/activescript/marshal.cpp new file mode 100755 index 0000000000..2d4c61cb87 --- /dev/null +++ b/sapi/activescript/marshal.cpp @@ -0,0 +1,406 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2004 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.0 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_0.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 | + +----------------------------------------------------------------------+ + */ +/* $Id$ */ + +/* Fun with threads */ + +#define _WIN32_DCOM +#define ZEND_INCLUDE_FULL_WINDOWS_HEADERS +#include +#include "php5as_scriptengine.h" +#include "php5as_classfactory.h" +#include +#undef php_win_err + +extern "C" char *php_win_err(HRESULT ret); + +#define APHPM_IN 1 +#define APHPM_OUT 2 + +#define APHPT_TERM 0 +#define APHPT_UNK 1 /* IUnknown * */ +#define APHPT_DISP 2 /* IDispatch * */ +#define APHPT_VAR 3 /* PVARIANT */ + +static inline void trace(char *fmt, ...) +{ + va_list ap; + char buf[4096]; + + sprintf(buf, "T=%08x [MARSHAL] ", tsrm_thread_id()); + OutputDebugString(buf); + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + + OutputDebugString(buf); + + va_end(ap); +} +struct marshal_arg { + int type; + int argno; + int direction; +}; + +static int parse_script_text_mdef[] = { + APHPT_UNK, 2, APHPM_IN, + APHPT_VAR, 7, APHPM_OUT, + APHPT_TERM +}; + +static int get_script_dispatch_mdef[] = { + APHPT_DISP, 1, APHPM_OUT, + APHPT_TERM +}; + +static int *mdef_by_func[APHP__Max] = { + parse_script_text_mdef, + NULL, /* InitNew */ + NULL, /* AddNamedItem */ + NULL, /* SetScriptState */ + get_script_dispatch_mdef, + NULL, /* Close */ + NULL, /* AddTypeLib */ + NULL, /* AddScriptlet */ +}; + +static HRESULT do_marshal_in(int stub, void *args[16], int *mdef, LPSTREAM *ppstm) +{ + int i = 0; + int want; + HRESULT ret = S_OK; + LPSTREAM stm = NULL; + + if (!mdef) + return S_OK; + + trace("marshalling ... \n"); + + ret = CreateStreamOnHGlobal(NULL, TRUE, &stm); + if (FAILED(ret)) { + trace(" failed to create stm %s", php_win_err(ret)); + return ret; + } + + *ppstm = stm; + + /* if stub is true, we are the stub and are marshaling OUT params, + * otherwise, we are the proxy and are marshalling IN params */ + + if (stub) { + want = APHPM_OUT; + } else { + want = APHPM_IN; + } + + while (mdef[i] != APHPT_TERM) { + if ((mdef[i+2] & want) == want) { + int argno = mdef[i+1]; + int isout = (mdef[i+2] & APHPM_OUT) == APHPM_OUT; + +#undef OUT_IFACE +#define OUT_IFACE (isout ? *(IUnknown**)args[argno] : (IUnknown*)args[argno]) +#define IFACE_PRESENT args[argno] && (!isout || *(IUnknown**)args[argno]) + switch (mdef[i]) { + case APHPT_UNK: + if (IFACE_PRESENT) { + ret = CoMarshalInterface(stm, IID_IUnknown, OUT_IFACE, MSHCTX_INPROC, NULL, MSHLFLAGS_NORMAL); + trace(" arg=%d IUnknown --> %s", argno, php_win_err(ret)); + } else { + trace(" arg=%d IUnknown(NULL) - skip\n", argno); + } + break; + + case APHPT_DISP: + if (IFACE_PRESENT) { + ret = CoMarshalInterface(stm, IID_IDispatch, OUT_IFACE, MSHCTX_INPROC, NULL, MSHLFLAGS_NORMAL); + trace(" arg=%d IDispatch --> %s", argno, php_win_err(ret)); + } else { + trace(" arg=%d IDispatch(NULL) - skip\n", argno); + } + break; + + case APHPT_VAR: + if (args[argno]) + ret = E_NOTIMPL; + break; + + default: + ret = E_NOTIMPL; + } + + if (FAILED(ret)) + break; + } else { + trace(" -- skipping (this param is not needed in this direction)\n"); + } + i += 3; + } + + if (FAILED(ret)) { + /* TODO: rollback (refcounts are held during marshalling) */ + trace(" rolling back\n"); + stm->Release(); + *ppstm = NULL; + } else { + LARGE_INTEGER pos = {0}; + stm->Seek(pos, STREAM_SEEK_SET, NULL); + } + + return ret; +} + +static HRESULT do_marshal_out(int stub, void *args[16], int *mdef, LPSTREAM stm) +{ + int i = 0; + int want; + HRESULT ret = S_OK; + + if (!mdef) + return S_OK; + + trace(" unmarshalling...\n"); + + /* if stub is true, we are the stub and are unmarshaling IN params, + * otherwise, we are the proxy and are unmarshalling OUT params */ + + if (!stub) { + want = APHPM_OUT; + } else { + want = APHPM_IN; + } + + while (mdef[i] != APHPT_TERM) { + if ((mdef[i+2] & want) == want) { + int argno = mdef[i+1]; + int isout = (mdef[i+2] & APHPM_OUT) == APHPM_OUT; +#undef OUT_IFACE +#define OUT_IFACE (isout ? (void**)args[argno] : &args[argno]) + + switch (mdef[i]) { + case APHPT_UNK: + if (IFACE_PRESENT) { + ret = CoUnmarshalInterface(stm, IID_IUnknown, OUT_IFACE); + trace(" unmarshal arg=%d IUnknown --> %s", argno, php_win_err(ret)); + } else { + trace(" unmarshal arg=%d IUnknown(NULL) - skip\n", argno); + } + break; + + case APHPT_DISP: + if (IFACE_PRESENT) { + trace(" unmarshal dispatch: args[%d]=%p *args[%d]=%p\n", + argno, args[argno], argno, args[argno] ? *(void**)args[argno] : NULL); + ret = CoUnmarshalInterface(stm, IID_IDispatch, OUT_IFACE); + trace(" unmarshal arg=%d IDispatch --> %s: args[%d]=%p *args[%d]=%p\n", argno, php_win_err(ret), + argno, args[argno], argno, args[argno] ? *(void**)args[argno] : NULL); + } else { + trace(" unmarshal arg=%d IDispatch(NULL) - skip\n", argno); + } + break; + + case APHPT_VAR: + if (args[argno]) + ret = E_NOTIMPL; + break; + + default: + ret = E_NOTIMPL; + } + if (FAILED(ret)) + break; + } + i += 3; + } + + return ret; +} + + +struct activephp_serialize_msg { + class TPHPScriptingEngine *engine; + void *args[16]; + int nargs; + enum activephp_engine_func func; + int *marshal_defs; + LPSTREAM instm, outstm; + + HANDLE evt; + HRESULT ret; +}; + +static const char *func_names[APHP__Max] = { + "ParseScriptText", + "InitNew", + "AddnamedItem", + "SetScriptState", + "GetScriptDispatch", + "Close", + "AddTypeLib", + "AddScriptlet", +}; + +HRESULT marshal_call(class TPHPScriptingEngine *engine, enum activephp_engine_func func, int nargs, ...) +{ + va_list ap; + struct activephp_serialize_msg msg ; + HRESULT ret; + + memset(&msg, 0, sizeof(msg)); + + msg.engine = engine; + msg.func = func; + msg.marshal_defs = mdef_by_func[func]; + + trace(" prepping for function code %d %s, %d args, marshal defs at %p\n", func, func_names[func], nargs, msg.marshal_defs); + + va_start(ap, nargs); + for (msg.nargs = 0; msg.nargs < nargs; msg.nargs++) { + msg.args[msg.nargs] = va_arg(ap, void*); + } + va_end(ap); + + ret = do_marshal_in(0, msg.args, msg.marshal_defs, &msg.instm); + + if (FAILED(ret)) { + return ret; + } + +#if 1 + msg.evt = CreateEvent(NULL, TRUE, FALSE, NULL); + PostMessage(engine->m_queue, WM_ACTIVEPHP_SERIALIZE, 0, (LPARAM)&msg); + + while (WAIT_OBJECT_0 != WaitForSingleObject(msg.evt, 0)) { + DWORD status = MsgWaitForMultipleObjects(1, &msg.evt, FALSE, INFINITE, QS_ALLEVENTS|QS_ALLINPUT|QS_ALLPOSTMESSAGE|QS_SENDMESSAGE|QS_POSTMESSAGE); + + if (status == WAIT_OBJECT_0) + break; + + MSG msg; + + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + CloseHandle(msg.evt); +#else + ret = SendMessage(engine->m_queue, WM_ACTIVEPHP_SERIALIZE, 0, (LPARAM)&msg); +#endif + + if (msg.outstm) { + ret = do_marshal_out(0, msg.args, msg.marshal_defs, msg.outstm); + msg.outstm->Release(); + } + + if (msg.instm) + msg.instm->Release(); + + trace("marshall call to %s completed %s", func_names[func], php_win_err(ret)); + + return ret; +} + +HRESULT marshal_stub(LPARAM lparam) +{ + struct activephp_serialize_msg *msg = (struct activephp_serialize_msg*)lparam; + + if (msg->instm) { + msg->ret = do_marshal_out(1, msg->args, msg->marshal_defs, msg->instm); + + if (FAILED(msg->ret)) { + SetEvent(msg->evt); + return msg->ret; + } + } + + switch (msg->func) { + case APHP_ParseScriptText: + msg->ret = msg->engine->ParseScriptText( + (LPCOLESTR)msg->args[0], + (LPCOLESTR)msg->args[1], + (IUnknown*)msg->args[2], + (LPCOLESTR)msg->args[3], + (DWORD)msg->args[4], + (ULONG)msg->args[5], + (DWORD)msg->args[6], + (VARIANT*)msg->args[7], + (EXCEPINFO*)msg->args[8]); + break; + + case APHP_InitNew: + msg->ret = msg->engine->InitNew(); + break; + + case APHP_AddNamedItem: + msg->ret = msg->engine->AddNamedItem( + (LPCOLESTR)msg->args[0], + (DWORD)msg->args[1]); + break; + + case APHP_SetScriptState: + msg->ret = msg->engine->SetScriptState((SCRIPTSTATE)(LONG)msg->args[0]); + break; + + case APHP_GetScriptDispatch: + msg->ret = msg->engine->GetScriptDispatch( + (LPCOLESTR)msg->args[0], + (IDispatch**)msg->args[1]); + break; + + case APHP_Close: + msg->ret = msg->engine->Close(); + break; + + case APHP_AddTypeLib: + msg->ret = msg->engine->AddTypeLib( + (REFGUID)msg->args[0], + (DWORD)msg->args[1], + (DWORD)msg->args[2], + (DWORD)msg->args[3]); + break; + + case APHP_AddScriptlet: + msg->ret = msg->engine->AddScriptlet( + (LPCOLESTR)msg->args[0], + (LPCOLESTR)msg->args[1], + (LPCOLESTR)msg->args[2], + (LPCOLESTR)msg->args[3], + (LPCOLESTR)msg->args[4], + (LPCOLESTR)msg->args[5], + (DWORD)msg->args[6], + (ULONG)msg->args[7], + (DWORD)msg->args[8], + (BSTR*)msg->args[9], + (EXCEPINFO*)msg->args[10]); + break; + + default: + msg->ret = E_NOTIMPL; + } + + if (SUCCEEDED(msg->ret)) { + msg->ret = do_marshal_in(1, msg->args, msg->marshal_defs, &msg->outstm); + } + + SetEvent(msg->evt); + + return msg->ret; +} + diff --git a/sapi/activescript/php5activescript.c b/sapi/activescript/php5activescript.c index 93222fdbf8..6e83b99d5c 100644 --- a/sapi/activescript/php5activescript.c +++ b/sapi/activescript/php5activescript.c @@ -145,6 +145,7 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) activescript_sapi_module.shutdown(&sapi_module); } //OutputDebugString("PROCESS_DETACH\n"); + sapi_shutdown(); tsrm_shutdown(); break; } diff --git a/sapi/activescript/php5as_classfactory.h b/sapi/activescript/php5as_classfactory.h index 61823fb689..048d38b7e5 100644 --- a/sapi/activescript/php5as_classfactory.h +++ b/sapi/activescript/php5as_classfactory.h @@ -25,6 +25,10 @@ DEFINE_GUID(CLSID_PHPActiveScriptEngine, 0xcf108a38, 0x59a9, 0x468a, 0xaf, 0x45, 0x13, 0x68, 0xd7, 0x85, 0x5d, 0xae); +// {AD504760-D6B9-4537-AEAC-512FFB359009} +DEFINE_GUID(CLSID_PHPActiveScriptEngineMarshal, +0xad504760, 0xd6b9, 0x4537, 0xae, 0xac, 0x51, 0x2f, 0xfb, 0x35, 0x90, 0x9); + #if 0 /* this was for PHP 4 */ // {A0AD8E7A-95EC-4819-986F-78D93895F2AE} diff --git a/sapi/activescript/php5as_scriptengine.h b/sapi/activescript/php5as_scriptengine.h index bd720a584c..83ab582cc2 100644 --- a/sapi/activescript/php5as_scriptengine.h +++ b/sapi/activescript/php5as_scriptengine.h @@ -18,129 +18,68 @@ /* $Id$ */ #include -#if ACTIVEPHP_OBJECT_SAFETY -# include -#endif +#include #include "zend.h" -#include - -/* Definitions for thread messages */ -enum { - PHPSE_STATE_CHANGE = WM_USER + 20, - PHPSE_INIT_NEW, - PHPSE_PARSE_SCRIPT, - PHPSE_ADD_SCRIPTLET, - PHPSE_CLOSE, - PHPSE_CLONE, - PHPSE_ENTER, - PHPSE_LEAVE, - PHPSE_TERMINATE, - PHPSE_PARSE_PROC, - PHPSE_EXEC_PROC, - PHPSE_ADD_NAMED_ITEM, - PHPSE_SET_SITE, - PHPSE_ADD_TYPELIB, - PHPSE_TRIGGER_ERROR, - PHPSE_GET_DISPATCH, - PHPSE_DUMMY_TICK, -}; -struct php_active_script_get_dispatch_info { - LPCOLESTR pstrItemName; - DWORD dispatch; -}; - -struct php_active_script_add_named_item_info { - LPCOLESTR pstrName; - DWORD dwFlags; - IUnknown *punk; - ITypeInfo *ptyp; - IDispatch *pdisp; - DWORD marshal; -}; +#if 0 +#define ACTIVEPHP_THREADING_MODE COINIT_MULTITHREADED +#else +#define ACTIVEPHP_THREADING_MODE COINIT_APARTMENTTHREADED +#endif -struct php_active_script_add_scriptlet_info { - /* [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; -}; +#define ACTIVEPHP_HAS_OWN_THREAD 1 -struct php_active_script_parse_info { - /* [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; -}; +#define WM_ACTIVEPHP_SERIALIZE WM_USER + 200 -struct php_active_script_parse_proc_info { - /* [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; - DWORD dispcookie; +enum activephp_engine_func { /* if you change the order, change marshal.cpp too */ + APHP_ParseScriptText, + APHP_InitNew, + APHP_AddNamedItem, + APHP_SetScriptState, + APHP_GetScriptDispatch, + APHP_Close, + APHP_AddTypeLib, + APHP_AddScriptlet, + APHP__Max }; -struct php_active_script_add_tlb_info { - /* [in] */ const GUID * rguidTypeLib; - /* [in] */ DWORD dwMajor; - /* [in] */ DWORD dwMinor; - /* [in] */ DWORD dwFlags; -}; +HRESULT marshal_call(class TPHPScriptingEngine *engine, enum activephp_engine_func func, int nargs, ...); +HRESULT marshal_stub(LPARAM lparam); class TPHPScriptingEngine: public IActiveScript, public IActiveScriptParse, - public IActiveScriptParseProcedure -#if ACTIVEPHP_OBJECT_SAFETY - , public IObjectSafety + public IActiveScriptParseProcedure, + public IObjectSafety, + public IDispatch +#if 0 + , public IMarshal #endif { public: volatile LONG m_refcount; IActiveScriptSite *m_pass; SCRIPTSTATE m_scriptstate; - MUTEX_T m_mutex; - HashTable m_script_dispatchers; - HANDLE m_engine_thread_handle; - HANDLE m_sync_thread_msg; - HRESULT m_sync_thread_ret; - - /* This is hacky, but only used when the host queries us for a script dispatch */ - void *** m_tsrm_hack; - void add_to_global_namespace(IDispatch *disp, DWORD flags, char *name TSRMLS_DC); THREAD_T m_enginethread, m_basethread; HashTable m_frags; ULONG m_lambda_count; - IActiveScriptSite *m_pass_eng; + DWORD m_gitcookie, m_asscookie; + HWND m_queue; + + int m_done_init; - jmp_buf *m_err_trap; int m_in_main, m_stop_main; - - HRESULT SendThreadMessage(LONG msg, WPARAM wparam, LPARAM lparam); - void engine_thread_func(void); - HRESULT engine_thread_handler(LONG msg, WPARAM wParam, LPARAM lParam, int *handled TSRMLS_DC); + void do_clone(TPHPScriptingEngine *src); +void setup_engine_state(void); + int create_id(OLECHAR *name, DISPID *dispid TSRMLS_DC); + + char *m_names[1024]; + int m_lens[1024]; + int m_ids; public: /* IUnknown */ STDMETHODIMP QueryInterface(REFIID iid, void **ppvObject); @@ -234,7 +173,6 @@ public: /* IActiveScriptParseProcedure */ /* [in] */ DWORD dwFlags, /* [out] */ IDispatch **ppdisp); -#if ACTIVEPHP_OBJECT_SAFETY public: /* IObjectSafety */ STDMETHODIMP GetInterfaceSafetyOptions( /* [in] */ REFIID riid, // Interface that we want options for @@ -245,11 +183,33 @@ public: /* IObjectSafety */ /* [in] */ REFIID riid, // Interface to set options for /* [in] */ DWORD dwOptionSetMask, // Options to change /* [in] */ DWORD dwEnabledOptions); // New option values +#if 0 +public: /* IMarshal */ + STDMETHODIMP GetUnmarshalClass( + REFIID riid, void *pv, DWORD dwDestContext, void *pvDestContext, DWORD mshlflags, CLSID *pCid); + STDMETHODIMP GetMarshalSizeMax( + REFIID riid, void *pv, DWORD dwDestContext, void *pvDestContext, DWORD mshlflags, ULONG *pSize); + STDMETHODIMP MarshalInterface( + IStream *pStm, REFIID riid, void *pv, DWORD dwDestContext, void *pvDestContext, DWORD mshflags); + STDMETHODIMP UnmarshalInterface( + IStream *pStm, REFIID riid, void **ppv); + STDMETHODIMP ReleaseMarshalData(IStream *pStm); + STDMETHODIMP DisconnectObject(DWORD dwReserved); #endif + +public: /* IDispatch */ + STDMETHODIMP GetIDsOfNames( REFIID riid, OLECHAR **rgszNames, unsigned int cNames, LCID lcid, DISPID *rgDispId); + STDMETHODIMP Invoke( DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, + DISPPARAMS FAR* pdp, VARIANT FAR* pvarRes, EXCEPINFO FAR* pei, + unsigned int FAR* puArgErr); + STDMETHODIMP GetTypeInfoCount(unsigned int * pctinfo); + STDMETHODIMP GetTypeInfo( unsigned int iTInfo, LCID lcid, ITypeInfo **ppTInfo); public: TPHPScriptingEngine(); ~TPHPScriptingEngine(); - + }; +IUnknown *create_scripting_engine(TPHPScriptingEngine *tobecloned); + diff --git a/sapi/activescript/scriptengine.cpp b/sapi/activescript/scriptengine.cpp index 9906ca1490..56e5c797c5 100644 --- a/sapi/activescript/scriptengine.cpp +++ b/sapi/activescript/scriptengine.cpp @@ -45,6 +45,7 @@ extern "C" { #include "php5activescript.h" #include "ext/com_dotnet/php_com_dotnet.h" #include "ext/com_dotnet/php_com_dotnet_internal.h" +#include "zend_exceptions.h" } #include "php_ticks.h" #include "php5as_scriptengine.h" @@ -52,7 +53,30 @@ extern "C" { #include #undef php_win_err -#define ACTIVEPHP_THREADING_MODE COINIT_MULTITHREADED +static int clone_frags(void *pDest, void *arg TSRMLS_DC); + +#define ENGINE_THREAD_ONLY(type, method) \ + if (tsrm_thread_id() != m_enginethread) { \ + trace("WRONG THREAD !! " #type "::" #method "\n"); \ + return RPC_E_WRONG_THREAD; \ + } \ + trace("[direct] " #type "::" #method "\n"); + + +#define ASS_CALL(ret, method, args) \ + if (tsrm_thread_id() == m_basethread) { \ + trace("Calling [direct] m_pass->" #method "\n"); \ + ret = m_pass->method args; \ + } else { \ + IActiveScriptSite *ass; \ + trace("Calling [marshall] m_pass->" #method "\n"); \ + ret = GIT_get(m_asscookie, IID_IActiveScriptSite, (void**)&ass); \ + if (SUCCEEDED(ret)) { \ + ret = ass->method args; \ + ass->Release(); \ + } \ + } \ + trace("--- done calling m_pass->" #method "\n"); /* {{{ trace */ static inline void trace(char *fmt, ...) @@ -99,10 +123,12 @@ class TWideString { TWideString(LPOLESTR olestr) { m_ole = olestr; m_ansi = NULL; + m_ansi_strlen = 0; } TWideString(LPCOLESTR olestr) { m_ole = (LPOLESTR)olestr; m_ansi = NULL; + m_ansi_strlen = 0; } ~TWideString() { @@ -110,6 +136,7 @@ class TWideString { CoTaskMemFree(m_ansi); } m_ansi = NULL; + m_ansi_strlen = 0; } char *safe_ansi_string() { @@ -162,12 +189,13 @@ class TWideString { if (m_ansi_strlen) { m_ansi_strlen--; - m_ansi[m_ansi_strlen] = 0; - } else { trace("conversion failed with return code %08x\n", GetLastError()); } } + if (m_ansi) { + m_ansi[m_ansi_strlen] = 0; + } } return m_ansi; } @@ -213,16 +241,6 @@ static code_frag *clone_code_fragment(code_frag *frag, TPHPScriptingEngine *engi /* }}} */ -/* 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)); -} - /* These functions do some magic so that interfaces can be * used across threads without worrying about marshalling * or not marshalling, as appropriate. @@ -353,19 +371,183 @@ public: } }; /* }}} */ + STDMETHODIMP TPHPScriptingEngine::GetTypeInfoCount(unsigned int * pctinfo) { + *pctinfo = 0; + trace("%08x: ScriptDispatch: GetTypeInfoCount\n", this); + return S_OK; + } + STDMETHODIMP TPHPScriptingEngine::GetTypeInfo( unsigned int iTInfo, LCID lcid, ITypeInfo **ppTInfo) { + trace("%08x: ScriptDispatch: GetTypeInfo\n", this); + return DISP_E_BADINDEX; + } -/* {{{ 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 +int TPHPScriptingEngine::create_id(OLECHAR *name, DISPID *dispid TSRMLS_DC) { -public: - ScriptDispatch() { - m_refcount = 1; + int ex = 0; + char *lcname; + int l; + + if (m_ids >= 1023) { + trace("too many ids\n"); + return 0; } -}; -/* }}} */ + + TWideString aname(name); + + l = aname.ansi_len(); + lcname = zend_str_tolower_dup(aname.ansi_string(), l); + ex = zend_hash_exists(EG(function_table), lcname, l+1); + efree(lcname); + + if (!ex) { + trace("no such id %s\n", aname.ansi_string()); + return 0; + } + + /* do we already have an id for this name? */ + int i; + for (i = 0; i < m_ids; i++) { + if (!strcasecmp(m_names[i], aname.ansi_string())) { + trace("already had this ID\n"); + return i; + } + } + + m_lens[m_ids] = aname.ansi_len(); + m_names[m_ids] = aname.ansi_string(); + trace("created ID %d for name %s\n", m_ids, m_names[m_ids]); + aname.m_ansi = NULL; + *dispid = m_ids; + m_ids++; + return 1; +} + +STDMETHODIMP TPHPScriptingEngine::GetIDsOfNames( REFIID riid, OLECHAR **rgszNames, unsigned int cNames, LCID lcid, DISPID *rgDispId) +{ + unsigned int i; + HRESULT ret = S_OK; + TSRMLS_FETCH(); + + if (tsrm_thread_id() != m_enginethread) { + trace("GetIDsOfNames called from wrong thread\n"); + return RPC_E_WRONG_THREAD; + } + + trace("%08x: ScriptDispatch: GetIDsOfNames %d names: \n", this, cNames); + for (i = 0; i < cNames; i++) { + if (!create_id(rgszNames[i], &rgDispId[i] TSRMLS_CC)) { + ret = DISP_E_UNKNOWNNAME; + break; + } + } + return ret; +} + +STDMETHODIMP TPHPScriptingEngine::Invoke( DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, + DISPPARAMS FAR* pdp, VARIANT FAR* pvarRes, EXCEPINFO FAR* pei, + unsigned int FAR* puArgErr) +{ + UINT i; + zval *retval = NULL; + zval ***params = NULL; + HRESULT ret = DISP_E_MEMBERNOTFOUND; + char *name; + int namelen; + TSRMLS_FETCH(); + + if (tsrm_thread_id() != m_enginethread) { + trace("Invoke called from wrong thread\n"); + return RPC_E_WRONG_THREAD; + } + + name = m_names[dispIdMember]; + namelen = m_lens[dispIdMember]; + + trace("%08x: ScriptDispatch: Invoke dispid %08x [%s]\n", this, dispIdMember, name); + /* this code is similar to that of our com_wrapper InvokeEx implementation */ + + /* convert args into zvals. + * Args are in reverse order */ + if (pdp->cArgs) { + params = (zval ***)safe_emalloc(sizeof(zval **), pdp->cArgs, 0); + for (i = 0; i < pdp->cArgs; i++) { + VARIANT *arg; + zval *zarg; + + arg = &pdp->rgvarg[ pdp->cArgs - 1 - i]; + + trace("alloc zval for arg %d VT=%08x\n", i, V_VT(arg)); + + ALLOC_INIT_ZVAL(zarg); + php_com_wrap_variant(zarg, arg, CP_ACP TSRMLS_CC); + params[i] = &zarg; + } + } + + trace("arguments processed, prepare to do some work\n"); + + /* TODO: if PHP raises an exception here, we should catch it + * and expose it as a COM exception */ + +#if 0 + if (wFlags & DISPATCH_PROPERTYGET) { + retval = zend_read_property(Z_OBJCE_P(disp->object), disp->object, m_names[dispIdMember], m_lens[dispIdMember]+1, 1 TSRMLS_CC); + } else if (wFlags & DISPATCH_PROPERTYPUT) { + zend_update_property(Z_OBJCE_P(disp->object), disp->object, m_names[dispIdMember], m_lens[dispIdMember]+1, *params[0] TSRMLS_CC); + } else +#endif + if (wFlags & DISPATCH_METHOD) { + zval *zname; + MAKE_STD_ZVAL(zname); + ZVAL_STRINGL(zname, (char*)name, namelen, 1); + trace("invoke function %s\n", Z_STRVAL_P(zname)); + + zend_try { + + if (SUCCESS == call_user_function_ex(CG(function_table), NULL, zname, + &retval, pdp->cArgs, params, 1, NULL TSRMLS_CC)) { + ret = S_OK; + trace("we ran it\n"); + } else { + ret = DISP_E_EXCEPTION; + trace("no such function\n"); + } + + } zend_catch { + ret = DISP_E_EXCEPTION; + /* need to populate the exception here */ + trace("bork\n"); + } zend_end_try(); + + zval_ptr_dtor(&zname); + } else { + trace("Don't know how to handle this invocation %08x\n", wFlags); + ret = E_UNEXPECTED; + } + + /* release arguments */ + for (i = 0; i < pdp->cArgs; i++) + zval_ptr_dtor(params[i]); + + if (params) + efree(params); + + /* return value */ + if (retval) { + if (pvarRes) { + VariantInit(pvarRes); + trace("setting up return value\n"); + php_com_variant_from_zval(pvarRes, retval, CP_ACP TSRMLS_CC); + } + zval_ptr_dtor(&retval); + } else if (pvarRes) { + VariantInit(pvarRes); + } + + trace("Invocation complete\n"); + + return ret; +} /* {{{ This object is used in conjunction with IActiveScriptParseProcedure to * allow scriptlets to be bound to events. IE uses this for declaring @@ -390,7 +572,7 @@ public: 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); + execute_code_fragment(m_frag, NULL, NULL TSRMLS_CC); } return S_OK; } @@ -496,10 +678,16 @@ static void free_code_fragment(code_frag *frag TSRMLS_DC) break; } - if (frag->opcodes) + if (frag->opcodes) { destroy_op_array(frag->opcodes TSRMLS_CC); - if (frag->functionname) + frag->opcodes = NULL; + } + + if (frag->functionname) { CoTaskMemFree(frag->functionname); + frag->functionname = NULL; + } + CoTaskMemFree(frag->code); CoTaskMemFree(frag); } @@ -533,23 +721,12 @@ trace("%08x: CLONED FRAG\n", newfrag); pv.value.str.val = newfrag->code; pv.value.str.len = newfrag->codelen; - newfrag->opcodes = compile_string(&pv, "fragment" TSRMLS_CC); + /* we defer compilation until we are ready to execute, + * as we need the host to AddNamedItem certain autoglobals + * BEFORE we compile */ + newfrag->opcodes = NULL; - if (newfrag->opcodes == NULL) { - free_code_fragment(newfrag TSRMLS_CC); -/* - 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, @@ -558,16 +735,26 @@ static int execute_code_fragment(code_frag *frag, 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) { + /* compiled cloned fragment, JIT */ + if (frag->persistent && frag->opcodes == NULL) { + zval pv; + pv.type = IS_STRING; + pv.value.str.val = frag->code; + pv.value.str.len = frag->codelen; + + frag->opcodes = compile_string(&pv, "fragment (JIT)" TSRMLS_CC); + + if (!frag->opcodes) { + trace("*** JIT compilation of cloned opcodes failed??"); + return 0; + } + } + + zend_try { trace("*** Executing code in thread %08x\n", tsrm_thread_id()); if (frag->functionname) { @@ -581,30 +768,29 @@ static int execute_code_fragment(code_frag *frag, 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; + zend_fcall_info_cache fci_cache; + zend_fcall_info fci; + + memset(&fci, 0, sizeof(fci)); + memset(&fci_cache, 0, sizeof(fci_cache)); + + fci.size = sizeof(fci); + fci.function_table = CG(function_table); + fci.retval_ptr_ptr = &retval_ptr; + fci.no_separation = 1; + + fci_cache.initialized = 1; + fci_cache.function_handler = (zend_function*)frag->opcodes; + frag->opcodes->type = ZEND_USER_FUNCTION; // mini hack + + zend_call_function(&fci, &fci_cache TSRMLS_CC); + } - } else { + } zend_catch { 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; + } zend_end_try(); if (frag->fragtype == FRAG_MAIN) frag->executed = 1; @@ -628,694 +814,231 @@ static void frag_dtor(void *pDest) free_code_fragment(frag TSRMLS_CC); } /* }}} */ - -/* 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); -} - -void activescript_run_ticks(int count) +void TPHPScriptingEngine::do_clone(TPHPScriptingEngine *src) { - MSG msg; TSRMLS_FETCH(); - TPHPScriptingEngine *engine; - - trace("ticking %d\n", count); - - engine = (TPHPScriptingEngine*)SG(server_context); - -/* PostThreadMessage(engine->m_enginethread, PHPSE_DUMMY_TICK, 0, 0); */ - - while(PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { - if (msg.hwnd) { - PeekMessage(&msg, NULL, 0, 0, PM_REMOVE); - TranslateMessage(&msg); - DispatchMessage(&msg); - } else { - break; - } - } + zend_hash_apply_with_argument(&src->m_frags, clone_frags, this TSRMLS_CC); } -/* Synchronize with the engine thread */ -HRESULT TPHPScriptingEngine::SendThreadMessage(LONG msg, WPARAM wparam, LPARAM lparam) -{ +#if ACTIVEPHP_HAS_OWN_THREAD +struct engine_startup { + HANDLE evt; HRESULT ret; + TPHPScriptingEngine *toclone; + TPHPScriptingEngine *localref; +}; - 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"); - TranslateMessage(&msg); - 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; -} +static WNDCLASS wc = {0}; -TPHPScriptingEngine::~TPHPScriptingEngine() +static LRESULT CALLBACK script_thread_msg_proc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { - trace("\n\n *** Engine Destructor Called\n\n"); + switch (message) { + case WM_ACTIVEPHP_SERIALIZE: + return marshal_stub(lparam); - if (m_scriptstate != SCRIPTSTATE_UNINITIALIZED && m_scriptstate != SCRIPTSTATE_CLOSED && m_enginethread) { - Close(); + case WM_DESTROY: + PostQuitMessage(0); + return 0; + + default: + return DefWindowProc(hwnd, message, wparam, lparam); } - - PostThreadMessage(m_enginethread, WM_QUIT, 0, 0); - WaitForSingleObject(m_engine_thread_handle, INFINITE); - CloseHandle(m_engine_thread_handle); - - 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) +static DWORD WINAPI script_thread(LPVOID param) { - 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"); - } + struct engine_startup *su = (struct engine_startup*)param; + TPHPScriptingEngine *engine; + IUnknown *punk = NULL; + MSG msg; - return ZEND_HASH_APPLY_KEEP; -} + trace("firing up engine thread/apartment\n"); -HRESULT TPHPScriptingEngine::engine_thread_handler(LONG msg, WPARAM wparam, LPARAM lParam, int *handled TSRMLS_DC) -{ - HRESULT ret = S_OK; + /* set up COM in this apartment */ + CoInitializeEx(0, COINIT_APARTMENTTHREADED); - 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; + /* create a window for message queueing */ + wc.lpfnWndProc = script_thread_msg_proc; + wc.lpszClassName = "ActivePHP Message Window"; + RegisterClass(&wc); - if (m_enginethread == 0) - return E_UNEXPECTED; + /* create the engine state */ + engine = new TPHPScriptingEngine; + + engine->m_enginethread = tsrm_thread_id(); + engine->m_basethread = 0; + engine->m_queue = CreateWindow(wc.lpszClassName, wc.lpszClassName, + 0, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + NULL, NULL, NULL, NULL); + + /* marshall it for another apartment */ + engine->QueryInterface(IID_IUnknown, (void**)&punk); + su->ret = GIT_put(punk, IID_IUnknown, &engine->m_gitcookie); + punk->Release(); + su->localref = engine; + + /* do we need to clone ? */ + if (su->toclone) { + engine->do_clone(su->toclone); + } - switch(msg) { - case PHPSE_ADD_TYPELIB: - { - struct php_active_script_add_tlb_info *info = (struct php_active_script_add_tlb_info*)lParam; - ITypeLib *TypeLib; + /* tell whoever spawned us that we're ready and waiting */ + SetEvent(su->evt); - if (SUCCEEDED(LoadRegTypeLib(*info->rguidTypeLib, (USHORT)info->dwMajor, - (USHORT)info->dwMinor, LANG_NEUTRAL, &TypeLib))) { - php_com_import_typelib(TypeLib, CONST_CS, CP_ACP 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); - -#if 0 - 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; -// trace("\n\n *** ticks func at %08x %08x ***\n\n\n", activescript_run_ticks, &activescript_run_ticks); -// php_add_tick_function(activescript_run_ticks); -#endif - - } - 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; - } -#if 0 - zend_hash_destroy(&m_frags); - php_request_shutdown(NULL); -#endif - - 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; - - if (info->pstrItemName != NULL) { - zval **tmp; - /* use this rather than php_OLECHAR_to_char because we want to avoid emalloc here */ - TWideString itemname(info->pstrItemName); - - /* 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.ansi_string(), - itemname.ansi_len() + 1, (void**)&tmp) == SUCCESS) { - if (Z_TYPE_PP(tmp) == IS_OBJECT) { - /* FIXME: if this causes an allocation (emalloc) and we are - * not in the engine thread, things could get ugly!!! */ - disp = php_com_wrapper_export(*tmp TSRMLS_CC); - } - } - - } else { - /* This object represents PHP global namespace */ - disp = (IDispatch*) new ScriptDispatch; - } + /* pump COM messages */ + while (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } - if (disp) { - ret = GIT_put(disp, IID_IDispatch, &info->dispatch); - disp->Release(); - trace("GET_DISPATCH: we put it in the GIT\n"); - } else { - ret = S_FALSE; - trace("GET_DISPATCH: FAILED to put it in the GIT\n"); - } - } - 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 = NULL; - HRESULT res; - - trace("ADD_NAMED_ITEM\n"); + trace("terminating engine thread\n"); + + CoUninitialize(); -#if ACTIVEPHP_THREADING_MODE == COINIT_MULTITHREADED - res = info->punk->QueryInterface(IID_IDispatch, (void**)&disp); -#else - res = GIT_get(info->marshal, IID_IDispatch, (void**)&disp); + return 0; +} #endif - if (SUCCEEDED(res) && disp) { - add_to_global_namespace(disp, info->dwFlags, name.ansi_string() TSRMLS_CC); - disp->Release(); - } else { - trace("Ouch: failed to get IDispatch for %s from GIT '%s'\n", name.ansi_string(), php_win_err(res)); - } - } - break; - case PHPSE_SET_SITE: - { - if (m_pass_eng) { - m_pass_eng->Release(); - m_pass_eng = NULL; - } +IUnknown *create_scripting_engine(TPHPScriptingEngine *tobecloned) +{ + IUnknown *punk = NULL; +#if ACTIVEPHP_HAS_OWN_THREAD + struct engine_startup su; + HANDLE hthr; + DWORD thid; - if (lParam) - GIT_get(lParam, IID_IActiveScriptSite, (void**)&m_pass_eng); + su.evt = CreateEvent(NULL, TRUE, FALSE, NULL); + su.toclone = tobecloned; - trace("%08x: site (engine-side) is now %08x (base=%08x)\n", this, m_pass_eng, m_pass); + hthr = CreateThread(NULL, 0, script_thread, &su, 0, &thid); + if (hthr) + CloseHandle(hthr); - } - 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 - formal_params(info->pstrFormalParams), - procedure_name(info->pstrProcedureName), - item_name(info->pstrItemName), - delimiter(info->pstrDelimiter); - - trace("%08x: ParseProc:\n state=%s\nparams=%s\nproc=%s\nitem=%s\n delim=%s\n line=%d\n", - this, scriptstate_to_string(m_scriptstate), - 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->m_frag = frag; - disp->m_procflags = info->dwFlags; - disp->m_engine = this; - frag->ptr = disp; - info->dispcookie = disp->m_gitcookie; - - } else { - ret = DISP_E_EXCEPTION; - } + WaitForSingleObject(su.evt, INFINITE); - } - break; + if (SUCCEEDED(su.ret)) { + punk = (IUnknown*)(void*)su.localref; + } - 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; + CloseHandle(su.evt); +#else + punk = (IActiveScript*)new TPHPScriptingEngine; +#endif + return punk; +} - if (info->dwFlags & SCRIPTTEXT_ISEXPRESSION) { - if (m_scriptstate == SCRIPTSTATE_INITIALIZED) { - /* not allowed to execute code in this state */ - ret = E_UNEXPECTED; - doexec = 0; - } - } +void TPHPScriptingEngine::setup_engine_state(void) +{ + TSRMLS_FETCH(); - if (doexec) { - /* execute the code as an expression */ - if (!execute_code_fragment(frag, info->pvarResult, info->pexcepinfo TSRMLS_CC)) - ret = DISP_E_EXCEPTION; - } + m_enginethread = tsrm_thread_id(); + trace("initializing zend engine on this thread\n"); + + SG(options) |= SAPI_OPTION_NO_CHDIR; + SG(headers_sent) = 1; + SG(request_info).no_headers = 1; - zend_hash_next_index_insert(&m_frags, &frag, sizeof(code_frag*), NULL); - } else { - ret = DISP_E_EXCEPTION; - } + SG(server_context) = this; - if (info->pvarResult) { - VariantInit(info->pvarResult); - } + /* 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); - } - break; - default: - trace("XXXXX: unhandled message type %08x\n", msg); - if (handled) - *handled = 0; - } - return ret; + php_request_startup(TSRMLS_C); + PG(during_request_startup) = 0; + zend_hash_init(&m_frags, 0, NULL, frag_dtor, 0); + + m_done_init = 1; + + trace("---- init done\n"); } -/* The PHP/Zend state actually lives in this thread */ -void TPHPScriptingEngine::engine_thread_func(void) +TPHPScriptingEngine::TPHPScriptingEngine() { TSRMLS_FETCH(); - int handled; - int terminated = 0; - MSG msg; + + /* CTOR */ - trace("%08x: engine thread started up!\n", this); + trace("*** NEW this=%08x\n", this); - CoInitializeEx(0, ACTIVEPHP_THREADING_MODE); - - zend_first_try { - m_tsrm_hack = tsrm_ls; - - SG(options) |= SAPI_OPTION_NO_CHDIR; - SG(headers_sent) = 1; - SG(request_info).no_headers = 1; - - 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); - - if (SUCCESS == php_request_startup(TSRMLS_C)) { - PG(during_request_startup) = 0; - if (FAILURE == zend_hash_init(&m_frags, 0, NULL, frag_dtor, 1)) { - trace("failed to init frags hash\n"); - } + m_scriptstate = SCRIPTSTATE_UNINITIALIZED; + m_pass = NULL; + m_in_main = 0; + m_done_init = 0; + m_stop_main = 0; + m_lambda_count = 0; + m_refcount = 1; + m_ids = 0; + TPHPClassFactory::AddToObjectCount(); - while(!terminated) { - DWORD result = MsgWaitForMultipleObjects(0, NULL, FALSE, 4000, QS_ALLINPUT); +#if ACTIVEPHP_HAS_OWN_THREAD + setup_engine_state(); +#endif - switch(result) { - case WAIT_OBJECT_0: - while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { +} - if (msg.message == WM_QUIT) { - terminated = 1; - } else if (msg.hwnd) { - TranslateMessage(&msg); - DispatchMessage(&msg); +TPHPScriptingEngine::~TPHPScriptingEngine() +{ + /* DTOR */ - } 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); - } + trace("\n\n *** Engine Destructor Called\n\n"); - } - break; - case WAIT_TIMEOUT: - trace("thread wait timed out\n"); - break; - default: - trace("some strange value\n"); - } - } + if (m_done_init && m_scriptstate != SCRIPTSTATE_CLOSED) { + Close(); + } -#if 0 - while(GetMessage(&msg, NULL, 0, 0)) { + while (m_ids--) { + CoTaskMemFree(m_names[m_ids]); + } - if (msg.message == WM_QUIT) - break; + TPHPClassFactory::RemoveFromObjectCount(); +} - 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); - } -#endif +/* 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; - trace("%08x: engine thread exiting!!!!!\n", this); - zend_hash_destroy(&m_frags); - trace("calling request shutdown\n"); - php_request_shutdown(NULL); - } else { - trace("request startup failed\n"); - } + if (frag->fragtype == FRAG_MAIN && !(frag->engine->m_in_main && frag->engine->m_stop_main)) + execute_code_fragment(frag, NULL, NULL TSRMLS_CC); - } zend_catch { - trace("ouch: bailed out\n"); - } zend_end_try(); - - m_enginethread = 0; - CoUninitialize(); + 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; +} + /* 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. - * - * TODO: evaluate if it is appropriate to register as an auto_global * */ static inline void make_auto_global(char *name, zval *val TSRMLS_DC) { int namelen = strlen(name); - +trace("make_auto_global %s\n", name); zend_register_auto_global(name, namelen, NULL TSRMLS_CC); zend_auto_global_disable_jit(name, namelen TSRMLS_CC); ZEND_SET_SYMBOL(&EG(symbol_table), name, val); @@ -1423,7 +1146,10 @@ trace("Add %s to global namespace\n", name); STDMETHODIMP_(DWORD) TPHPScriptingEngine::AddRef(void) { - return InterlockedIncrement(const_cast (&m_refcount)); + DWORD ret; + ret = InterlockedIncrement(const_cast (&m_refcount)); + trace("AddRef --> %d\n", ret); + return ret; } STDMETHODIMP_(DWORD) TPHPScriptingEngine::Release(void) @@ -1433,25 +1159,26 @@ STDMETHODIMP_(DWORD) TPHPScriptingEngine::Release(void) trace("%08x: Release: zero refcount, destroy the engine!\n", this); delete this; } + trace("Release --> %d\n", ret); return ret; } STDMETHODIMP TPHPScriptingEngine::QueryInterface(REFIID iid, void **ppvObject) { *ppvObject = NULL; - + if (IsEqualGUID(IID_IActiveScript, iid)) { *ppvObject = (IActiveScript*)this; } else if (IsEqualGUID(IID_IActiveScriptParse, iid)) { *ppvObject = (IActiveScriptParse*)this; } else if (IsEqualGUID(IID_IActiveScriptParseProcedure, iid)) { *ppvObject = (IActiveScriptParseProcedure*)this; -#if ACTIVEPHP_OBJECT_SAFETY } else if (IsEqualGUID(IID_IObjectSafety, iid)) { *ppvObject = (IObjectSafety*)this; -#endif } else if (IsEqualGUID(IID_IUnknown, iid)) { *ppvObject = this; + } else if (IsEqualGUID(IID_IDispatch, iid)) { + *ppvObject = (IDispatch*)this; } else { LPOLESTR guidw; StringFromCLSID(iid, &guidw); @@ -1465,7 +1192,7 @@ STDMETHODIMP TPHPScriptingEngine::QueryInterface(REFIID iid, void **ppvObject) AddRef(); return S_OK; } - + return E_NOINTERFACE; } @@ -1473,38 +1200,53 @@ STDMETHODIMP TPHPScriptingEngine::QueryInterface(REFIID iid, void **ppvObject) * It also defines the base thread. */ STDMETHODIMP TPHPScriptingEngine::SetScriptSite(IActiveScriptSite *pass) { + HRESULT ret = S_OK; TSRMLS_FETCH(); - tsrm_mutex_lock(m_mutex); + if (m_pass && pass) { + trace("SetScriptSite: we're already set\n"); + return E_FAIL; + } + + trace("%08x: SetScriptSite(%08x) -----> Base thread is %08x\n", this, pass, tsrm_thread_id()); - trace("%08x: -----> Base thread is %08x\n", this, tsrm_thread_id()); + if (pass) { + m_basethread = tsrm_thread_id(); +#if ACTIVEPHP_HAS_OWN_THREAD + HRESULT ret = GIT_put(pass, IID_IActiveScriptSite, &m_asscookie); +#endif + } if (m_pass) { +#if ACTIVEPHP_HAS_OWN_THREAD + trace("killing off ass cookie\n"); + GIT_revoke(m_asscookie, m_pass); +#endif 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=%08x\n What about named items??\n", + m_pass); } - m_pass = pass; - if (m_pass) { - m_pass->AddRef(); - - DWORD cookie; - if (SUCCEEDED(GIT_put(m_pass, IID_IActiveScriptSite, &cookie))) - SEND_THREAD_MESSAGE(this, PHPSE_SET_SITE, 0, cookie TSRMLS_CC); + if (pass) { + trace("taking a ref on pass\n"); + ret = pass->QueryInterface(IID_IActiveScriptSite, (void**)&m_pass); + trace("----> %s", php_win_err(ret)); - if (m_scriptstate == SCRIPTSTATE_UNINITIALIZED) - SEND_THREAD_MESSAGE(this, PHPSE_STATE_CHANGE, 0, SCRIPTSTATE_INITIALIZED TSRMLS_CC); + if (SUCCEEDED(ret)) { + +#if !ACTIVEPHP_HAS_OWN_THREAD + setup_engine_state(); +#endif + SetScriptState(SCRIPTSTATE_INITIALIZED); + } } - tsrm_mutex_unlock(m_mutex); - return S_OK; + return ret; } STDMETHODIMP TPHPScriptingEngine::GetScriptSite(REFIID riid, void **ppvObject) @@ -1512,87 +1254,148 @@ STDMETHODIMP TPHPScriptingEngine::GetScriptSite(REFIID riid, void **ppvObject) HRESULT ret = S_FALSE; trace("%08x: GetScriptSite()\n", this); - tsrm_mutex_lock(m_mutex); + if (m_pass) { + ASS_CALL(ret, QueryInterface, (riid, ppvObject)) + } - if (m_pass) - ret = m_pass->QueryInterface(riid, ppvObject); - - tsrm_mutex_unlock(m_mutex); return ret; } STDMETHODIMP TPHPScriptingEngine::SetScriptState(SCRIPTSTATE ss) { + HRESULT dummy = E_UNEXPECTED; + int start_running = 0; TSRMLS_FETCH(); - trace("%08x: SetScriptState(%s)\n", this, scriptstate_to_string(ss)); - return SEND_THREAD_MESSAGE(this, PHPSE_STATE_CHANGE, 0, ss TSRMLS_CC); + + if (tsrm_thread_id() != m_enginethread) + return marshal_call(this, APHP_SetScriptState, 1, ss); + + trace("%08x: SetScriptState(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; + + if (start_running) { + /* run "main()", as described in the docs */ + if (m_pass) { + ASS_CALL(dummy, 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) { + ASS_CALL(dummy, OnLeaveScript, ()); + } + } + + /* inform host/site of the change */ + if (m_pass) { +#if 0 + if (ss == SCRIPTSTATE_INITIALIZED) { + VARIANT varRes; + VariantInit(&varRes); + +// ASS_CALL(dummy, OnScriptTerminate, (&varRes, NULL)); + } +#endif + + ASS_CALL(dummy, OnStateChange, (m_scriptstate)); + } + + + return S_OK; } 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(); + + trace("Close() refcount = %d\n", m_refcount); - if (m_pass) { + if (m_scriptstate == SCRIPTSTATE_CLOSED) + return E_UNEXPECTED; + + if (m_done_init) { + + if (tsrm_thread_id() != m_enginethread) + return marshal_call(this, APHP_Close, 0); + + m_done_init = 0; + m_scriptstate = SCRIPTSTATE_CLOSED; + zend_hash_destroy(&m_frags); + php_request_shutdown(NULL); + m_enginethread = 0; + } + + + if (m_pass && tsrm_thread_id() == m_basethread) { + m_pass->OnStateChange(m_scriptstate); + } + + //GIT_revoke(m_asscookie, m_pass); + trace("%08x: release site \n", this); + + if (m_pass && tsrm_thread_id() == m_basethread) { 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. */ +/* 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. */ + STDMETHODIMP TPHPScriptingEngine::AddNamedItem(LPCOLESTR pstrName, DWORD dwFlags) { - struct php_active_script_add_named_item_info info; HRESULT res; + IUnknown *punk = NULL; + ITypeInfo *ti = NULL; TSRMLS_FETCH(); - info.pstrName = pstrName; - info.dwFlags = dwFlags; - - res = m_pass->GetItemInfo(pstrName, SCRIPTINFO_IUNKNOWN, &info.punk, NULL); + if (tsrm_thread_id() != m_enginethread) + return marshal_call(this, APHP_AddNamedItem, 2, pstrName, dwFlags); + + TWideString name(pstrName); + trace("AddNamedItem: %s (%08x) m_pass=%08x\n", name.ansi_string(), dwFlags, m_pass); + + res = m_pass->GetItemInfo(pstrName, SCRIPTINFO_IUNKNOWN, &punk, &ti); if (SUCCEEDED(res)) { -#if ACTIVEPHP_THREADING_MODE == COINIT_MULTITHREADED - /* strangely, the GIT doesn't want to give the engine thread the interface - * in this threading mode */ - SEND_THREAD_MESSAGE(this, PHPSE_ADD_NAMED_ITEM, 0, (LPARAM)&info TSRMLS_CC); -#else - IDispatch *disp; - res = info.punk->QueryInterface(IID_IDispatch, (void**)&disp); - + IDispatch *disp = NULL; + + trace("ADD_NAMED_ITEM\n"); + + res = punk->QueryInterface(IID_IDispatch, (void**)&disp); if (SUCCEEDED(res) && disp) { - if (SUCCEEDED(GIT_put(disp, IID_IDispatch, &info.marshal))) { - trace("put disp=%p into git with marshal ID of %x\n", disp, info.marshal); - SEND_THREAD_MESSAGE(this, PHPSE_ADD_NAMED_ITEM, 0, (LPARAM)&info TSRMLS_CC); - GIT_revoke(info.marshal, disp); - } + add_to_global_namespace(disp, dwFlags, name.ansi_string() TSRMLS_CC); disp->Release(); } else { - trace("failed to get IDispatch from punk %s", php_win_err(res)); + trace("Ouch: failed to get IDispatch for %s from GIT '%s'\n", name.ansi_string(), php_win_err(res)); } - info.punk->Release(); -#endif + } else { trace("failed to get named item, %s", php_win_err(res)); } - - return S_OK; + return res; } /* Bind to a type library */ @@ -1602,17 +1405,25 @@ STDMETHODIMP TPHPScriptingEngine::AddTypeLib( /* [in] */ DWORD dwMinor, /* [in] */ DWORD dwFlags) { - struct php_active_script_add_tlb_info info; + HRESULT ret; + ITypeLib *TypeLib; TSRMLS_FETCH(); - info.rguidTypeLib = &rguidTypeLib; - info.dwMajor = dwMajor; - info.dwMinor = dwMinor; - info.dwFlags = dwFlags; + if (tsrm_thread_id() != m_enginethread) + return marshal_call(this, APHP_AddTypeLib, 4, rguidTypeLib, dwMajor, dwMinor, dwFlags); - SEND_THREAD_MESSAGE(this, PHPSE_ADD_TYPELIB, 0, (LPARAM)&info TSRMLS_CC); + ENGINE_THREAD_ONLY(IActiveScript, AddTypeLib); + + trace("AddTypeLib\n"); + ret = LoadRegTypeLib(rguidTypeLib, (USHORT)dwMajor, (USHORT)dwMinor, LANG_NEUTRAL, &TypeLib); - return S_OK; + if (SUCCEEDED(ret)) { + php_com_import_typelib(TypeLib, CONST_CS, CP_ACP TSRMLS_CC); + TypeLib->Release(); + } + + + return ret; } /* Returns an object representing the PHP Scripting Engine. @@ -1624,34 +1435,40 @@ STDMETHODIMP TPHPScriptingEngine::GetScriptDispatch( /* [in] */ LPCOLESTR pstrItemName, /* [out] */ IDispatch **ppdisp) { + zend_function *func = NULL; 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 its 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. - * This appears to only happen when we set the threading to - * apartment. */ if (tsrm_thread_id() != m_enginethread) { - tsrm_ls = m_tsrm_hack; - trace("HEY: hacking thread safety!\n"); + return marshal_call(this, APHP_GetScriptDispatch, 2, pstrItemName, ppdisp); } + + *ppdisp = NULL; + + if (pstrItemName != NULL) { + zval **tmp; + TWideString itemname(pstrItemName); + trace("GetScriptDispatch %s\n", itemname.ansi_string()); + + /* 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.ansi_string(), + itemname.ansi_len() + 1, (void**)&tmp) == SUCCESS) { + if (Z_TYPE_PP(tmp) == IS_OBJECT) { + *ppdisp = php_com_wrapper_export(*tmp TSRMLS_CC); + } + } else if (zend_hash_find(EG(function_table), itemname.ansi_string(), + itemname.ansi_len() + 1, (void**)&func) == SUCCESS) { + trace("The host wants a function, but we don't have one\n"); + } - if (S_OK == engine_thread_handler(PHPSE_GET_DISPATCH, 0, (LPARAM)&info, NULL TSRMLS_CC)) { - GIT_get(info.dispatch, IID_IDispatch, (void**)ppdisp); + } else { + trace("GetScriptDispatch NULL\n"); + /* This object represents PHP global namespace */ + *ppdisp = (IDispatch*)this; } + if (*ppdisp) { return S_OK; @@ -1662,10 +1479,8 @@ STDMETHODIMP TPHPScriptingEngine::GetScriptDispatch( STDMETHODIMP TPHPScriptingEngine::GetCurrentScriptThreadID( /* [out] */ SCRIPTTHREADID *pstidThread) { -// tsrm_mutex_lock(m_mutex); trace("%08x: GetCurrentScriptThreadID()\n", this); - *pstidThread = m_enginethread; -// tsrm_mutex_unlock(m_mutex); + *pstidThread = GetCurrentThreadId();//m_enginethread; return S_OK; } @@ -1673,10 +1488,8 @@ 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; } @@ -1684,11 +1497,8 @@ 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; @@ -1702,7 +1512,6 @@ STDMETHODIMP TPHPScriptingEngine::GetScriptThreadState( } else if (stidThread == m_enginethread) { *pstsState = SCRIPTTHREADSTATE_NOTINSCRIPT; } -// tsrm_mutex_unlock(m_mutex); return S_OK; } @@ -1724,31 +1533,34 @@ STDMETHODIMP TPHPScriptingEngine::InterruptScriptThread( STDMETHODIMP TPHPScriptingEngine::Clone( /* [out] */ IActiveScript **ppscript) { - TPHPScriptingEngine *cloned = new TPHPScriptingEngine; - TSRMLS_FETCH(); + HRESULT ret = E_FAIL; + IUnknown *punk; - 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; - + trace("************* Clone this[%08x]\n", this); + + punk = create_scripting_engine(this); + + if (punk) { + ret = punk->QueryInterface(IID_IActiveScript, (void**)ppscript); + punk->Release(); } - - return E_FAIL; + + return ret; } STDMETHODIMP TPHPScriptingEngine::InitNew( void) { TSRMLS_FETCH(); - SEND_THREAD_MESSAGE(this, PHPSE_INIT_NEW, 0, 0 TSRMLS_CC); + trace("InitNew() this=%08x\n", this); + if (m_done_init) { + if (tsrm_thread_id() != m_enginethread) + return marshal_call(this, APHP_InitNew, 0); + + zend_hash_destroy(&m_frags); + php_request_shutdown(NULL); + setup_engine_state(); + } return S_OK; } @@ -1765,22 +1577,102 @@ STDMETHODIMP TPHPScriptingEngine::AddScriptlet( /* [out] */ BSTR *pbstrName, /* [out] */ EXCEPINFO *pexcepinfo) { - struct php_active_script_add_scriptlet_info info; + HRESULT ret; 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); + if (tsrm_thread_id() != m_enginethread) + return marshal_call(this, APHP_AddScriptlet, 11, pstrDefaultName, pstrCode, pstrItemName, pstrSubItemName, pstrEventName, pstrDelimiter, dwSourceContextCookie, ulStartingLineNumber, dwFlags, pbstrName, pexcepinfo); + + ENGINE_THREAD_ONLY(IActiveScriptParse, AddScriptlet); + +trace("AddScriptlet\n"); + + /* 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 + * */ + + TWideString + default_name(pstrDefaultName), + code(pstrCode), + item_name(pstrItemName), + sub_item_name(pstrSubItemName), + event_name(pstrEventName), + delimiter(pstrDelimiter); + + /* lets invent a function name for the scriptlet */ + char sname[256]; + + /* should check if the name is already used! */ + if (pstrDefaultName) + strcpy(sname, default_name.ansi_string()); + else { + sname[0] = 0; + strcat(sname, "__"); + if (pstrItemName) { + strcat(sname, item_name.ansi_string()); + strcat(sname, "_"); + } + if (pstrSubItemName) { + strcat(sname, sub_item_name.ansi_string()); + strcat(sname, "_"); + } + if (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(), + ulStartingLineNumber); + + + code_frag *frag = compile_code_fragment( + FRAG_SCRIPTLET, + sname, + pstrCode, + ulStartingLineNumber, + pexcepinfo, + this + TSRMLS_CC); + + if (frag) { + + frag->persistent = (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; + + *ppdisp = disp; + */ + ret = S_OK; + } else { + ret = DISP_E_EXCEPTION; + } + + *pbstrName = TWideString::bstr_from_ansi(sname); + + trace("%08x: done with scriptlet %s\n", this, sname); + + + return ret; } STDMETHODIMP TPHPScriptingEngine::ParseScriptText( @@ -1794,20 +1686,74 @@ STDMETHODIMP TPHPScriptingEngine::ParseScriptText( /* [out] */ VARIANT *pvarResult, /* [out] */ EXCEPINFO *pexcepinfo) { - struct php_active_script_parse_info info; +trace("ParseScriptText\n"); + int doexec; + TWideString + code(pstrCode), + item_name(pstrItemName), + delimiter(pstrDelimiter); + HRESULT ret; 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); + trace("pstrCode=%p pstrItemName=%p punkContext=%p pstrDelimiter=%p pvarResult=%p pexcepinfo=%p\n", + pstrCode, pstrItemName, punkContext, pstrDelimiter, pvarResult, pexcepinfo); + + if (tsrm_thread_id() != m_enginethread) { + return marshal_call(this, APHP_ParseScriptText, 9, + pstrCode, pstrItemName, punkContext, pstrDelimiter, + dwSourceContextCookie, ulStartingLineNumber, + dwFlags, pvarResult, pexcepinfo); + } + + 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.m_ansi_strlen, + code.safe_ansi_string(), item_name.safe_ansi_string(), delimiter.safe_ansi_string(), + ulStartingLineNumber); + + code_frag *frag = compile_code_fragment( + FRAG_MAIN, + dwFlags & SCRIPTTEXT_ISEXPRESSION ? FRAG_CREATE_FUNC : NULL, + pstrCode, + ulStartingLineNumber, + pexcepinfo, + this + TSRMLS_CC); + + doexec = (dwFlags & SCRIPTTEXT_ISEXPRESSION) || + m_scriptstate == SCRIPTSTATE_STARTED || + m_scriptstate == SCRIPTSTATE_CONNECTED || + m_scriptstate == SCRIPTSTATE_DISCONNECTED; + + if (frag) { + frag->persistent = (dwFlags & SCRIPTTEXT_ISPERSISTENT); + ret = S_OK; + + if (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, pvarResult, 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 (pvarResult) { + VariantInit(pvarResult); + } + + + return ret; } STDMETHODIMP TPHPScriptingEngine::ParseProcedureText( @@ -1822,37 +1768,69 @@ STDMETHODIMP TPHPScriptingEngine::ParseProcedureText( /* [in] */ DWORD dwFlags, /* [out] */ IDispatch **ppdisp) { - struct php_active_script_parse_proc_info info; +trace("ParseProcedureText\n"); + ENGINE_THREAD_ONLY(IActiveScriptParseProcedure, ParseProcedureText); + /* 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 */ + TWideString + formal_params(pstrFormalParams), + procedure_name(pstrProcedureName), + item_name(pstrItemName), + delimiter(pstrDelimiter); 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; + + trace("%08x: ParseProc:\n state=%s\nparams=%s\nproc=%s\nitem=%s\n delim=%s\n line=%d\n", + this, scriptstate_to_string(m_scriptstate), + formal_params.ansi_string(), procedure_name.ansi_string(), + item_name.safe_ansi_string(), delimiter.safe_ansi_string(), + ulStartingLineNumber); + + code_frag *frag = compile_code_fragment( + FRAG_PROCEDURE, + NULL, + pstrCode, + ulStartingLineNumber, + NULL, + this + TSRMLS_CC); + + if (frag) { + + frag->persistent = (dwFlags & SCRIPTTEXT_ISPERSISTENT); + zend_hash_next_index_insert(&m_frags, &frag, sizeof(code_frag*), NULL); + + ScriptProcedureDispatch *disp = new ScriptProcedureDispatch; + + disp->m_frag = frag; + disp->m_procflags = dwFlags; + disp->m_engine = this; + frag->ptr = disp; + + *ppdisp = (IDispatch*)disp; + } else { + ret = DISP_E_EXCEPTION; + } - ret = SEND_THREAD_MESSAGE(this, PHPSE_PARSE_PROC, 0, (LPARAM)&info TSRMLS_CC); - if (ret == S_OK) - ret = GIT_get(info.dispcookie, IID_IDispatch, (void**)ppdisp); - trace("ParseProc: ret=%08x disp=%08x\n", ret, *ppdisp); return ret; } -#if ACTIVEPHP_OBJECT_SAFETY STDMETHODIMP TPHPScriptingEngine::GetInterfaceSafetyOptions( /* [in] */ REFIID riid, // Interface that we want options for /* [out] */ DWORD *pdwSupportedOptions, // Options meaningful on this interface /* [out] */ DWORD *pdwEnabledOptions) // current option values on this interface { /* PHP isn't really safe for untrusted anything */ - *pdwSupportedOptions = 0; + trace("GetInterfaceSafetyOptions called\n"); + *pdwSupportedOptions = INTERFACESAFE_FOR_UNTRUSTED_DATA|INTERFACESAFE_FOR_UNTRUSTED_CALLER; *pdwEnabledOptions = 0; return S_OK; } @@ -1863,22 +1841,49 @@ STDMETHODIMP TPHPScriptingEngine::SetInterfaceSafetyOptions( /* [in] */ DWORD dwEnabledOptions) // New option values { /* PHP isn't really safe for untrusted anything */ + trace("SetInterfaceSafetyOptions mask=%08x enabled=%08x\n", dwOptionSetMask, dwEnabledOptions); if (dwEnabledOptions == 0) { return S_OK; } + trace("can't set those options; we're not safe enough\n"); return E_FAIL; } -#endif - -extern "C" -void activescript_error_func(int type, const char *error_msg, ...) + +#if 0 +STDMETHODIMP TPHPScriptingEngine::GetUnmarshalClass( + REFIID riid, void *pv, DWORD dwDestContext, void *pvDestContext, DWORD mshlflags, CLSID *pCid) { - TSRMLS_FETCH(); + return E_NOTIMPL; +} - TPHPScriptingEngine *engine = (TPHPScriptingEngine*)SG(server_context); +STDMETHODIMP TPHPScriptingEngine::GetMarshalSizeMax( + REFIID riid, void *pv, DWORD dwDestContext, void *pvDestContext, DWORD mshlflags, ULONG *pSize) +{ + return E_NOTIMPL; +} - +STDMETHODIMP TPHPScriptingEngine::MarshalInterface( + IStream *pStm, REFIID riid, void *pv, DWORD dwDestContext, void *pvDestContext, DWORD mshflags) +{ + return E_NOTIMPL; +} + +STDMETHODIMP TPHPScriptingEngine::UnmarshalInterface( + IStream *pStm, REFIID riid, void **ppv) +{ + return E_NOTIMPL; +} + +STDMETHODIMP TPHPScriptingEngine::ReleaseMarshalData(IStream *pStm) +{ + return E_NOTIMPL; +} + +STDMETHODIMP TPHPScriptingEngine::DisconnectObject(DWORD dwReserved) +{ + return E_NOTIMPL; } +#endif class TActiveScriptError: public IActiveScriptError @@ -1929,9 +1934,12 @@ public: /* [out] */ ULONG *pulLineNumber, /* [out] */ LONG *plCharacterPosition) { - *pdwSourceContext = 0; - *pulLineNumber = m_lineno; - *plCharacterPosition = 0; + if (pdwSourceContext) + *pdwSourceContext = 0; + if (pulLineNumber) + *pulLineNumber = m_lineno; + if (plCharacterPosition) + *plCharacterPosition = 0; return S_OK; } @@ -1971,10 +1979,19 @@ void activescript_error_handler(int type, const char *error_filename, 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. */ + /* ugly way to detect an exception for an eval'd fragment */ + if (type == E_ERROR && EG(exception) && strstr("Exception thrown without a stack frame", format)) { + zend_exception_error(EG(exception) TSRMLS_CC); + /* NOTREACHED -- recursive E_ERROR */ + } else { + buflen = vspprintf(&buf, PG(log_errors_max_len), format, args); + trace("%08x: PHP Error: %s\n", engine, buf); + } + + TActiveScriptError *eobj = new TActiveScriptError(error_filename, error_lineno, buf); + trace("raising error object!\n"); + if (engine->m_pass) + engine->m_pass->OnScriptError(eobj); switch(type) { case E_ERROR: @@ -1982,16 +1999,8 @@ void activescript_error_handler(int type, const char *error_filename, 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); - + zend_bailout(); break; } efree(buf);