1: /*
2: +----------------------------------------------------------------------+
3: | PHP Version 5 |
4: +----------------------------------------------------------------------+
5: | Copyright (c) 1997-2012 The PHP Group |
6: +----------------------------------------------------------------------+
7: | This source file is subject to version 3.01 of the PHP license, |
8: | that is bundled with this package in the file LICENSE, and is |
9: | available through the world-wide-web at the following url: |
10: | http://www.php.net/license/3_01.txt |
11: | If you did not receive a copy of the PHP license and are unable to |
12: | obtain it through the world-wide-web, please send a note to |
13: | license@php.net so we can mail you a copy immediately. |
14: +----------------------------------------------------------------------+
15: | Author: Wez Furlong <wez@thebrainroom.com> |
16: +----------------------------------------------------------------------+
17: */
18:
19: /* $Id: com_wrapper.c,v 1.1.1.1 2012/02/21 23:47:53 misho Exp $ */
20:
21: /* This module exports a PHP object as a COM object by wrapping it
22: * using IDispatchEx */
23:
24: #ifdef HAVE_CONFIG_H
25: #include "config.h"
26: #endif
27:
28: #include "php.h"
29: #include "php_ini.h"
30: #include "ext/standard/info.h"
31: #include "php_com_dotnet.h"
32: #include "php_com_dotnet_internal.h"
33:
34: typedef struct {
35: /* This first part MUST match the declaration
36: * of interface IDispatchEx */
37: CONST_VTBL struct IDispatchExVtbl *lpVtbl;
38:
39: /* now the PHP stuff */
40:
41: DWORD engine_thread; /* for sanity checking */
42: zval *object; /* the object exported */
43: LONG refcount; /* COM reference count */
44:
45: HashTable *dispid_to_name; /* keep track of dispid -> name mappings */
46: HashTable *name_to_dispid; /* keep track of name -> dispid mappings */
47:
48: GUID sinkid; /* iid that we "implement" for event sinking */
49:
50: int id;
51: } php_dispatchex;
52:
53: static int le_dispatch;
54:
55: static void disp_destructor(php_dispatchex *disp);
56:
57: static void dispatch_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC)
58: {
59: php_dispatchex *disp = (php_dispatchex *)rsrc->ptr;
60: disp_destructor(disp);
61: }
62:
63: int php_com_wrapper_minit(INIT_FUNC_ARGS)
64: {
65: le_dispatch = zend_register_list_destructors_ex(dispatch_dtor,
66: NULL, "com_dotnet_dispatch_wrapper", module_number);
67: return le_dispatch;
68: }
69:
70:
71: /* {{{ trace */
72: static inline void trace(char *fmt, ...)
73: {
74: va_list ap;
75: char buf[4096];
76:
77: snprintf(buf, sizeof(buf), "T=%08x ", GetCurrentThreadId());
78: OutputDebugString(buf);
79:
80: va_start(ap, fmt);
81: vsnprintf(buf, sizeof(buf), fmt, ap);
82:
83: OutputDebugString(buf);
84:
85: va_end(ap);
86: }
87: /* }}} */
88:
89: #ifdef ZTS
90: # define TSRMLS_FIXED() TSRMLS_FETCH();
91: #else
92: # define TSRMLS_FIXED()
93: #endif
94:
95: #define FETCH_DISP(methname) \
96: TSRMLS_FIXED() \
97: php_dispatchex *disp = (php_dispatchex*)This; \
98: if (COMG(rshutdown_started)) { \
99: trace(" PHP Object:%p (name:unknown) %s\n", disp->object, methname); \
100: } else { \
101: trace(" PHP Object:%p (name:%s) %s\n", disp->object, Z_OBJCE_P(disp->object)->name, methname); \
102: } \
103: if (GetCurrentThreadId() != disp->engine_thread) { \
104: return RPC_E_WRONG_THREAD; \
105: }
106:
107: static HRESULT STDMETHODCALLTYPE disp_queryinterface(
108: IDispatchEx *This,
109: /* [in] */ REFIID riid,
110: /* [iid_is][out] */ void **ppvObject)
111: {
112: FETCH_DISP("QueryInterface");
113:
114: if (IsEqualGUID(&IID_IUnknown, riid) ||
115: IsEqualGUID(&IID_IDispatch, riid) ||
116: IsEqualGUID(&IID_IDispatchEx, riid) ||
117: IsEqualGUID(&disp->sinkid, riid)) {
118: *ppvObject = This;
119: InterlockedIncrement(&disp->refcount);
120: return S_OK;
121: }
122:
123: *ppvObject = NULL;
124: return E_NOINTERFACE;
125: }
126:
127: static ULONG STDMETHODCALLTYPE disp_addref(IDispatchEx *This)
128: {
129: FETCH_DISP("AddRef");
130:
131: return InterlockedIncrement(&disp->refcount);
132: }
133:
134: static ULONG STDMETHODCALLTYPE disp_release(IDispatchEx *This)
135: {
136: ULONG ret;
137: FETCH_DISP("Release");
138:
139: ret = InterlockedDecrement(&disp->refcount);
140: trace("-- refcount now %d\n", ret);
141: if (ret == 0) {
142: /* destroy it */
143: if (disp->id)
144: zend_list_delete(disp->id);
145: }
146: return ret;
147: }
148:
149: static HRESULT STDMETHODCALLTYPE disp_gettypeinfocount(
150: IDispatchEx *This,
151: /* [out] */ UINT *pctinfo)
152: {
153: FETCH_DISP("GetTypeInfoCount");
154:
155: *pctinfo = 0;
156: return S_OK;
157: }
158:
159: static HRESULT STDMETHODCALLTYPE disp_gettypeinfo(
160: IDispatchEx *This,
161: /* [in] */ UINT iTInfo,
162: /* [in] */ LCID lcid,
163: /* [out] */ ITypeInfo **ppTInfo)
164: {
165: FETCH_DISP("GetTypeInfo");
166:
167: *ppTInfo = NULL;
168: return DISP_E_BADINDEX;
169: }
170:
171: static HRESULT STDMETHODCALLTYPE disp_getidsofnames(
172: IDispatchEx *This,
173: /* [in] */ REFIID riid,
174: /* [size_is][in] */ LPOLESTR *rgszNames,
175: /* [in] */ UINT cNames,
176: /* [in] */ LCID lcid,
177: /* [size_is][out] */ DISPID *rgDispId)
178: {
179: UINT i;
180: HRESULT ret = S_OK;
181: FETCH_DISP("GetIDsOfNames");
182:
183: for (i = 0; i < cNames; i++) {
184: char *name;
185: unsigned int namelen;
186: zval **tmp;
187:
188: name = php_com_olestring_to_string(rgszNames[i], &namelen, COMG(code_page) TSRMLS_CC);
189:
190: /* Lookup the name in the hash */
191: if (zend_hash_find(disp->name_to_dispid, name, namelen+1, (void**)&tmp) == FAILURE) {
192: ret = DISP_E_UNKNOWNNAME;
193: rgDispId[i] = 0;
194: } else {
195: rgDispId[i] = Z_LVAL_PP(tmp);
196: }
197:
198: efree(name);
199:
200: }
201:
202: return ret;
203: }
204:
205: static HRESULT STDMETHODCALLTYPE disp_invoke(
206: IDispatchEx *This,
207: /* [in] */ DISPID dispIdMember,
208: /* [in] */ REFIID riid,
209: /* [in] */ LCID lcid,
210: /* [in] */ WORD wFlags,
211: /* [out][in] */ DISPPARAMS *pDispParams,
212: /* [out] */ VARIANT *pVarResult,
213: /* [out] */ EXCEPINFO *pExcepInfo,
214: /* [out] */ UINT *puArgErr)
215: {
216: return This->lpVtbl->InvokeEx(This, dispIdMember,
217: lcid, wFlags, pDispParams,
218: pVarResult, pExcepInfo, NULL);
219: }
220:
221: static HRESULT STDMETHODCALLTYPE disp_getdispid(
222: IDispatchEx *This,
223: /* [in] */ BSTR bstrName,
224: /* [in] */ DWORD grfdex,
225: /* [out] */ DISPID *pid)
226: {
227: HRESULT ret = DISP_E_UNKNOWNNAME;
228: char *name;
229: unsigned int namelen;
230: zval **tmp;
231: FETCH_DISP("GetDispID");
232:
233: name = php_com_olestring_to_string(bstrName, &namelen, COMG(code_page) TSRMLS_CC);
234:
235: trace("Looking for %s, namelen=%d in %p\n", name, namelen, disp->name_to_dispid);
236:
237: /* Lookup the name in the hash */
238: if (zend_hash_find(disp->name_to_dispid, name, namelen+1, (void**)&tmp) == SUCCESS) {
239: trace("found it\n");
240: *pid = Z_LVAL_PP(tmp);
241: ret = S_OK;
242: }
243:
244: efree(name);
245:
246: return ret;
247: }
248:
249: static HRESULT STDMETHODCALLTYPE disp_invokeex(
250: IDispatchEx *This,
251: /* [in] */ DISPID id,
252: /* [in] */ LCID lcid,
253: /* [in] */ WORD wFlags,
254: /* [in] */ DISPPARAMS *pdp,
255: /* [out] */ VARIANT *pvarRes,
256: /* [out] */ EXCEPINFO *pei,
257: /* [unique][in] */ IServiceProvider *pspCaller)
258: {
259: zval **name;
260: UINT i;
261: zval *retval = NULL;
262: zval ***params = NULL;
263: HRESULT ret = DISP_E_MEMBERNOTFOUND;
264: FETCH_DISP("InvokeEx");
265:
266: if (SUCCESS == zend_hash_index_find(disp->dispid_to_name, id, (void**)&name)) {
267: /* TODO: add support for overloaded objects */
268:
269: trace("-- Invoke: %d %20s [%d] flags=%08x args=%d\n", id, Z_STRVAL_PP(name), Z_STRLEN_PP(name), wFlags, pdp->cArgs);
270:
271: /* convert args into zvals.
272: * Args are in reverse order */
273: if (pdp->cArgs) {
274: params = (zval ***)safe_emalloc(sizeof(zval **), pdp->cArgs, 0);
275: for (i = 0; i < pdp->cArgs; i++) {
276: VARIANT *arg;
277: zval *zarg;
278:
279: arg = &pdp->rgvarg[ pdp->cArgs - 1 - i];
280:
281: trace("alloc zval for arg %d VT=%08x\n", i, V_VT(arg));
282:
283: ALLOC_INIT_ZVAL(zarg);
284: php_com_wrap_variant(zarg, arg, COMG(code_page) TSRMLS_CC);
285: params[i] = (zval**)emalloc(sizeof(zval**));
286: *params[i] = zarg;
287: }
288: }
289:
290: trace("arguments processed, prepare to do some work\n");
291:
292: /* TODO: if PHP raises an exception here, we should catch it
293: * and expose it as a COM exception */
294:
295: if (wFlags & DISPATCH_PROPERTYGET) {
296: retval = zend_read_property(Z_OBJCE_P(disp->object), disp->object, Z_STRVAL_PP(name), Z_STRLEN_PP(name)+1, 1 TSRMLS_CC);
297: } else if (wFlags & DISPATCH_PROPERTYPUT) {
298: zend_update_property(Z_OBJCE_P(disp->object), disp->object, Z_STRVAL_PP(name), Z_STRLEN_PP(name)+1, *params[0] TSRMLS_CC);
299: } else if (wFlags & DISPATCH_METHOD) {
300: zend_try {
301: if (SUCCESS == call_user_function_ex(EG(function_table), &disp->object, *name,
302: &retval, pdp->cArgs, params, 1, NULL TSRMLS_CC)) {
303: ret = S_OK;
304: trace("function called ok\n");
305:
306: /* Copy any modified values to callers copy of variant*/
307: for (i = 0; i < pdp->cArgs; i++) {
308: php_com_dotnet_object *obj = CDNO_FETCH(*params[i]);
309: VARIANT *srcvar = &obj->v;
310: VARIANT *dstvar = &pdp->rgvarg[ pdp->cArgs - 1 - i];
311: if ((V_VT(dstvar) & VT_BYREF) && obj->modified ) {
312: trace("percolate modified value for arg %d VT=%08x\n", i, V_VT(dstvar));
313: php_com_copy_variant(dstvar, srcvar TSRMLS_CC);
314: }
315: }
316: } else {
317: trace("failed to call func\n");
318: ret = DISP_E_EXCEPTION;
319: }
320: } zend_catch {
321: trace("something blew up\n");
322: ret = DISP_E_EXCEPTION;
323: } zend_end_try();
324: } else {
325: trace("Don't know how to handle this invocation %08x\n", wFlags);
326: }
327:
328: /* release arguments */
329: if (params) {
330: for (i = 0; i < pdp->cArgs; i++) {
331: zval_ptr_dtor(params[i]);
332: efree(params[i]);
333: }
334: efree(params);
335: }
336:
337: /* return value */
338: if (retval) {
339: if (pvarRes) {
340: VariantInit(pvarRes);
341: php_com_variant_from_zval(pvarRes, retval, COMG(code_page) TSRMLS_CC);
342: }
343: zval_ptr_dtor(&retval);
344: } else if (pvarRes) {
345: VariantInit(pvarRes);
346: }
347:
348: } else {
349: trace("InvokeEx: I don't support DISPID=%d\n", id);
350: }
351:
352: return ret;
353: }
354:
355: static HRESULT STDMETHODCALLTYPE disp_deletememberbyname(
356: IDispatchEx *This,
357: /* [in] */ BSTR bstrName,
358: /* [in] */ DWORD grfdex)
359: {
360: FETCH_DISP("DeleteMemberByName");
361:
362: /* TODO: unset */
363:
364: return S_FALSE;
365: }
366:
367: static HRESULT STDMETHODCALLTYPE disp_deletememberbydispid(
368: IDispatchEx *This,
369: /* [in] */ DISPID id)
370: {
371: FETCH_DISP("DeleteMemberByDispID");
372:
373: /* TODO: unset */
374:
375: return S_FALSE;
376: }
377:
378: static HRESULT STDMETHODCALLTYPE disp_getmemberproperties(
379: IDispatchEx *This,
380: /* [in] */ DISPID id,
381: /* [in] */ DWORD grfdexFetch,
382: /* [out] */ DWORD *pgrfdex)
383: {
384: FETCH_DISP("GetMemberProperties");
385:
386: return DISP_E_UNKNOWNNAME;
387: }
388:
389: static HRESULT STDMETHODCALLTYPE disp_getmembername(
390: IDispatchEx *This,
391: /* [in] */ DISPID id,
392: /* [out] */ BSTR *pbstrName)
393: {
394: zval *name;
395: FETCH_DISP("GetMemberName");
396:
397: if (SUCCESS == zend_hash_index_find(disp->dispid_to_name, id, (void**)&name)) {
398: OLECHAR *olestr = php_com_string_to_olestring(Z_STRVAL_P(name), Z_STRLEN_P(name), COMG(code_page) TSRMLS_CC);
399: *pbstrName = SysAllocString(olestr);
400: efree(olestr);
401: return S_OK;
402: } else {
403: return DISP_E_UNKNOWNNAME;
404: }
405: }
406:
407: static HRESULT STDMETHODCALLTYPE disp_getnextdispid(
408: IDispatchEx *This,
409: /* [in] */ DWORD grfdex,
410: /* [in] */ DISPID id,
411: /* [out] */ DISPID *pid)
412: {
413: ulong next = id+1;
414: FETCH_DISP("GetNextDispID");
415:
416: while(!zend_hash_index_exists(disp->dispid_to_name, next))
417: next++;
418:
419: if (zend_hash_index_exists(disp->dispid_to_name, next)) {
420: *pid = next;
421: return S_OK;
422: }
423: return S_FALSE;
424: }
425:
426: static HRESULT STDMETHODCALLTYPE disp_getnamespaceparent(
427: IDispatchEx *This,
428: /* [out] */ IUnknown **ppunk)
429: {
430: FETCH_DISP("GetNameSpaceParent");
431:
432: *ppunk = NULL;
433: return E_NOTIMPL;
434: }
435:
436: static struct IDispatchExVtbl php_dispatch_vtbl = {
437: disp_queryinterface,
438: disp_addref,
439: disp_release,
440: disp_gettypeinfocount,
441: disp_gettypeinfo,
442: disp_getidsofnames,
443: disp_invoke,
444: disp_getdispid,
445: disp_invokeex,
446: disp_deletememberbyname,
447: disp_deletememberbydispid,
448: disp_getmemberproperties,
449: disp_getmembername,
450: disp_getnextdispid,
451: disp_getnamespaceparent
452: };
453:
454:
455: /* enumerate functions and properties of the object and assign
456: * dispatch ids */
457: static void generate_dispids(php_dispatchex *disp TSRMLS_DC)
458: {
459: HashPosition pos;
460: char *name = NULL;
461: zval *tmp;
462: int namelen;
463: int keytype;
464: ulong pid;
465:
466: if (disp->dispid_to_name == NULL) {
467: ALLOC_HASHTABLE(disp->dispid_to_name);
468: ALLOC_HASHTABLE(disp->name_to_dispid);
469: zend_hash_init(disp->name_to_dispid, 0, NULL, ZVAL_PTR_DTOR, 0);
470: zend_hash_init(disp->dispid_to_name, 0, NULL, ZVAL_PTR_DTOR, 0);
471: }
472:
473: /* properties */
474: if (Z_OBJPROP_P(disp->object)) {
475: zend_hash_internal_pointer_reset_ex(Z_OBJPROP_P(disp->object), &pos);
476: while (HASH_KEY_NON_EXISTANT != (keytype =
477: zend_hash_get_current_key_ex(Z_OBJPROP_P(disp->object), &name,
478: &namelen, &pid, 0, &pos))) {
479: char namebuf[32];
480: if (keytype == HASH_KEY_IS_LONG) {
481: snprintf(namebuf, sizeof(namebuf), "%d", pid);
482: name = namebuf;
483: namelen = strlen(namebuf)+1;
484: }
485:
486: zend_hash_move_forward_ex(Z_OBJPROP_P(disp->object), &pos);
487:
488: /* Find the existing id */
489: if (zend_hash_find(disp->name_to_dispid, name, namelen, (void**)&tmp) == SUCCESS)
490: continue;
491:
492: /* add the mappings */
493: MAKE_STD_ZVAL(tmp);
494: ZVAL_STRINGL(tmp, name, namelen-1, 1);
495: pid = zend_hash_next_free_element(disp->dispid_to_name);
496: zend_hash_index_update(disp->dispid_to_name, pid, (void*)&tmp, sizeof(zval *), NULL);
497:
498: MAKE_STD_ZVAL(tmp);
499: ZVAL_LONG(tmp, pid);
500: zend_hash_update(disp->name_to_dispid, name, namelen, (void*)&tmp, sizeof(zval *), NULL);
501: }
502: }
503:
504: /* functions */
505: if (Z_OBJCE_P(disp->object)) {
506: zend_hash_internal_pointer_reset_ex(&Z_OBJCE_P(disp->object)->function_table, &pos);
507: while (HASH_KEY_NON_EXISTANT != (keytype =
508: zend_hash_get_current_key_ex(&Z_OBJCE_P(disp->object)->function_table,
509: &name, &namelen, &pid, 0, &pos))) {
510:
511: char namebuf[32];
512: if (keytype == HASH_KEY_IS_LONG) {
513: snprintf(namebuf, sizeof(namebuf), "%d", pid);
514: name = namebuf;
515: namelen = strlen(namebuf) + 1;
516: }
517:
518: zend_hash_move_forward_ex(Z_OBJPROP_P(disp->object), &pos);
519:
520: /* Find the existing id */
521: if (zend_hash_find(disp->name_to_dispid, name, namelen, (void**)&tmp) == SUCCESS)
522: continue;
523:
524: /* add the mappings */
525: MAKE_STD_ZVAL(tmp);
526: ZVAL_STRINGL(tmp, name, namelen-1, 1);
527: pid = zend_hash_next_free_element(disp->dispid_to_name);
528: zend_hash_index_update(disp->dispid_to_name, pid, (void*)&tmp, sizeof(zval *), NULL);
529:
530: MAKE_STD_ZVAL(tmp);
531: ZVAL_LONG(tmp, pid);
532: zend_hash_update(disp->name_to_dispid, name, namelen, (void*)&tmp, sizeof(zval *), NULL);
533: }
534: }
535: }
536:
537: static php_dispatchex *disp_constructor(zval *object TSRMLS_DC)
538: {
539: php_dispatchex *disp = (php_dispatchex*)CoTaskMemAlloc(sizeof(php_dispatchex));
540:
541: trace("constructing a COM wrapper for PHP object %p (%s)\n", object, Z_OBJCE_P(object)->name);
542:
543: if (disp == NULL)
544: return NULL;
545:
546: memset(disp, 0, sizeof(php_dispatchex));
547:
548: disp->engine_thread = GetCurrentThreadId();
549: disp->lpVtbl = &php_dispatch_vtbl;
550: disp->refcount = 1;
551:
552:
553: if (object)
554: Z_ADDREF_P(object);
555: disp->object = object;
556:
557: disp->id = zend_list_insert(disp, le_dispatch);
558:
559: return disp;
560: }
561:
562: static void disp_destructor(php_dispatchex *disp)
563: {
564: TSRMLS_FETCH();
565:
566: /* Object store not available during request shutdown */
567: if (COMG(rshutdown_started)) {
568: trace("destroying COM wrapper for PHP object %p (name:unknown)\n", disp->object);
569: } else {
570: trace("destroying COM wrapper for PHP object %p (name:%s)\n", disp->object, Z_OBJCE_P(disp->object)->name);
571: }
572:
573: disp->id = 0;
574:
575: if (disp->refcount > 0)
576: CoDisconnectObject((IUnknown*)disp, 0);
577:
578: zend_hash_destroy(disp->dispid_to_name);
579: zend_hash_destroy(disp->name_to_dispid);
580: FREE_HASHTABLE(disp->dispid_to_name);
581: FREE_HASHTABLE(disp->name_to_dispid);
582:
583: if (disp->object)
584: zval_ptr_dtor(&disp->object);
585:
586: CoTaskMemFree(disp);
587: }
588:
589: PHPAPI IDispatch *php_com_wrapper_export_as_sink(zval *val, GUID *sinkid,
590: HashTable *id_to_name TSRMLS_DC)
591: {
592: php_dispatchex *disp = disp_constructor(val TSRMLS_CC);
593: HashPosition pos;
594: char *name = NULL;
595: zval *tmp, **ntmp;
596: int namelen;
597: int keytype;
598: ulong pid;
599:
600: disp->dispid_to_name = id_to_name;
601:
602: memcpy(&disp->sinkid, sinkid, sizeof(disp->sinkid));
603:
604: /* build up the reverse mapping */
605: ALLOC_HASHTABLE(disp->name_to_dispid);
606: zend_hash_init(disp->name_to_dispid, 0, NULL, ZVAL_PTR_DTOR, 0);
607:
608: zend_hash_internal_pointer_reset_ex(id_to_name, &pos);
609: while (HASH_KEY_NON_EXISTANT != (keytype =
610: zend_hash_get_current_key_ex(id_to_name, &name, &namelen, &pid, 0, &pos))) {
611:
612: if (keytype == HASH_KEY_IS_LONG) {
613:
614: zend_hash_get_current_data_ex(id_to_name, (void**)&ntmp, &pos);
615:
616: MAKE_STD_ZVAL(tmp);
617: ZVAL_LONG(tmp, pid);
618: zend_hash_update(disp->name_to_dispid, Z_STRVAL_PP(ntmp),
619: Z_STRLEN_PP(ntmp)+1, (void*)&tmp, sizeof(zval *), NULL);
620: }
621:
622: zend_hash_move_forward_ex(id_to_name, &pos);
623: }
624:
625: return (IDispatch*)disp;
626: }
627:
628: PHPAPI IDispatch *php_com_wrapper_export(zval *val TSRMLS_DC)
629: {
630: php_dispatchex *disp = NULL;
631:
632: if (Z_TYPE_P(val) != IS_OBJECT) {
633: return NULL;
634: }
635:
636: if (php_com_is_valid_object(val TSRMLS_CC)) {
637: /* pass back its IDispatch directly */
638: php_com_dotnet_object *obj = CDNO_FETCH(val);
639:
640: if (obj == NULL)
641: return NULL;
642:
643: if (V_VT(&obj->v) == VT_DISPATCH && V_DISPATCH(&obj->v)) {
644: IDispatch_AddRef(V_DISPATCH(&obj->v));
645: return V_DISPATCH(&obj->v);
646: }
647:
648: return NULL;
649: }
650:
651: disp = disp_constructor(val TSRMLS_CC);
652: generate_dispids(disp TSRMLS_CC);
653:
654: return (IDispatch*)disp;
655: }
656:
657:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>