msxml3: Added partial implementation of ISAXXMLReader_parse.
[wine] / dlls / mshtml / dispex.c
1 /*
2  * Copyright 2008 Jacek Caban for CodeWeavers
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18
19 #include <stdarg.h>
20
21 #define COBJMACROS
22
23 #include "windef.h"
24 #include "winbase.h"
25 #include "winuser.h"
26 #include "ole2.h"
27
28 #include "wine/debug.h"
29
30 #include "mshtml_private.h"
31
32 WINE_DEFAULT_DEBUG_CHANNEL(mshtml);
33
34 typedef struct {
35     DISPID id;
36     BSTR name;
37     tid_t tid;
38 } func_info_t;
39
40 struct dispex_data_t {
41     DWORD func_cnt;
42     func_info_t *funcs;
43     func_info_t **name_table;
44
45     struct list entry;
46 };
47
48 typedef struct {
49     VARIANT var;
50     LPWSTR name;
51 } dynamic_prop_t;
52
53 struct dispex_dynamic_data_t {
54     DWORD buf_size;
55     DWORD prop_cnt;
56     dynamic_prop_t *props;
57 };
58
59 #define DISPID_DYNPROP_0    0x50000000
60 #define DISPID_DYNPROP_MAX  0x5fffffff
61
62 static ITypeLib *typelib;
63 static ITypeInfo *typeinfos[LAST_tid];
64 static struct list dispex_data_list = LIST_INIT(dispex_data_list);
65
66 static REFIID tid_ids[] = {
67     &IID_NULL,
68     &DIID_DispDOMChildrenCollection,
69     &DIID_DispHTMLCommentElement,
70     &DIID_DispHTMLDocument,
71     &DIID_DispHTMLDOMTextNode,
72     &DIID_DispHTMLElementCollection,
73     &DIID_DispHTMLGenericElement,
74     &DIID_DispHTMLImg,
75     &DIID_DispHTMLInputElement,
76     &DIID_DispHTMLOptionElement,
77     &DIID_DispHTMLSelectElement,
78     &DIID_DispHTMLStyle,
79     &DIID_DispHTMLUnknownElement,
80     &DIID_DispHTMLWindow2,
81     &IID_IHTMLCommentElement,
82     &IID_IHTMLDocument2,
83     &IID_IHTMLDocument3,
84     &IID_IHTMLDocument4,
85     &IID_IHTMLDocument5,
86     &IID_IHTMLDOMChildrenCollection,
87     &IID_IHTMLDOMNode,
88     &IID_IHTMLDOMNode2,
89     &IID_IHTMLDOMTextNode,
90     &IID_IHTMLElement,
91     &IID_IHTMLElement2,
92     &IID_IHTMLElementCollection,
93     &IID_IHTMLGenericElement,
94     &IID_IHTMLImgElement,
95     &IID_IHTMLInputElement,
96     &IID_IHTMLOptionElement,
97     &IID_IHTMLSelectElement,
98     &IID_IHTMLStyle,
99     &IID_IHTMLWindow2,
100     &IID_IHTMLWindow3,
101     &IID_IOmNavigator
102 };
103
104 HRESULT get_typeinfo(tid_t tid, ITypeInfo **typeinfo)
105 {
106     HRESULT hres;
107
108     if(!typelib) {
109         ITypeLib *tl;
110
111         hres = LoadRegTypeLib(&LIBID_MSHTML, 4, 0, LOCALE_SYSTEM_DEFAULT, &tl);
112         if(FAILED(hres)) {
113             ERR("LoadRegTypeLib failed: %08x\n", hres);
114             return hres;
115         }
116
117         if(InterlockedCompareExchangePointer((void**)&typelib, tl, NULL))
118             ITypeLib_Release(tl);
119     }
120
121     if(!typeinfos[tid]) {
122         ITypeInfo *typeinfo;
123
124         hres = ITypeLib_GetTypeInfoOfGuid(typelib, tid_ids[tid], &typeinfo);
125         if(FAILED(hres)) {
126             ERR("GetTypeInfoOfGuid(%s) failed: %08x\n", debugstr_guid(tid_ids[tid]), hres);
127             return hres;
128         }
129
130         if(InterlockedCompareExchangePointer((void**)(typeinfos+tid), typeinfo, NULL))
131             ITypeInfo_Release(typeinfo);
132     }
133
134     *typeinfo = typeinfos[tid];
135     return S_OK;
136 }
137
138 void release_typelib(void)
139 {
140     dispex_data_t *iter;
141     unsigned i;
142
143     while(!list_empty(&dispex_data_list)) {
144         iter = LIST_ENTRY(list_head(&dispex_data_list), dispex_data_t, entry);
145         list_remove(&iter->entry);
146
147         for(i=0; i < iter->func_cnt; i++)
148             SysFreeString(iter->funcs[i].name);
149
150         heap_free(iter->funcs);
151         heap_free(iter->name_table);
152         heap_free(iter);
153     }
154
155     if(!typelib)
156         return;
157
158     for(i=0; i < sizeof(typeinfos)/sizeof(*typeinfos); i++)
159         if(typeinfos[i])
160             ITypeInfo_Release(typeinfos[i]);
161
162     ITypeLib_Release(typelib);
163 }
164
165 static void add_func_info(dispex_data_t *data, DWORD *size, tid_t tid, DISPID id, ITypeInfo *dti)
166 {
167     HRESULT hres;
168
169     if(data->func_cnt && data->funcs[data->func_cnt-1].id == id)
170         return;
171
172     if(data->func_cnt == *size)
173         data->funcs = heap_realloc(data->funcs, (*size <<= 1)*sizeof(func_info_t));
174
175     hres = ITypeInfo_GetDocumentation(dti, id, &data->funcs[data->func_cnt].name, NULL, NULL, NULL);
176     if(FAILED(hres))
177         return;
178
179     data->funcs[data->func_cnt].id = id;
180     data->funcs[data->func_cnt].tid = tid;
181
182     data->func_cnt++;
183 }
184
185 static int dispid_cmp(const void *p1, const void *p2)
186 {
187     return ((func_info_t*)p1)->id - ((func_info_t*)p2)->id;
188 }
189
190 static int func_name_cmp(const void *p1, const void *p2)
191 {
192     return strcmpiW((*(func_info_t**)p1)->name, (*(func_info_t**)p2)->name);
193 }
194
195 static dispex_data_t *preprocess_dispex_data(DispatchEx *This)
196 {
197     const tid_t *tid = This->data->iface_tids;
198     FUNCDESC *funcdesc;
199     dispex_data_t *data;
200     DWORD size = 16, i;
201     ITypeInfo *ti, *dti;
202     HRESULT hres;
203
204     TRACE("(%p)\n", This);
205
206     hres = get_typeinfo(This->data->disp_tid, &dti);
207     if(FAILED(hres)) {
208         ERR("Could not get disp type info: %08x\n", hres);
209         return NULL;
210     }
211
212     data = heap_alloc(sizeof(dispex_data_t));
213     data->func_cnt = 0;
214     data->funcs = heap_alloc(size*sizeof(func_info_t));
215     list_add_tail(&dispex_data_list, &data->entry);
216
217     while(*tid) {
218         hres = get_typeinfo(*tid, &ti);
219         if(FAILED(hres))
220             break;
221
222         i=7;
223         while(1) {
224             hres = ITypeInfo_GetFuncDesc(ti, i++, &funcdesc);
225             if(FAILED(hres))
226                 break;
227
228             add_func_info(data, &size, *tid, funcdesc->memid, dti);
229             ITypeInfo_ReleaseFuncDesc(ti, funcdesc);
230         }
231
232         tid++;
233     }
234
235     if(!data->func_cnt) {
236         heap_free(data->funcs);
237         data->funcs = NULL;
238     }else if(data->func_cnt != size) {
239         data->funcs = heap_realloc(data->funcs, data->func_cnt * sizeof(func_info_t));
240     }
241
242     qsort(data->funcs, data->func_cnt, sizeof(func_info_t), dispid_cmp);
243
244     if(data->funcs) {
245         data->name_table = heap_alloc(data->func_cnt * sizeof(func_info_t*));
246         for(i=0; i < data->func_cnt; i++)
247             data->name_table[i] = data->funcs+i;
248         qsort(data->name_table, data->func_cnt, sizeof(func_info_t*), func_name_cmp);
249     }else {
250         data->name_table = NULL;
251     }
252
253     return data;
254 }
255
256 static CRITICAL_SECTION cs_dispex_static_data;
257 static CRITICAL_SECTION_DEBUG cs_dispex_static_data_dbg =
258 {
259     0, 0, &cs_dispex_static_data,
260     { &cs_dispex_static_data_dbg.ProcessLocksList, &cs_dispex_static_data_dbg.ProcessLocksList },
261       0, 0, { (DWORD_PTR)(__FILE__ ": dispex_static_data") }
262 };
263 static CRITICAL_SECTION cs_dispex_static_data = { &cs_dispex_static_data_dbg, -1, 0, 0, 0, 0 };
264
265
266 static dispex_data_t *get_dispex_data(DispatchEx *This)
267 {
268     if(This->data->data)
269         return This->data->data;
270
271     EnterCriticalSection(&cs_dispex_static_data);
272
273     if(!This->data->data)
274         This->data->data = preprocess_dispex_data(This);
275
276     LeaveCriticalSection(&cs_dispex_static_data);
277
278     return This->data->data;
279 }
280
281 void call_disp_func(HTMLDocument *doc, IDispatch *disp)
282 {
283     DISPID named_arg = DISPID_THIS;
284     VARIANTARG arg;
285     DISPPARAMS params = {&arg, &named_arg, 1, 1};
286     EXCEPINFO ei;
287     IDispatchEx *dispex;
288     VARIANT res;
289     HRESULT hres;
290
291     hres = IDispatch_QueryInterface(disp, &IID_IDispatchEx, (void**)&dispex);
292     if(FAILED(hres)) {
293         FIXME("Could not get IDispatchEx interface: %08x\n", hres);
294         return;
295     }
296
297     V_VT(&arg) = VT_DISPATCH;
298     V_DISPATCH(&arg) = (IDispatch*)HTMLWINDOW2(doc->window);
299     VariantInit(&res);
300     memset(&ei, 0, sizeof(ei));
301
302     hres = IDispatchEx_InvokeEx(dispex, 0, GetUserDefaultLCID(), DISPATCH_METHOD, &params, &res, &ei, NULL);
303     IDispatchEx_Release(dispex);
304
305     TRACE("%p returned %08x\n", disp, hres);
306
307     VariantClear(&res);
308 }
309
310 static inline BOOL is_custom_dispid(DISPID id)
311 {
312     return MSHTML_DISPID_CUSTOM_MIN <= id && id <= MSHTML_DISPID_CUSTOM_MAX;
313 }
314
315 static inline BOOL is_dynamic_dispid(DISPID id)
316 {
317     return DISPID_DYNPROP_0 <= id && id <= DISPID_DYNPROP_MAX;
318 }
319
320 #define DISPATCHEX_THIS(iface) DEFINE_THIS(DispatchEx, IDispatchEx, iface)
321
322 static HRESULT WINAPI DispatchEx_QueryInterface(IDispatchEx *iface, REFIID riid, void **ppv)
323 {
324     DispatchEx *This = DISPATCHEX_THIS(iface);
325
326     return IUnknown_QueryInterface(This->outer, riid, ppv);
327 }
328
329 static ULONG WINAPI DispatchEx_AddRef(IDispatchEx *iface)
330 {
331     DispatchEx *This = DISPATCHEX_THIS(iface);
332
333     return IUnknown_AddRef(This->outer);
334 }
335
336 static ULONG WINAPI DispatchEx_Release(IDispatchEx *iface)
337 {
338     DispatchEx *This = DISPATCHEX_THIS(iface);
339
340     return IUnknown_Release(This->outer);
341 }
342
343 static HRESULT WINAPI DispatchEx_GetTypeInfoCount(IDispatchEx *iface, UINT *pctinfo)
344 {
345     DispatchEx *This = DISPATCHEX_THIS(iface);
346
347     TRACE("(%p)->(%p)\n", This, pctinfo);
348
349     *pctinfo = 1;
350     return S_OK;
351 }
352
353 static HRESULT WINAPI DispatchEx_GetTypeInfo(IDispatchEx *iface, UINT iTInfo,
354                                               LCID lcid, ITypeInfo **ppTInfo)
355 {
356     DispatchEx *This = DISPATCHEX_THIS(iface);
357     HRESULT hres;
358
359     TRACE("(%p)->(%u %u %p)\n", This, iTInfo, lcid, ppTInfo);
360
361     hres = get_typeinfo(This->data->disp_tid, ppTInfo);
362     if(FAILED(hres))
363         return hres;
364
365     ITypeInfo_AddRef(*ppTInfo);
366     return S_OK;
367 }
368
369 static HRESULT WINAPI DispatchEx_GetIDsOfNames(IDispatchEx *iface, REFIID riid,
370                                                 LPOLESTR *rgszNames, UINT cNames,
371                                                 LCID lcid, DISPID *rgDispId)
372 {
373     DispatchEx *This = DISPATCHEX_THIS(iface);
374     UINT i;
375     HRESULT hres;
376
377     TRACE("(%p)->(%s %p %u %u %p)\n", This, debugstr_guid(riid), rgszNames, cNames,
378           lcid, rgDispId);
379
380     for(i=0; i < cNames; i++) {
381         hres = IDispatchEx_GetDispID(DISPATCHEX(This), rgszNames[i], 0, rgDispId+i);
382         if(FAILED(hres))
383             return hres;
384     }
385
386     return S_OK;
387 }
388
389 static HRESULT WINAPI DispatchEx_Invoke(IDispatchEx *iface, DISPID dispIdMember,
390                             REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams,
391                             VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
392 {
393     DispatchEx *This = DISPATCHEX_THIS(iface);
394
395     TRACE("(%p)->(%d %s %d %d %p %p %p %p)\n", This, dispIdMember, debugstr_guid(riid),
396           lcid, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr);
397
398     return IDispatchEx_InvokeEx(DISPATCHEX(This), dispIdMember, lcid, wFlags,
399                                 pDispParams, pVarResult, pExcepInfo, NULL);
400 }
401
402 static HRESULT WINAPI DispatchEx_GetDispID(IDispatchEx *iface, BSTR bstrName, DWORD grfdex, DISPID *pid)
403 {
404     DispatchEx *This = DISPATCHEX_THIS(iface);
405     dispex_data_t *data;
406     int min, max, n, c;
407
408     TRACE("(%p)->(%s %x %p)\n", This, debugstr_w(bstrName), grfdex, pid);
409
410     if(grfdex & ~(fdexNameCaseSensitive|fdexNameEnsure|fdexNameImplicit))
411         FIXME("Unsupported grfdex %x\n", grfdex);
412
413     data = get_dispex_data(This);
414     if(!data)
415         return E_FAIL;
416
417     min = 0;
418     max = data->func_cnt-1;
419
420     while(min <= max) {
421         n = (min+max)/2;
422
423         c = strcmpiW(data->name_table[n]->name, bstrName);
424         if(!c) {
425             if((grfdex & fdexNameCaseSensitive) && strcmpW(data->name_table[n]->name, bstrName))
426                 break;
427
428             *pid = data->name_table[n]->id;
429             return S_OK;
430         }
431
432         if(c > 0)
433             max = n-1;
434         else
435             min = n+1;
436     }
437
438     if(This->dynamic_data) {
439         unsigned i;
440
441         for(i=0; i < This->dynamic_data->prop_cnt; i++) {
442             if(!strcmpW(This->dynamic_data->props[i].name, bstrName)) {
443                 *pid = DISPID_DYNPROP_0 + i;
444                 return S_OK;
445             }
446         }
447     }
448
449     if(This->data->vtbl && This->data->vtbl->get_dispid) {
450         HRESULT hres;
451
452         hres = This->data->vtbl->get_dispid(This->outer, bstrName, grfdex, pid);
453         if(hres != DISP_E_UNKNOWNNAME)
454             return hres;
455     }
456
457     if(grfdex & fdexNameEnsure) {
458         TRACE("creating dynamic prop %s\n", debugstr_w(bstrName));
459
460         if(!This->dynamic_data) {
461             This->dynamic_data = heap_alloc_zero(sizeof(dispex_dynamic_data_t));
462             This->dynamic_data->props = heap_alloc(This->dynamic_data->buf_size = 4);
463         }else if(This->dynamic_data->buf_size == This->dynamic_data->prop_cnt) {
464             This->dynamic_data->props = heap_realloc(This->dynamic_data->props, This->dynamic_data->buf_size<<=1);
465         }
466
467         This->dynamic_data->props[This->dynamic_data->prop_cnt].name = heap_strdupW(bstrName);
468         VariantInit(&This->dynamic_data->props[This->dynamic_data->prop_cnt].var);
469         *pid = DISPID_DYNPROP_0 + This->dynamic_data->prop_cnt++;
470
471         return S_OK;
472     }
473
474     TRACE("not found %s\n", debugstr_w(bstrName));
475     return DISP_E_UNKNOWNNAME;
476 }
477
478 static HRESULT WINAPI DispatchEx_InvokeEx(IDispatchEx *iface, DISPID id, LCID lcid, WORD wFlags, DISPPARAMS *pdp,
479         VARIANT *pvarRes, EXCEPINFO *pei, IServiceProvider *pspCaller)
480 {
481     DispatchEx *This = DISPATCHEX_THIS(iface);
482     IUnknown *unk;
483     ITypeInfo *ti;
484     dispex_data_t *data;
485     UINT argerr=0;
486     int min, max, n;
487     HRESULT hres;
488
489     TRACE("(%p)->(%x %x %x %p %p %p %p)\n", This, id, lcid, wFlags, pdp, pvarRes, pei, pspCaller);
490
491     if(is_custom_dispid(id) && This->data->vtbl && This->data->vtbl->invoke)
492         return This->data->vtbl->invoke(This->outer, id, lcid, wFlags, pdp, pvarRes, pei, pspCaller);
493
494     if(wFlags == DISPATCH_CONSTRUCT) {
495         FIXME("DISPATCH_CONSTRUCT not implemented\n");
496         return E_NOTIMPL;
497     }
498
499     if(is_dynamic_dispid(id)) {
500         DWORD idx = id - DISPID_DYNPROP_0;
501         VARIANT *var;
502
503         if(!This->dynamic_data || This->dynamic_data->prop_cnt <= idx)
504             return DISP_E_UNKNOWNNAME;
505
506         var = &This->dynamic_data->props[idx].var;
507
508         switch(wFlags) {
509         case INVOKE_PROPERTYGET:
510             return VariantCopy(pvarRes, var);
511         case INVOKE_PROPERTYPUT:
512             VariantClear(var);
513             return VariantCopy(var, pdp->rgvarg);
514         default:
515             FIXME("unhandled wFlags %x\n", wFlags);
516             return E_NOTIMPL;
517         }
518     }
519
520     data = get_dispex_data(This);
521     if(!data)
522         return E_FAIL;
523
524     min = 0;
525     max = data->func_cnt-1;
526
527     while(min <= max) {
528         n = (min+max)/2;
529
530         if(data->funcs[n].id == id)
531             break;
532
533         if(data->funcs[n].id < id)
534             min = n+1;
535         else
536             max = n-1;
537     }
538
539     if(min > max) {
540         WARN("invalid id %x\n", id);
541         return DISP_E_UNKNOWNNAME;
542     }
543
544     hres = get_typeinfo(data->funcs[n].tid, &ti);
545     if(FAILED(hres)) {
546         ERR("Could not get type info: %08x\n", hres);
547         return hres;
548     }
549
550     hres = IUnknown_QueryInterface(This->outer, tid_ids[data->funcs[n].tid], (void**)&unk);
551     if(FAILED(hres)) {
552         ERR("Could not get iface: %08x\n", hres);
553         return E_FAIL;
554     }
555
556     hres = ITypeInfo_Invoke(ti, unk, id, wFlags, pdp, pvarRes, pei, &argerr);
557
558     IUnknown_Release(unk);
559     return hres;
560 }
561
562 static HRESULT WINAPI DispatchEx_DeleteMemberByName(IDispatchEx *iface, BSTR bstrName, DWORD grfdex)
563 {
564     DispatchEx *This = DISPATCHEX_THIS(iface);
565     FIXME("(%p)->(%s %x)\n", This, debugstr_w(bstrName), grfdex);
566     return E_NOTIMPL;
567 }
568
569 static HRESULT WINAPI DispatchEx_DeleteMemberByDispID(IDispatchEx *iface, DISPID id)
570 {
571     DispatchEx *This = DISPATCHEX_THIS(iface);
572     FIXME("(%p)->(%x)\n", This, id);
573     return E_NOTIMPL;
574 }
575
576 static HRESULT WINAPI DispatchEx_GetMemberProperties(IDispatchEx *iface, DISPID id, DWORD grfdexFetch, DWORD *pgrfdex)
577 {
578     DispatchEx *This = DISPATCHEX_THIS(iface);
579     FIXME("(%p)->(%x %x %p)\n", This, id, grfdexFetch, pgrfdex);
580     return E_NOTIMPL;
581 }
582
583 static HRESULT WINAPI DispatchEx_GetMemberName(IDispatchEx *iface, DISPID id, BSTR *pbstrName)
584 {
585     DispatchEx *This = DISPATCHEX_THIS(iface);
586     FIXME("(%p)->(%x %p)\n", This, id, pbstrName);
587     return E_NOTIMPL;
588 }
589
590 static HRESULT WINAPI DispatchEx_GetNextDispID(IDispatchEx *iface, DWORD grfdex, DISPID id, DISPID *pid)
591 {
592     DispatchEx *This = DISPATCHEX_THIS(iface);
593     FIXME("(%p)->(%x %x %p)\n", This, grfdex, id, pid);
594     return E_NOTIMPL;
595 }
596
597 static HRESULT WINAPI DispatchEx_GetNameSpaceParent(IDispatchEx *iface, IUnknown **ppunk)
598 {
599     DispatchEx *This = DISPATCHEX_THIS(iface);
600     FIXME("(%p)->(%p)\n", This, ppunk);
601     return E_NOTIMPL;
602 }
603
604 #undef DISPATCHEX_THIS
605
606 static IDispatchExVtbl DispatchExVtbl = {
607     DispatchEx_QueryInterface,
608     DispatchEx_AddRef,
609     DispatchEx_Release,
610     DispatchEx_GetTypeInfoCount,
611     DispatchEx_GetTypeInfo,
612     DispatchEx_GetIDsOfNames,
613     DispatchEx_Invoke,
614     DispatchEx_GetDispID,
615     DispatchEx_InvokeEx,
616     DispatchEx_DeleteMemberByName,
617     DispatchEx_DeleteMemberByDispID,
618     DispatchEx_GetMemberProperties,
619     DispatchEx_GetMemberName,
620     DispatchEx_GetNextDispID,
621     DispatchEx_GetNameSpaceParent
622 };
623
624 BOOL dispex_query_interface(DispatchEx *This, REFIID riid, void **ppv)
625 {
626     static const IID IID_UndocumentedScriptIface =
627         {0x719c3050,0xf9d3,0x11cf,{0xa4,0x93,0x00,0x40,0x05,0x23,0xa8,0xa0}};
628
629     if(IsEqualGUID(&IID_IDispatch, riid)) {
630         TRACE("(%p)->(IID_IDispatch %p)\n", This, ppv);
631         *ppv = DISPATCHEX(This);
632     }else if(IsEqualGUID(&IID_IDispatchEx, riid)) {
633         TRACE("(%p)->(IID_IDispatchEx %p)\n", This, ppv);
634         *ppv = DISPATCHEX(This);
635     }else if(IsEqualGUID(&IID_UndocumentedScriptIface, riid)) {
636         TRACE("(%p)->(IID_UndocumentedScriptIface %p) returning NULL\n", This, ppv);
637         *ppv = NULL;
638     }else {
639         return FALSE;
640     }
641
642     if(*ppv)
643         IUnknown_AddRef((IUnknown*)*ppv);
644     return TRUE;
645 }
646
647 void init_dispex(DispatchEx *dispex, IUnknown *outer, dispex_static_data_t *data)
648 {
649     dispex->lpIDispatchExVtbl = &DispatchExVtbl;
650     dispex->outer = outer;
651     dispex->data = data;
652 }