#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 {
CONST_VTBL struct IDispatchExVtbl *lpVtbl;
DWORD engine_thread;
zval *object;
LONG refcount;
HashTable *dispid_to_name;
HashTable *name_to_dispid;
GUID sinkid;
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;
}
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,
REFIID riid,
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) {
if (disp->id)
zend_list_delete(disp->id);
}
return ret;
}
static HRESULT STDMETHODCALLTYPE disp_gettypeinfocount(
IDispatchEx *This,
UINT *pctinfo)
{
FETCH_DISP("GetTypeInfoCount");
*pctinfo = 0;
return S_OK;
}
static HRESULT STDMETHODCALLTYPE disp_gettypeinfo(
IDispatchEx *This,
UINT iTInfo,
LCID lcid,
ITypeInfo **ppTInfo)
{
FETCH_DISP("GetTypeInfo");
*ppTInfo = NULL;
return DISP_E_BADINDEX;
}
static HRESULT STDMETHODCALLTYPE disp_getidsofnames(
IDispatchEx *This,
REFIID riid,
LPOLESTR *rgszNames,
UINT cNames,
LCID lcid,
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);
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,
DISPID dispIdMember,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS *pDispParams,
VARIANT *pVarResult,
EXCEPINFO *pExcepInfo,
UINT *puArgErr)
{
return This->lpVtbl->InvokeEx(This, dispIdMember,
lcid, wFlags, pDispParams,
pVarResult, pExcepInfo, NULL);
}
static HRESULT STDMETHODCALLTYPE disp_getdispid(
IDispatchEx *This,
BSTR bstrName,
DWORD grfdex,
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);
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,
DISPID id,
LCID lcid,
WORD wFlags,
DISPPARAMS *pdp,
VARIANT *pvarRes,
EXCEPINFO *pei,
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)) {
trace("-- Invoke: %d %20s [%d] flags=%08x args=%d\n", id, Z_STRVAL_PP(name), Z_STRLEN_PP(name), wFlags, pdp->cArgs);
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");
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");
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);
}
if (params) {
for (i = 0; i < pdp->cArgs; i++) {
zval_ptr_dtor(params[i]);
efree(params[i]);
}
efree(params);
}
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,
BSTR bstrName,
DWORD grfdex)
{
FETCH_DISP("DeleteMemberByName");
return S_FALSE;
}
static HRESULT STDMETHODCALLTYPE disp_deletememberbydispid(
IDispatchEx *This,
DISPID id)
{
FETCH_DISP("DeleteMemberByDispID");
return S_FALSE;
}
static HRESULT STDMETHODCALLTYPE disp_getmemberproperties(
IDispatchEx *This,
DISPID id,
DWORD grfdexFetch,
DWORD *pgrfdex)
{
FETCH_DISP("GetMemberProperties");
return DISP_E_UNKNOWNNAME;
}
static HRESULT STDMETHODCALLTYPE disp_getmembername(
IDispatchEx *This,
DISPID id,
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,
DWORD grfdex,
DISPID id,
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,
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
};
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);
}
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);
if (zend_hash_find(disp->name_to_dispid, name, namelen, (void**)&tmp) == SUCCESS)
continue;
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);
}
}
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);
if (zend_hash_find(disp->name_to_dispid, name, namelen, (void**)&tmp) == SUCCESS)
continue;
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)
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();
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));
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)) {
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;
}