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