diff options
author | Wez Furlong <wez@php.net> | 2004-01-07 21:00:07 +0000 |
---|---|---|
committer | Wez Furlong <wez@php.net> | 2004-01-07 21:00:07 +0000 |
commit | e10c206dac0fbd43e20f9ae9b704e76c5c564d2f (patch) | |
tree | 9675f1c8d2760ce9e7d3f6bf766d5ad434dc96c0 /ext/com_dotnet/com_wrapper.c | |
parent | 48b96c10d2c9efbe4ff11876c6cd9f9361073bc3 (diff) | |
download | php-git-e10c206dac0fbd43e20f9ae9b704e76c5c564d2f.tar.gz |
Port other major parts of PHP 4 COM extension into PHP 5 com_dotnet
extension.
This enables:
- iteration of SafeArray types via foreach()
- proxying of multi-dimensional SafeArray types so that multi-dimension
array accesses work (untested!)
- Fix COM exceptions, and expose them as their own class of exception
"com_exception"
- auto typelib file import (com.typelib_file ini option)
- event sinking
- wrapper to map PHP objects to COM
- fix mapping of variant values to PHP values
# Could someone please add com_saproxy.c and com_wrapper.c to the .dsp
# file?
Diffstat (limited to 'ext/com_dotnet/com_wrapper.c')
-rw-r--r-- | ext/com_dotnet/com_wrapper.c | 640 |
1 files changed, 640 insertions, 0 deletions
diff --git a/ext/com_dotnet/com_wrapper.c b/ext/com_dotnet/com_wrapper.c new file mode 100644 index 0000000000..349c937bf4 --- /dev/null +++ b/ext/com_dotnet/com_wrapper.c @@ -0,0 +1,640 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2003 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.0 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_0.txt. | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | 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 */ + + THREAD_T 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]; + + sprintf(buf, "T=%08x ", tsrm_thread_id()); + OutputDebugString(buf); + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + + OutputDebugString(buf); + + va_end(ap); +} +/* }}} */ + +#define FETCH_DISP(methname) \ + php_dispatchex *disp = (php_dispatchex*)This; \ + trace(" PHP:%s %s\n", Z_OBJCE_P(disp->object)->name, methname); \ + if (tsrm_thread_id() != disp->engine_thread) \ + return E_UNEXPECTED; + + +static HRESULT STDMETHODCALLTYPE disp_queryinterface( + IDispatchEx *This, + /* [in] */ REFIID riid, + /* [iid_is][out] */ void **ppvObject) +{ + TSRMLS_FETCH(); + + 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) +{ + TSRMLS_FETCH(); + + FETCH_DISP("AddRef"); + + return InterlockedIncrement(&disp->refcount); +} + +static ULONG STDMETHODCALLTYPE disp_release(IDispatchEx *This) +{ + ULONG ret; + TSRMLS_FETCH(); + + 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) +{ + TSRMLS_FETCH(); + + FETCH_DISP("GetTypeInfoCount"); + + *pctinfo = 0; + return S_OK; +} + +static HRESULT STDMETHODCALLTYPE disp_gettypeinfo( + IDispatchEx *This, + /* [in] */ UINT iTInfo, + /* [in] */ LCID lcid, + /* [out] */ ITypeInfo **ppTInfo) +{ + TSRMLS_FETCH(); + + 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; + TSRMLS_FETCH(); + + 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; + TSRMLS_FETCH(); + + FETCH_DISP("GetDispID"); + + name = php_com_olestring_to_string(bstrName, &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) == SUCCESS) { + *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; + TSRMLS_FETCH(); + + 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 flags=%08x args=%d\n", id, Z_STRVAL_PP(name), wFlags, pdp->cArgs); + + /* convert args into zvals. + * Args are in reverse order */ + params = (zval ***)emalloc(sizeof(zval **) * pdp->cArgs); + 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] = &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) { + if (SUCCESS == call_user_function_ex(EG(function_table), &disp->object, *name, + &retval, pdp->cArgs, params, 1, NULL TSRMLS_CC)) { + ret = S_OK; + } else { + ret = DISP_E_EXCEPTION; + } + } else { + trace("Don't know how to handle this invocation %08x\n", wFlags); + } + + /* release arguments */ + for (i = 0; i < pdp->cArgs; i++) + zval_ptr_dtor(params[i]); + efree(params); + + /* return value */ + if (retval) { + if (pvarRes) { + if (Z_TYPE_P(retval) == IS_OBJECT) { + /* export the object using a dispatch like ourselves */ + VariantInit(pvarRes); + V_VT(pvarRes) = VT_DISPATCH; + V_DISPATCH(pvarRes) = php_com_wrapper_export(retval TSRMLS_CC); + } else { + 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) +{ + TSRMLS_FETCH(); + + FETCH_DISP("DeleteMemberByName"); + + return S_FALSE; +} + +static HRESULT STDMETHODCALLTYPE disp_deletememberbydispid( + IDispatchEx *This, + /* [in] */ DISPID id) +{ + TSRMLS_FETCH(); + + FETCH_DISP("DeleteMemberByDispID"); + + return S_FALSE; +} + +static HRESULT STDMETHODCALLTYPE disp_getmemberproperties( + IDispatchEx *This, + /* [in] */ DISPID id, + /* [in] */ DWORD grfdexFetch, + /* [out] */ DWORD *pgrfdex) +{ + TSRMLS_FETCH(); + + FETCH_DISP("GetMemberProperties"); + + return DISP_E_UNKNOWNNAME; +} + +static HRESULT STDMETHODCALLTYPE disp_getmembername( + IDispatchEx *This, + /* [in] */ DISPID id, + /* [out] */ BSTR *pbstrName) +{ + zval *name; + TSRMLS_FETCH(); + + 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; + TSRMLS_FETCH(); + + 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) +{ + TSRMLS_FETCH(); + + 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) { + sprintf(namebuf, "%d", pid); + name = namebuf; + namelen = strlen(namebuf); + } + + 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+1, (void**)&tmp) == SUCCESS) + continue; + + /* add the mappings */ + MAKE_STD_ZVAL(tmp); + ZVAL_STRINGL(tmp, name, namelen, 1); + 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+1, (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) { + sprintf(namebuf, "%d", pid); + name = namebuf; + namelen = strlen(namebuf); + } + + 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+1, (void**)&tmp) == SUCCESS) + continue; + + /* add the mappings */ + MAKE_STD_ZVAL(tmp); + ZVAL_STRINGL(tmp, name, namelen, 1); + 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+1, (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 proxy\n"); + + if (disp == NULL) + return NULL; + + memset(disp, 0, sizeof(php_dispatchex)); + + disp->engine_thread = tsrm_thread_id(); + disp->lpVtbl = &php_dispatch_vtbl; + disp->refcount = 1; + + + if (object) + ZVAL_ADDREF(object); + disp->object = object; + + disp->id = zend_list_insert(disp, le_dispatch); + + return disp; +} + +static void disp_destructor(php_dispatchex *disp) +{ + TSRMLS_FETCH(); + + trace("destroying COM wrapper for PHP object %s\n", 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; +} + + |