From: Wez Furlong Date: Mon, 20 May 2002 01:35:29 +0000 (+0000) Subject: Implement ActiveScript interfaces. X-Git-Tag: RELEASE_0_4~10 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=e754361405395908abd687b1403215da2bd98b92;p=php 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. --- diff --git a/sapi/activescript/classfactory.cpp b/sapi/activescript/classfactory.cpp new file mode 100644 index 0000000000..3d4f256db6 --- /dev/null +++ b/sapi/activescript/classfactory.cpp @@ -0,0 +1,361 @@ +/* + +----------------------------------------------------------------------+ + | 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 | + +----------------------------------------------------------------------+ + */ +/* $Id$ */ + +/* IClassFactory Implementation, and DllXXX function implementation */ + +#define INITGUID +#define DEBUG_CLASS_FACTORY 0 + +#include +#include + +extern "C" { +HINSTANCE module_handle; +} + +#include +#include "TSRM.h" +#include "php4as_classfactory.h" +#include "php4as_scriptengine.h" + +volatile LONG TPHPClassFactory::factory_count = 0; +volatile LONG TPHPClassFactory::object_count = 0; + +/* {{{ Class Factory Implementation */ +TPHPClassFactory::TPHPClassFactory() +{ + m_refcount = 1; + InterlockedIncrement(&factory_count); +} + +TPHPClassFactory::~TPHPClassFactory() +{ + InterlockedDecrement(&factory_count); +} + +void TPHPClassFactory::AddToObjectCount(void) +{ + InterlockedIncrement(&object_count); +} + +void TPHPClassFactory::RemoveFromObjectCount(void) +{ + InterlockedDecrement(&object_count); +} + +STDMETHODIMP_(DWORD) TPHPClassFactory::AddRef(void) +{ + return InterlockedIncrement(&m_refcount); +} + +STDMETHODIMP_(DWORD) TPHPClassFactory::Release(void) +{ + DWORD ret = InterlockedDecrement(&m_refcount); + if (ret == 0) + delete this; + return ret; +} + +STDMETHODIMP TPHPClassFactory::QueryInterface(REFIID iid, void **ppvObject) +{ + *ppvObject = NULL; + + if (IsEqualGUID(IID_IClassFactory, iid)) { + *ppvObject = (IClassFactory*)this; + } else if (IsEqualGUID(IID_IUnknown, iid)) { + *ppvObject = this; + } + if (*ppvObject) { + AddRef(); + return S_OK; + } + + return E_NOINTERFACE; +} + +STDMETHODIMP TPHPClassFactory::LockServer(BOOL fLock) +{ + return E_NOTIMPL; +} + +STDMETHODIMP TPHPClassFactory::CreateInstance(IUnknown *pUnkOuter, REFIID iid, void **ppvObject) +{ + TPHPScriptingEngine *engine = new TPHPScriptingEngine; + + HRESULT ret = engine->QueryInterface(iid, ppvObject); + + engine->Release(); + + return ret; +} + +int TPHPClassFactory::CanUnload(void) +{ + return (object_count + factory_count) == 0 ? 1 : 0; +} + +/* }}} */ + +/* {{{ Registration structures */ +struct reg_entry { + HKEY root_key; + char *sub_key; + char *value_name; + char *data; +}; + +struct reg_class { + /* REFIID here causes compilation errors */ + const GUID *class_id; + char *class_name; + char *threading; + const struct reg_entry *reg; + const GUID **cats; +}; + +#define MAX_REG_SUBST 8 +struct reg_subst { + int count; + char *names[MAX_REG_SUBST]; + char *values[MAX_REG_SUBST]; +}; +/* }}} */ + +/* {{{ Registration information */ +/* This registers the sapi as a scripting engine that can be instantiated using it's progid */ +static const struct reg_entry engine_entries[] = { + { HKEY_CLASSES_ROOT, "CLSID\\[CLSID]", NULL, "[CLASSNAME]" }, + { HKEY_CLASSES_ROOT, "CLSID\\[CLSID]\\InprocServer32", NULL, "[MODULENAME]" }, + { HKEY_CLASSES_ROOT, "CLSID\\[CLSID]\\InprocServer32", "ThreadingModel", "[THREADING]" }, + { HKEY_CLASSES_ROOT, "CLSID\\[CLSID]\\OLEScript", NULL, NULL }, + { HKEY_CLASSES_ROOT, "CLSID\\[CLSID]\\ProgID", NULL, "ActivePHP" }, + { HKEY_CLASSES_ROOT, "ActivePHP", NULL, "ActivePHP" }, + { HKEY_CLASSES_ROOT, "ActivePHP\\CLSID", NULL, "[CLSID]"}, + { HKEY_CLASSES_ROOT, "ActivePHP\\OLEScript", NULL, NULL}, + + { 0, NULL, NULL, NULL } +}; + +static const GUID *script_engine_categories[] = { + &CATID_ActiveScript, + &CATID_ActiveScriptParse, + NULL +}; + +static const struct reg_class classes_to_register[] = { + { &CLSID_PHPActiveScriptEngine, "PHP Active Script Engine", "Both", engine_entries, script_engine_categories }, + { NULL, NULL, NULL, 0, NULL } +}; +/* }}} */ + +/* {{{ Registration code */ +static void substitute(struct reg_subst *subst, char *srcstring, char *deststring) +{ + int i; + char *srcpos = srcstring; + char *destpos = deststring; + + while(1) { + char *repstart = strchr(srcpos, '['); + + if (repstart == NULL) { + strcpy(destpos, srcpos); + break; + } + + /* copy everything up until that character to the dest */ + strncpy(destpos, srcpos, repstart - srcpos); + destpos += repstart - srcpos; + *destpos = 0; + + /* search for replacement strings in the table */ + for (i = 0; i < subst->count; i++) { + int len = strlen(subst->names[i]); + if (strncmp(subst->names[i], repstart + 1, len) == 0) { + /* append the replacement value to the buffer */ + strcpy(destpos, subst->values[i]); + destpos += strlen(subst->values[i]); + srcpos = repstart + len + 2; + + break; + } + } + } +#if DEBUG_CLASS_FACTORY + MessageBox(0, deststring, srcstring, MB_ICONHAND); +#endif +} + +static int reg_string(BOOL do_reg, struct reg_subst *subst, const struct reg_entry *entry) +{ + char subbuf[MAX_PATH], valuebuf[MAX_PATH], databuf[MAX_PATH]; + char *sub = NULL, *value = NULL, *data = NULL; + LRESULT res; + HKEY hkey; + DWORD disp; + + if (entry->sub_key) { + substitute(subst, entry->sub_key, subbuf); + sub = subbuf; + } + + if (entry->value_name) { + substitute(subst, entry->value_name, valuebuf); + value = valuebuf; + } + + if (entry->data) { + substitute(subst, entry->data, databuf); + data = databuf; + } + + if (do_reg) { + res = RegCreateKeyEx(entry->root_key, sub, 0, NULL, REG_OPTION_NON_VOLATILE, + KEY_WRITE, NULL, &hkey, &disp); + if (res == NOERROR) { + if (data) + res = RegSetValueEx(hkey, value, 0, REG_SZ, (LPBYTE)data, strlen(data) + 1); + RegCloseKey(hkey); + } + } else { + res = RegDeleteKey(entry->root_key, sub); + } + return res == NOERROR ? 1 : 0; +} + +static int perform_registration(BOOL do_reg) +{ + char module_name[MAX_PATH]; + char classid[40]; + int ret = 0; + int i, j; + struct reg_subst subst = {0}; + LPOLESTR classidw; + ICatRegister *catreg = NULL; + CATID cats[8]; + int ncats; + + CoInitialize(NULL); + CoCreateInstance(CLSID_StdComponentCategoriesMgr, NULL, CLSCTX_INPROC_SERVER, IID_ICatRegister, (LPVOID*)&catreg); + + GetModuleFileName(module_handle, module_name, sizeof(module_name)); + + subst.names[0] = "CLSID"; + subst.values[0] = classid; + + subst.names[1] = "MODULENAME"; + subst.values[1] = module_name; + + subst.names[2] = "CLASSNAME"; + subst.names[3] = "THREADING"; + + subst.count = 4; + + for (i = 0; classes_to_register[i].class_id; i++) { + StringFromCLSID(*classes_to_register[i].class_id, &classidw); + WideCharToMultiByte(CP_ACP, 0, classidw, -1, classid, sizeof(classid), NULL, NULL); + classid[39] = 0; + CoTaskMemFree(classidw); + + subst.values[2] = classes_to_register[i].class_name; + subst.values[3] = classes_to_register[i].threading; + + if (catreg && classes_to_register[i].cats) { + ncats = 0; + while(classes_to_register[i].cats[ncats]) { + CopyMemory(&cats[ncats], classes_to_register[i].cats[ncats], sizeof(CATID)); + ncats++; + } + } + + if (do_reg) { + for (j = 0; classes_to_register[i].reg[j].root_key; j++) { + if (!reg_string(do_reg, &subst, &classes_to_register[i].reg[j])) { + ret = 0; + break; + } + } + + if (catreg && ncats) { + catreg->RegisterClassImplCategories(*classes_to_register[i].class_id, ncats, cats); + } + + } else { + + if (catreg && ncats) { + catreg->UnRegisterClassImplCategories(*classes_to_register[i].class_id, ncats, cats); + } + + /* do it in reverse order, so count the number of entries first */ + for (j = 0; classes_to_register[i].reg[j].root_key; j++) + ; + while(j-- > 0) { + if (!reg_string(do_reg, &subst, &classes_to_register[i].reg[j])) { + ret = 0; + break; + } + } + } + } + + if (catreg) + catreg->Release(); + CoUninitialize(); + + return ret; +} +/* }}} */ + +/* {{{ Standard COM server DllXXX entry points */ + +STDAPI DllCanUnloadNow(void) +{ + if (TPHPClassFactory::CanUnload()) + return S_OK; + return S_FALSE; +} + +STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) +{ + HRESULT ret = E_FAIL; + + if (IsEqualCLSID(CLSID_PHPActiveScriptEngine, rclsid)) { + TPHPClassFactory *factory = new TPHPClassFactory(); + + if (factory) { + ret = factory->QueryInterface(riid, ppv); + factory->Release(); + } + } + return ret; +} + + + +STDAPI DllRegisterServer(void) +{ + return perform_registration(TRUE) ? S_OK : S_FALSE; +} + +STDAPI DllUnregisterServer(void) +{ + return perform_registration(FALSE) ? S_OK : S_FALSE; +} + +/* }}} */ diff --git a/sapi/activescript/php4activescript.c b/sapi/activescript/php4activescript.c new file mode 100644 index 0000000000..eed1d7e52e --- /dev/null +++ b/sapi/activescript/php4activescript.c @@ -0,0 +1,156 @@ +/* + +----------------------------------------------------------------------+ + | 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 | + +----------------------------------------------------------------------+ + */ +/* $Id$ */ + +#include "php.h" +#include "php_main.h" +#include "SAPI.h" +#include "php_globals.h" +#include "ext/standard/info.h" +#include "php_variables.h" +#include "php_ini.h" +#include "php4activescript.h" + +/* SAPI definitions and DllMain */ + +static int php_activescript_startup(sapi_module_struct *sapi_module) +{ + if (php_module_startup(sapi_module) == FAILURE || + zend_startup_module(&php_activescript_module) == FAILURE) { + return FAILURE; + } else { + return SUCCESS; + } +} + +static int sapi_activescript_ub_write(const char *str, uint str_length TSRMLS_DC) +{ + /* In theory, this is a blackhole. In practice, I wan't to see the output + * in the debugger! */ + + char buf[1024]; + uint l, a = str_length; + + while(a) { + l = a; + if (l > sizeof(buf) - 1) + l = sizeof(buf) - 1; + memcpy(buf, str, l); + buf[l] = 0; + OutputDebugString(buf); + a -= l; + } + + return str_length; +} + +static void sapi_activescript_register_server_variables(zval *track_vars_array TSRMLS_DC) +{ +} + +static char *sapi_activescript_read_cookies(TSRMLS_D) +{ + return NULL; +} + +static int sapi_activescript_header_handler(sapi_header_struct *sapi_header, sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + return SAPI_HEADER_ADD; +} + +static int sapi_activescript_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + return SAPI_HEADER_SENT_SUCCESSFULLY; +} + +zend_module_entry php_activescript_module = { + STANDARD_MODULE_HEADER, + "ActiveScript", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + STANDARD_MODULE_PROPERTIES +}; + + +sapi_module_struct activescript_sapi_module = { + "activescript", /* name */ + "Active Script", /* pretty name */ + + php_activescript_startup, /* startup */ + php_module_shutdown_wrapper, /* shutdown */ + + NULL, /* activate */ + NULL, /* deactivate */ + + sapi_activescript_ub_write, /* unbuffered write */ + NULL, /* flush */ + NULL, /* get uid */ + NULL, /* getenv */ + + zend_error, /* error handler */ + + sapi_activescript_header_handler, /* header handler */ + sapi_activescript_send_headers, /* send headers handler */ + NULL, /* send header handler */ + + NULL, /* read POST data */ + sapi_activescript_read_cookies, /* read Cookies */ + + sapi_activescript_register_server_variables, /* register server variables */ + NULL, /* Log message */ + + NULL, /* Block interruptions */ + NULL, /* Unblock interruptions */ + + STANDARD_SAPI_MODULE_PROPERTIES +}; + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) +{ + switch (fdwReason) { + case DLL_PROCESS_ATTACH: + module_handle = hinstDLL; + + tsrm_startup(128, 1, TSRM_ERROR_LEVEL_CORE, "C:\\TSRM.log"); + + sapi_startup(&activescript_sapi_module); + if (activescript_sapi_module.startup) { + activescript_sapi_module.startup(&sapi_module); + } + break; + case DLL_THREAD_ATTACH: + break; + case DLL_THREAD_DETACH: + ts_free_thread(); + break; + case DLL_PROCESS_DETACH: + if (activescript_sapi_module.shutdown) { + activescript_sapi_module.shutdown(&sapi_module); + } + tsrm_shutdown(); + break; + } + return TRUE; +} + + diff --git a/sapi/activescript/php4activescript.def b/sapi/activescript/php4activescript.def new file mode 100644 index 0000000000..1f8e945838 --- /dev/null +++ b/sapi/activescript/php4activescript.def @@ -0,0 +1,5 @@ +EXPORTS +DllCanUnloadNow @1 PRIVATE +DllGetClassObject @2 PRIVATE +DllRegisterServer @3 PRIVATE +DllUnregisterServer @4 PRIVATE diff --git a/sapi/activescript/php4activescript.dsp b/sapi/activescript/php4activescript.dsp new file mode 100644 index 0000000000..cbfafa1272 --- /dev/null +++ b/sapi/activescript/php4activescript.dsp @@ -0,0 +1,185 @@ +# Microsoft Developer Studio Project File - Name="php4activescript" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=php4activescript - Win32 Debug_TS +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "php4activescript.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "php4activescript.mak" CFG="php4activescript - Win32 Debug_TS" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "php4activescript - Win32 Debug_TS" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "php4activescript - Win32 Release_TS" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "php4activescript - Win32 Release_TS_inline" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "php4activescript - Win32 Release_TSDbg" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "php4activescript - Win32 Debug_TS" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug_TS" +# PROP BASE Intermediate_Dir "Debug_TS" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\..\Debug_TS" +# PROP Intermediate_Dir "Debug_TS" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PHP4ISAPI_EXPORTS" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /Ze /W3 /Gm /GX /ZI /Od /Ob0 /I "..\..\win32" /I "..\..\Zend" /I "..\.." /I "..\..\main" /I "..\..\TSRM" /D "_DEBUG" /D "COMPILE_LIBZEND" /D ZEND_DEBUG=1 /D "_WINDOWS" /D "_USRDLL" /D "PHP4ISAPI_EXPORTS" /D "MSVC5" /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D "WIN32" /D "_MBCS" /FR /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x40d /d "_DEBUG" +# ADD RSC /l 0x40d /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 wsock32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib php4ts_debug.lib /nologo /version:4.0 /dll /debug /machine:I386 /nodefaultlib:"libcmt" /pdbtype:sept /libpath:"..\..\Debug_TS" + +!ELSEIF "$(CFG)" == "php4activescript - Win32 Release_TS" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release_TS" +# PROP BASE Intermediate_Dir "Release_TS" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\..\Release_TS" +# PROP Intermediate_Dir "Release_TS" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PHP4ISAPI_EXPORTS" /YX /FD /c +# ADD CPP /nologo /MD /W3 /GX /O2 /I "...\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\.." /I "..\..\main" /I "..\..\TSRM" /D "NDEBUG" /D ZEND_DEBUG=0 /D "_WINDOWS" /D "_USRDLL" /D "PHP4ISAPI_EXPORTS" /D "MSVC5" /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D "WIN32" /D "_MBCS" /FR /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x40d /d "NDEBUG" +# ADD RSC /l 0x40d /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib wsock32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib php4ts.lib /nologo /version:4.0 /dll /machine:I386 /libpath:"..\..\Release_TS" + +!ELSEIF "$(CFG)" == "php4activescript - Win32 Release_TS_inline" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "php4activescript___Win32_Release_TS_inline" +# PROP BASE Intermediate_Dir "php4activescript___Win32_Release_TS_inline" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\..\Release_TS_inline" +# PROP Intermediate_Dir "Release_TS_inline" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MD /W3 /GX /O2 /I "...\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\.." /D "NDEBUG" /D "_WINDOWS" /D "_USRDLL" /D "PHP4ISAPI_EXPORTS" /D "MSVC5" /D "ZTS" /D "WIN32" /D "_MBCS" /D ZEND_DEBUG=0 /FR /YX /FD /c +# ADD CPP /nologo /MD /W3 /GX /O2 /I "...\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\.." /I "..\..\main" /I "..\..\TSRM" /D "NDEBUG" /D ZEND_DEBUG=0 /D "ZEND_WIN32_FORCE_INLINE" /D "_WINDOWS" /D "_USRDLL" /D "PHP4ISAPI_EXPORTS" /D "MSVC5" /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D "WIN32" /D "_MBCS" /FR /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x40d /d "NDEBUG" +# ADD RSC /l 0x40d /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib wsock32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib php4ts.lib /nologo /dll /machine:I386 /libpath:"..\..\Release_TS" +# ADD LINK32 kernel32.lib user32.lib gdi32.lib wsock32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib php4ts.lib /nologo /version:4.0 /dll /machine:I386 /libpath:"..\..\Release_TS_inline" + +!ELSEIF "$(CFG)" == "php4activescript - Win32 Release_TSDbg" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "php4activescript___Win32_Release_TSDbg" +# PROP BASE Intermediate_Dir "php4activescript___Win32_Release_TSDbg" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\..\Release_TSDbg" +# PROP Intermediate_Dir "Release_TSDbg" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MD /W3 /GX /O2 /I "...\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\.." /I "..\..\main" /I "..\..\TSRM" /D "NDEBUG" /D ZEND_DEBUG=0 /D "_WINDOWS" /D "_USRDLL" /D "PHP4ISAPI_EXPORTS" /D "MSVC5" /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D "WIN32" /D "_MBCS" /FR /YX /FD /c +# ADD CPP /nologo /MD /W3 /GX /Zi /Od /I "...\..\include" /I "..\..\win32" /I "..\..\Zend" /I "..\.." /I "..\..\main" /I "..\..\TSRM" /D "NDEBUG" /D ZEND_DEBUG=0 /D "_WINDOWS" /D "_USRDLL" /D "PHP4ISAPI_EXPORTS" /D "MSVC5" /D "ZTS" /D "ZEND_WIN32" /D "PHP_WIN32" /D "WIN32" /D "_MBCS" /FR /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x40d /d "NDEBUG" +# ADD RSC /l 0x40d /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib wsock32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib php4ts.lib /nologo /version:4.0 /dll /machine:I386 /libpath:"..\..\Release_TS" +# ADD LINK32 kernel32.lib user32.lib gdi32.lib wsock32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib php4ts.lib /nologo /version:4.0 /dll /debug /machine:I386 /libpath:"..\..\Release_TSDbg" + +!ENDIF + +# Begin Target + +# Name "php4activescript - Win32 Debug_TS" +# Name "php4activescript - Win32 Release_TS" +# Name "php4activescript - Win32 Release_TS_inline" +# Name "php4activescript - Win32 Release_TSDbg" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\classfactory.cpp +# End Source File +# Begin Source File + +SOURCE=.\php4activescript.c +# End Source File +# Begin Source File + +SOURCE=.\php4activescript.def +# End Source File +# Begin Source File + +SOURCE=.\scriptengine.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\php4activescript.h +# End Source File +# Begin Source File + +SOURCE=.\php4as_classfactory.h +# End Source File +# Begin Source File + +SOURCE=.\php4as_scriptengine.h +# End Source File +# End Group +# End Target +# End Project diff --git a/sapi/activescript/php4activescript.h b/sapi/activescript/php4activescript.h new file mode 100644 index 0000000000..423958fba0 --- /dev/null +++ b/sapi/activescript/php4activescript.h @@ -0,0 +1,26 @@ +/* + +----------------------------------------------------------------------+ + | 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 | + +----------------------------------------------------------------------+ + */ +/* $Id$ */ + +extern zend_module_entry php_activescript_module; +extern sapi_module_struct activescript_sapi_module; +extern HINSTANCE module_handle; +extern void activescript_error_func(int type, const char *error_msg, ...); +extern void activescript_error_handler(int type, const char *error_filename, + const uint error_lineno, const char *format, va_list args); + diff --git a/sapi/activescript/php4as_classfactory.h b/sapi/activescript/php4as_classfactory.h new file mode 100644 index 0000000000..bc4d530c03 --- /dev/null +++ b/sapi/activescript/php4as_classfactory.h @@ -0,0 +1,52 @@ +/* + +----------------------------------------------------------------------+ + | 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 | + +----------------------------------------------------------------------+ + */ +/* $Id$ */ + +/* IClassFactory Implementation */ + +#include + +// {A0AD8E7A-95EC-4819-986F-78D93895F2AE} +DEFINE_GUID(CLSID_PHPActiveScriptEngine, +0xa0ad8e7a, 0x95ec, 0x4819, 0x98, 0x6f, 0x78, 0xd9, 0x38, 0x95, 0xf2, 0xae); + +class TPHPClassFactory: + public IClassFactory +{ +protected: + volatile LONG m_refcount; + + static volatile LONG factory_count; + static volatile LONG object_count; + +public: /* IUnknown */ + STDMETHODIMP QueryInterface(REFIID iid, void **ppvObject); + STDMETHODIMP_(DWORD) AddRef(void); + STDMETHODIMP_(DWORD) Release(void); +public: /* IClassFactory */ + STDMETHODIMP CreateInstance(IUnknown *pUnkOuter, REFIID iid, void **ppvObject); + STDMETHODIMP LockServer(BOOL fLock); + + TPHPClassFactory(); + ~TPHPClassFactory(); + + static void AddToObjectCount(void); + static void RemoveFromObjectCount(void); + static int CanUnload(void); +}; + diff --git a/sapi/activescript/php4as_scriptengine.h b/sapi/activescript/php4as_scriptengine.h new file mode 100644 index 0000000000..1d2314b5e2 --- /dev/null +++ b/sapi/activescript/php4as_scriptengine.h @@ -0,0 +1,234 @@ +/* + +----------------------------------------------------------------------+ + | 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 | + +----------------------------------------------------------------------+ + */ +/* $Id$ */ + +#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 +}; + +struct php_active_script_get_dispatch_info { + LPCOLESTR pstrItemName; + LPSTREAM dispatch; +}; + +struct php_active_script_add_named_item_info { + LPCOLESTR pstrName; + DWORD dwFlags; + IUnknown *punk; + ITypeInfo *ptyp; + IDispatch *pdisp; + LPSTREAM marshal; +}; + +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; +}; + +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; +}; + +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; + /* [out] */ IDispatch **ppdisp; +}; + +struct php_active_script_add_tlb_info { + /* [in] */ const GUID * rguidTypeLib; + /* [in] */ DWORD dwMajor; + /* [in] */ DWORD dwMinor; + /* [in] */ DWORD dwFlags; +}; + +class TPHPScriptingEngine: + public IActiveScript, + public IActiveScriptParse, + public IActiveScriptParseProcedure +{ +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; + + 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); + +public: /* IUnknown */ + STDMETHODIMP QueryInterface(REFIID iid, void **ppvObject); + STDMETHODIMP_(DWORD) AddRef(void); + STDMETHODIMP_(DWORD) Release(void); +public: /* IActiveScript */ + STDMETHODIMP SetScriptSite( + /* [in] */ IActiveScriptSite *pass); + + STDMETHODIMP GetScriptSite( + /* [in] */ REFIID riid, + /* [iid_is][out] */ void **ppvObject); + + STDMETHODIMP SetScriptState( + /* [in] */ SCRIPTSTATE ss); + + STDMETHODIMP GetScriptState( + /* [out] */ SCRIPTSTATE *pssState); + + STDMETHODIMP Close( void); + + STDMETHODIMP AddNamedItem( + /* [in] */ LPCOLESTR pstrName, + /* [in] */ DWORD dwFlags); + + STDMETHODIMP AddTypeLib( + /* [in] */ REFGUID rguidTypeLib, + /* [in] */ DWORD dwMajor, + /* [in] */ DWORD dwMinor, + /* [in] */ DWORD dwFlags); + + STDMETHODIMP GetScriptDispatch( + /* [in] */ LPCOLESTR pstrItemName, + /* [out] */ IDispatch **ppdisp); + + STDMETHODIMP GetCurrentScriptThreadID( + /* [out] */ SCRIPTTHREADID *pstidThread); + + STDMETHODIMP GetScriptThreadID( + /* [in] */ DWORD dwWin32ThreadId, + /* [out] */ SCRIPTTHREADID *pstidThread); + + STDMETHODIMP GetScriptThreadState( + /* [in] */ SCRIPTTHREADID stidThread, + /* [out] */ SCRIPTTHREADSTATE *pstsState); + + STDMETHODIMP InterruptScriptThread( + /* [in] */ SCRIPTTHREADID stidThread, + /* [in] */ const EXCEPINFO *pexcepinfo, + /* [in] */ DWORD dwFlags); + + STDMETHODIMP Clone( + /* [out] */ IActiveScript **ppscript); + +public: /* IActiveScriptParse */ + STDMETHODIMP InitNew( void); + + STDMETHODIMP 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); + + STDMETHODIMP 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); +public: /* IActiveScriptParseProcedure */ + STDMETHODIMP 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); +public: + TPHPScriptingEngine(); + ~TPHPScriptingEngine(); + +}; + 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 | + +----------------------------------------------------------------------+ + */ +/* $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 + +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 ""; + 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); +} +