root/com_dotnet/com_wrapper.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. dispatch_dtor
  2. php_com_wrapper_minit
  3. trace
  4. disp_queryinterface
  5. disp_addref
  6. disp_release
  7. disp_gettypeinfocount
  8. disp_gettypeinfo
  9. disp_getidsofnames
  10. disp_invoke
  11. disp_getdispid
  12. disp_invokeex
  13. disp_deletememberbyname
  14. disp_deletememberbydispid
  15. disp_getmemberproperties
  16. disp_getmembername
  17. disp_getnextdispid
  18. disp_getnamespaceparent
  19. generate_dispids
  20. disp_constructor
  21. disp_destructor
  22. php_com_wrapper_export_as_sink
  23. php_com_wrapper_export

/*
   +----------------------------------------------------------------------+
   | PHP Version 5                                                        |
   +----------------------------------------------------------------------+
   | Copyright (c) 1997-2013 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 TSRMLS_DC);

static void dispatch_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC)
{
        php_dispatchex *disp = (php_dispatchex *)rsrc->ptr;
        disp_destructor(disp TSRMLS_CC);
}

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);
}
/* }}} */

#define FETCH_DISP(methname)                                                                                                                                                    \
        php_dispatchex *disp = (php_dispatchex*)This;                                                                                                           \
        TSRMLS_FETCH();                                                                                                                                                                         \
        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 TSRMLS_CC);
        
        return disp;
}

static void disp_destructor(php_dispatchex *disp TSRMLS_DC)
{       
        /* 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);
}

PHP_COM_DOTNET_API 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;
}

PHP_COM_DOTNET_API 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;
}



/* [<][>][^][v][top][bottom][index][help] */