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