|
|
/*
+----------------------------------------------------------------------+ | PHP Version 5 | +----------------------------------------------------------------------+ | Copyright (c) 1997-2009 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 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_01.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. | +----------------------------------------------------------------------+ | Author: Wez Furlong <wez@thebrainroom.com> | +----------------------------------------------------------------------+ */
/* $Id$ */
/* This module exports a PHP object as a COM object by wrapping it
* using IDispatchEx */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_com_dotnet.h"
#include "php_com_dotnet_internal.h"
typedef struct { /* This first part MUST match the declaration
* of interface IDispatchEx */ CONST_VTBL struct IDispatchExVtbl *lpVtbl;
/* now the PHP stuff */ DWORD engine_thread; /* for sanity checking */ zval *object; /* the object exported */ LONG refcount; /* COM reference count */
HashTable *dispid_to_name; /* keep track of dispid -> name mappings */ HashTable *name_to_dispid; /* keep track of name -> dispid mappings */
GUID sinkid; /* iid that we "implement" for event sinking */ int id;} php_dispatchex;
static int le_dispatch;
static void disp_destructor(php_dispatchex *disp);
static void dispatch_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC){ php_dispatchex *disp = (php_dispatchex *)rsrc->ptr; disp_destructor(disp);}
int php_com_wrapper_minit(INIT_FUNC_ARGS){ le_dispatch = zend_register_list_destructors_ex(dispatch_dtor, NULL, "com_dotnet_dispatch_wrapper", module_number); return le_dispatch;}
/* {{{ trace */static inline void trace(char *fmt, ...){ va_list ap; char buf[4096];
snprintf(buf, sizeof(buf), "T=%08x ", GetCurrentThreadId()); OutputDebugString(buf); va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap);
OutputDebugString(buf); va_end(ap);}/* }}} */
#ifdef ZTS
# define TSRMLS_FIXED() TSRMLS_FETCH();
#else
# define TSRMLS_FIXED()
#endif
#define FETCH_DISP(methname) \
TSRMLS_FIXED() \ php_dispatchex *disp = (php_dispatchex*)This; \ if (COMG(rshutdown_started)) { \ trace(" PHP Object:%p (name:unknown) %s\n", disp->object, methname); \ } else { \ trace(" PHP Object:%p (name:%s) %s\n", disp->object, Z_OBJCE_P(disp->object)->name, methname); \ } \ if (GetCurrentThreadId() != disp->engine_thread) { \ return RPC_E_WRONG_THREAD; \ }
static HRESULT STDMETHODCALLTYPE disp_queryinterface( IDispatchEx *This, /* [in] */ REFIID riid, /* [iid_is][out] */ void **ppvObject){ FETCH_DISP("QueryInterface");
if (IsEqualGUID(&IID_IUnknown, riid) || IsEqualGUID(&IID_IDispatch, riid) || IsEqualGUID(&IID_IDispatchEx, riid) || IsEqualGUID(&disp->sinkid, riid)) { *ppvObject = This; InterlockedIncrement(&disp->refcount); return S_OK; }
*ppvObject = NULL; return E_NOINTERFACE;} static ULONG STDMETHODCALLTYPE disp_addref(IDispatchEx *This){ FETCH_DISP("AddRef");
return InterlockedIncrement(&disp->refcount);} static ULONG STDMETHODCALLTYPE disp_release(IDispatchEx *This){ ULONG ret; FETCH_DISP("Release");
ret = InterlockedDecrement(&disp->refcount); trace("-- refcount now %d\n", ret); if (ret == 0) { /* destroy it */ if (disp->id) zend_list_delete(disp->id); } return ret;}
static HRESULT STDMETHODCALLTYPE disp_gettypeinfocount( IDispatchEx *This, /* [out] */ UINT *pctinfo){ FETCH_DISP("GetTypeInfoCount");
*pctinfo = 0; return S_OK;} static HRESULT STDMETHODCALLTYPE disp_gettypeinfo( IDispatchEx *This, /* [in] */ UINT iTInfo, /* [in] */ LCID lcid, /* [out] */ ITypeInfo **ppTInfo){ FETCH_DISP("GetTypeInfo"); *ppTInfo = NULL; return DISP_E_BADINDEX;}
static HRESULT STDMETHODCALLTYPE disp_getidsofnames( IDispatchEx *This, /* [in] */ REFIID riid, /* [size_is][in] */ LPOLESTR *rgszNames, /* [in] */ UINT cNames, /* [in] */ LCID lcid, /* [size_is][out] */ DISPID *rgDispId){ UINT i; HRESULT ret = S_OK; FETCH_DISP("GetIDsOfNames");
for (i = 0; i < cNames; i++) { char *name; unsigned int namelen; zval **tmp; name = php_com_olestring_to_string(rgszNames[i], &namelen, COMG(code_page) TSRMLS_CC); /* Lookup the name in the hash */ if (zend_hash_find(disp->name_to_dispid, name, namelen+1, (void**)&tmp) == FAILURE) { ret = DISP_E_UNKNOWNNAME; rgDispId[i] = 0; } else { rgDispId[i] = Z_LVAL_PP(tmp); }
efree(name);
} return ret;}
static HRESULT STDMETHODCALLTYPE disp_invoke( IDispatchEx *This, /* [in] */ DISPID dispIdMember, /* [in] */ REFIID riid, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [out][in] */ DISPPARAMS *pDispParams, /* [out] */ VARIANT *pVarResult, /* [out] */ EXCEPINFO *pExcepInfo, /* [out] */ UINT *puArgErr){ return This->lpVtbl->InvokeEx(This, dispIdMember, lcid, wFlags, pDispParams, pVarResult, pExcepInfo, NULL);}
static HRESULT STDMETHODCALLTYPE disp_getdispid( IDispatchEx *This, /* [in] */ BSTR bstrName, /* [in] */ DWORD grfdex, /* [out] */ DISPID *pid){ HRESULT ret = DISP_E_UNKNOWNNAME; char *name; unsigned int namelen; zval **tmp; FETCH_DISP("GetDispID");
name = php_com_olestring_to_string(bstrName, &namelen, COMG(code_page) TSRMLS_CC);
trace("Looking for %s, namelen=%d in %p\n", name, namelen, disp->name_to_dispid); /* Lookup the name in the hash */ if (zend_hash_find(disp->name_to_dispid, name, namelen+1, (void**)&tmp) == SUCCESS) { trace("found it\n"); *pid = Z_LVAL_PP(tmp); ret = S_OK; }
efree(name); return ret;}
static HRESULT STDMETHODCALLTYPE disp_invokeex( IDispatchEx *This, /* [in] */ DISPID id, /* [in] */ LCID lcid, /* [in] */ WORD wFlags, /* [in] */ DISPPARAMS *pdp, /* [out] */ VARIANT *pvarRes, /* [out] */ EXCEPINFO *pei, /* [unique][in] */ IServiceProvider *pspCaller){ zval **name; UINT i; zval *retval = NULL; zval ***params = NULL; HRESULT ret = DISP_E_MEMBERNOTFOUND; FETCH_DISP("InvokeEx");
if (SUCCESS == zend_hash_index_find(disp->dispid_to_name, id, (void**)&name)) { /* TODO: add support for overloaded objects */
trace("-- Invoke: %d %20s [%d] flags=%08x args=%d\n", id, Z_STRVAL_PP(name), Z_STRLEN_PP(name), wFlags, pdp->cArgs); /* 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, COMG(code_page) TSRMLS_CC); params[i] = (zval**)emalloc(sizeof(zval**)); *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 (wFlags & DISPATCH_PROPERTYGET) { retval = zend_read_property(Z_OBJCE_P(disp->object), disp->object, Z_STRVAL_PP(name), Z_STRLEN_PP(name)+1, 1 TSRMLS_CC); } else if (wFlags & DISPATCH_PROPERTYPUT) { zend_update_property(Z_OBJCE_P(disp->object), disp->object, Z_STRVAL_PP(name), Z_STRLEN_PP(name)+1, *params[0] TSRMLS_CC); } else if (wFlags & DISPATCH_METHOD) { zend_try { if (SUCCESS == call_user_function_ex(EG(function_table), &disp->object, *name, &retval, pdp->cArgs, params, 1, NULL TSRMLS_CC)) { ret = S_OK; trace("function called ok\n");
/* Copy any modified values to callers copy of variant*/ for (i = 0; i < pdp->cArgs; i++) { php_com_dotnet_object *obj = CDNO_FETCH(*params[i]); VARIANT *srcvar = &obj->v; VARIANT *dstvar = &pdp->rgvarg[ pdp->cArgs - 1 - i]; if ((V_VT(dstvar) & VT_BYREF) && obj->modified ) { trace("percolate modified value for arg %d VT=%08x\n", i, V_VT(dstvar)); php_com_copy_variant(dstvar, srcvar TSRMLS_CC); } } } else { trace("failed to call func\n"); ret = DISP_E_EXCEPTION; } } zend_catch { trace("something blew up\n"); ret = DISP_E_EXCEPTION; } zend_end_try(); } else { trace("Don't know how to handle this invocation %08x\n", wFlags); } /* release arguments */ if (params) { for (i = 0; i < pdp->cArgs; i++) { zval_ptr_dtor(params[i]); efree(params[i]); } efree(params); } /* return value */ if (retval) { if (pvarRes) { VariantInit(pvarRes); php_com_variant_from_zval(pvarRes, retval, COMG(code_page) TSRMLS_CC); } zval_ptr_dtor(&retval); } else if (pvarRes) { VariantInit(pvarRes); } } else { trace("InvokeEx: I don't support DISPID=%d\n", id); }
return ret;}
static HRESULT STDMETHODCALLTYPE disp_deletememberbyname( IDispatchEx *This, /* [in] */ BSTR bstrName, /* [in] */ DWORD grfdex){ FETCH_DISP("DeleteMemberByName");
/* TODO: unset */
return S_FALSE;}
static HRESULT STDMETHODCALLTYPE disp_deletememberbydispid( IDispatchEx *This, /* [in] */ DISPID id){ FETCH_DISP("DeleteMemberByDispID"); /* TODO: unset */ return S_FALSE;}
static HRESULT STDMETHODCALLTYPE disp_getmemberproperties( IDispatchEx *This, /* [in] */ DISPID id, /* [in] */ DWORD grfdexFetch, /* [out] */ DWORD *pgrfdex){ FETCH_DISP("GetMemberProperties");
return DISP_E_UNKNOWNNAME;}
static HRESULT STDMETHODCALLTYPE disp_getmembername( IDispatchEx *This, /* [in] */ DISPID id, /* [out] */ BSTR *pbstrName){ zval *name; FETCH_DISP("GetMemberName");
if (SUCCESS == zend_hash_index_find(disp->dispid_to_name, id, (void**)&name)) { OLECHAR *olestr = php_com_string_to_olestring(Z_STRVAL_P(name), Z_STRLEN_P(name), COMG(code_page) TSRMLS_CC); *pbstrName = SysAllocString(olestr); efree(olestr); return S_OK; } else { return DISP_E_UNKNOWNNAME; }}
static HRESULT STDMETHODCALLTYPE disp_getnextdispid( IDispatchEx *This, /* [in] */ DWORD grfdex, /* [in] */ DISPID id, /* [out] */ DISPID *pid){ ulong next = id+1; FETCH_DISP("GetNextDispID");
while(!zend_hash_index_exists(disp->dispid_to_name, next)) next++;
if (zend_hash_index_exists(disp->dispid_to_name, next)) { *pid = next; return S_OK; } return S_FALSE;}
static HRESULT STDMETHODCALLTYPE disp_getnamespaceparent( IDispatchEx *This, /* [out] */ IUnknown **ppunk){ FETCH_DISP("GetNameSpaceParent");
*ppunk = NULL; return E_NOTIMPL;} static struct IDispatchExVtbl php_dispatch_vtbl = { disp_queryinterface, disp_addref, disp_release, disp_gettypeinfocount, disp_gettypeinfo, disp_getidsofnames, disp_invoke, disp_getdispid, disp_invokeex, disp_deletememberbyname, disp_deletememberbydispid, disp_getmemberproperties, disp_getmembername, disp_getnextdispid, disp_getnamespaceparent};
/* enumerate functions and properties of the object and assign
* dispatch ids */static void generate_dispids(php_dispatchex *disp TSRMLS_DC){ HashPosition pos; char *name = NULL; zval *tmp; int namelen; int keytype; ulong pid;
if (disp->dispid_to_name == NULL) { ALLOC_HASHTABLE(disp->dispid_to_name); ALLOC_HASHTABLE(disp->name_to_dispid); zend_hash_init(disp->name_to_dispid, 0, NULL, ZVAL_PTR_DTOR, 0); zend_hash_init(disp->dispid_to_name, 0, NULL, ZVAL_PTR_DTOR, 0); }
/* properties */ if (Z_OBJPROP_P(disp->object)) { zend_hash_internal_pointer_reset_ex(Z_OBJPROP_P(disp->object), &pos); while (HASH_KEY_NON_EXISTANT != (keytype = zend_hash_get_current_key_ex(Z_OBJPROP_P(disp->object), &name, &namelen, &pid, 0, &pos))) { char namebuf[32]; if (keytype == HASH_KEY_IS_LONG) { snprintf(namebuf, sizeof(namebuf), "%d", pid); name = namebuf; namelen = strlen(namebuf)+1; }
zend_hash_move_forward_ex(Z_OBJPROP_P(disp->object), &pos);
/* Find the existing id */ if (zend_hash_find(disp->name_to_dispid, name, namelen, (void**)&tmp) == SUCCESS) continue;
/* add the mappings */ MAKE_STD_ZVAL(tmp); ZVAL_STRINGL(tmp, name, namelen-1, 1); pid = zend_hash_next_free_element(disp->dispid_to_name); zend_hash_index_update(disp->dispid_to_name, pid, (void*)&tmp, sizeof(zval *), NULL);
MAKE_STD_ZVAL(tmp); ZVAL_LONG(tmp, pid); zend_hash_update(disp->name_to_dispid, name, namelen, (void*)&tmp, sizeof(zval *), NULL); } } /* functions */ if (Z_OBJCE_P(disp->object)) { zend_hash_internal_pointer_reset_ex(&Z_OBJCE_P(disp->object)->function_table, &pos); while (HASH_KEY_NON_EXISTANT != (keytype = zend_hash_get_current_key_ex(&Z_OBJCE_P(disp->object)->function_table, &name, &namelen, &pid, 0, &pos))) {
char namebuf[32]; if (keytype == HASH_KEY_IS_LONG) { snprintf(namebuf, sizeof(namebuf), "%d", pid); name = namebuf; namelen = strlen(namebuf) + 1; }
zend_hash_move_forward_ex(Z_OBJPROP_P(disp->object), &pos);
/* Find the existing id */ if (zend_hash_find(disp->name_to_dispid, name, namelen, (void**)&tmp) == SUCCESS) continue;
/* add the mappings */ MAKE_STD_ZVAL(tmp); ZVAL_STRINGL(tmp, name, namelen-1, 1); pid = zend_hash_next_free_element(disp->dispid_to_name); zend_hash_index_update(disp->dispid_to_name, pid, (void*)&tmp, sizeof(zval *), NULL);
MAKE_STD_ZVAL(tmp); ZVAL_LONG(tmp, pid); zend_hash_update(disp->name_to_dispid, name, namelen, (void*)&tmp, sizeof(zval *), NULL); } }}
static php_dispatchex *disp_constructor(zval *object TSRMLS_DC){ php_dispatchex *disp = (php_dispatchex*)CoTaskMemAlloc(sizeof(php_dispatchex));
trace("constructing a COM wrapper for PHP object %p (%s)\n", object, Z_OBJCE_P(object)->name); if (disp == NULL) return NULL;
memset(disp, 0, sizeof(php_dispatchex));
disp->engine_thread = GetCurrentThreadId(); disp->lpVtbl = &php_dispatch_vtbl; disp->refcount = 1;
if (object) Z_ADDREF_P(object); disp->object = object;
disp->id = zend_list_insert(disp, le_dispatch); return disp;}
static void disp_destructor(php_dispatchex *disp){ TSRMLS_FETCH(); /* Object store not available during request shutdown */ if (COMG(rshutdown_started)) { trace("destroying COM wrapper for PHP object %p (name:unknown)\n", disp->object); } else { trace("destroying COM wrapper for PHP object %p (name:%s)\n", disp->object, Z_OBJCE_P(disp->object)->name); } disp->id = 0; if (disp->refcount > 0) CoDisconnectObject((IUnknown*)disp, 0);
zend_hash_destroy(disp->dispid_to_name); zend_hash_destroy(disp->name_to_dispid); FREE_HASHTABLE(disp->dispid_to_name); FREE_HASHTABLE(disp->name_to_dispid); if (disp->object) zval_ptr_dtor(&disp->object);
CoTaskMemFree(disp);}
PHPAPI IDispatch *php_com_wrapper_export_as_sink(zval *val, GUID *sinkid, HashTable *id_to_name TSRMLS_DC){ php_dispatchex *disp = disp_constructor(val TSRMLS_CC); HashPosition pos; char *name = NULL; zval *tmp, **ntmp; int namelen; int keytype; ulong pid;
disp->dispid_to_name = id_to_name;
memcpy(&disp->sinkid, sinkid, sizeof(disp->sinkid)); /* build up the reverse mapping */ ALLOC_HASHTABLE(disp->name_to_dispid); zend_hash_init(disp->name_to_dispid, 0, NULL, ZVAL_PTR_DTOR, 0); zend_hash_internal_pointer_reset_ex(id_to_name, &pos); while (HASH_KEY_NON_EXISTANT != (keytype = zend_hash_get_current_key_ex(id_to_name, &name, &namelen, &pid, 0, &pos))) {
if (keytype == HASH_KEY_IS_LONG) {
zend_hash_get_current_data_ex(id_to_name, (void**)&ntmp, &pos); MAKE_STD_ZVAL(tmp); ZVAL_LONG(tmp, pid); zend_hash_update(disp->name_to_dispid, Z_STRVAL_PP(ntmp), Z_STRLEN_PP(ntmp)+1, (void*)&tmp, sizeof(zval *), NULL); }
zend_hash_move_forward_ex(id_to_name, &pos); }
return (IDispatch*)disp;}
PHPAPI IDispatch *php_com_wrapper_export(zval *val TSRMLS_DC){ php_dispatchex *disp = NULL;
if (Z_TYPE_P(val) != IS_OBJECT) { return NULL; }
if (php_com_is_valid_object(val TSRMLS_CC)) { /* pass back its IDispatch directly */ php_com_dotnet_object *obj = CDNO_FETCH(val); if (obj == NULL) return NULL;
if (V_VT(&obj->v) == VT_DISPATCH && V_DISPATCH(&obj->v)) { IDispatch_AddRef(V_DISPATCH(&obj->v)); return V_DISPATCH(&obj->v); } return NULL; }
disp = disp_constructor(val TSRMLS_CC); generate_dispids(disp TSRMLS_CC);
return (IDispatch*)disp;}
|