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