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