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 /* Infrastructure for working with persistent COM objects.
18 * Implements: IStream* wrapper for PHP streams.
19 * TODO:
20 * - Magic __wakeup and __sleep handlers for serialization.
21 * - Track the stream and dispatch instances in a global list to make sure
22 * they are destroyed when a fatal error occurs.
23 */
24
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28
29 #include "php.h"
30 #include "php_ini.h"
31 #include "ext/standard/info.h"
32 #include "php_com_dotnet.h"
33 #include "php_com_dotnet_internal.h"
34 #include "Zend/zend_exceptions.h"
35 #include "com_persist_arginfo.h"
36
37 /* {{{ expose php_stream as a COM IStream */
38
39 typedef struct {
40 CONST_VTBL struct IStreamVtbl *lpVtbl;
41 DWORD engine_thread;
42 LONG refcount;
43 php_stream *stream;
44 } php_istream;
45
46 static void istream_destructor(php_istream *stm);
47
48 #define FETCH_STM() \
49 php_istream *stm = (php_istream*)This; \
50 if (GetCurrentThreadId() != stm->engine_thread) \
51 return RPC_E_WRONG_THREAD;
52
53 #define FETCH_STM_EX() \
54 php_istream *stm = (php_istream*)This; \
55 if (GetCurrentThreadId() != stm->engine_thread) \
56 return RPC_E_WRONG_THREAD;
57
stm_queryinterface(IStream * This,REFIID riid,void ** ppvObject)58 static HRESULT STDMETHODCALLTYPE stm_queryinterface(
59 IStream *This,
60 /* [in] */ REFIID riid,
61 /* [iid_is][out] */ void **ppvObject)
62 {
63 FETCH_STM_EX();
64
65 if (IsEqualGUID(&IID_IUnknown, riid) ||
66 IsEqualGUID(&IID_IStream, riid)) {
67 *ppvObject = This;
68 InterlockedIncrement(&stm->refcount);
69 return S_OK;
70 }
71
72 *ppvObject = NULL;
73 return E_NOINTERFACE;
74 }
75
stm_addref(IStream * This)76 static ULONG STDMETHODCALLTYPE stm_addref(IStream *This)
77 {
78 FETCH_STM_EX();
79
80 return InterlockedIncrement(&stm->refcount);
81 }
82
stm_release(IStream * This)83 static ULONG STDMETHODCALLTYPE stm_release(IStream *This)
84 {
85 ULONG ret;
86 FETCH_STM();
87
88 ret = InterlockedDecrement(&stm->refcount);
89 if (ret == 0) {
90 /* destroy it */
91 istream_destructor(stm);
92 }
93 return ret;
94 }
95
stm_read(IStream * This,void * pv,ULONG cb,ULONG * pcbRead)96 static HRESULT STDMETHODCALLTYPE stm_read(IStream *This, void *pv, ULONG cb, ULONG *pcbRead)
97 {
98 ULONG nread;
99 FETCH_STM();
100
101 nread = (ULONG)php_stream_read(stm->stream, pv, cb);
102
103 if (pcbRead) {
104 *pcbRead = nread > 0 ? nread : 0;
105 }
106 if (nread > 0) {
107 return S_OK;
108 }
109 return S_FALSE;
110 }
111
stm_write(IStream * This,void const * pv,ULONG cb,ULONG * pcbWritten)112 static HRESULT STDMETHODCALLTYPE stm_write(IStream *This, void const *pv, ULONG cb, ULONG *pcbWritten)
113 {
114 ssize_t nwrote;
115 FETCH_STM();
116
117 nwrote = php_stream_write(stm->stream, pv, cb);
118
119 if (pcbWritten) {
120 *pcbWritten = nwrote > 0 ? (ULONG)nwrote : 0;
121 }
122 if (nwrote > 0) {
123 return S_OK;
124 }
125 return S_FALSE;
126 }
127
stm_seek(IStream * This,LARGE_INTEGER dlibMove,DWORD dwOrigin,ULARGE_INTEGER * plibNewPosition)128 static HRESULT STDMETHODCALLTYPE stm_seek(IStream *This, LARGE_INTEGER dlibMove,
129 DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition)
130 {
131 off_t offset;
132 int whence;
133 int ret;
134 FETCH_STM();
135
136 switch (dwOrigin) {
137 case STREAM_SEEK_SET: whence = SEEK_SET; break;
138 case STREAM_SEEK_CUR: whence = SEEK_CUR; break;
139 case STREAM_SEEK_END: whence = SEEK_END; break;
140 default:
141 return STG_E_INVALIDFUNCTION;
142 }
143
144 if (dlibMove.HighPart) {
145 /* we don't support 64-bit offsets */
146 return STG_E_INVALIDFUNCTION;
147 }
148
149 offset = (off_t) dlibMove.QuadPart;
150
151 ret = php_stream_seek(stm->stream, offset, whence);
152
153 if (plibNewPosition) {
154 plibNewPosition->QuadPart = (ULONGLONG)(ret >= 0 ? ret : 0);
155 }
156
157 return ret >= 0 ? S_OK : STG_E_INVALIDFUNCTION;
158 }
159
stm_set_size(IStream * This,ULARGE_INTEGER libNewSize)160 static HRESULT STDMETHODCALLTYPE stm_set_size(IStream *This, ULARGE_INTEGER libNewSize)
161 {
162 FETCH_STM();
163
164 if (libNewSize.HighPart) {
165 return STG_E_INVALIDFUNCTION;
166 }
167
168 if (php_stream_truncate_supported(stm->stream)) {
169 int ret = php_stream_truncate_set_size(stm->stream, (size_t)libNewSize.QuadPart);
170
171 if (ret == 0) {
172 return S_OK;
173 }
174 }
175
176 return STG_E_INVALIDFUNCTION;
177 }
178
stm_copy_to(IStream * This,IStream * pstm,ULARGE_INTEGER cb,ULARGE_INTEGER * pcbRead,ULARGE_INTEGER * pcbWritten)179 static HRESULT STDMETHODCALLTYPE stm_copy_to(IStream *This, IStream *pstm, ULARGE_INTEGER cb,
180 ULARGE_INTEGER *pcbRead, ULARGE_INTEGER *pcbWritten)
181 {
182 FETCH_STM_EX();
183
184 return E_NOTIMPL;
185 }
186
stm_commit(IStream * This,DWORD grfCommitFlags)187 static HRESULT STDMETHODCALLTYPE stm_commit(IStream *This, DWORD grfCommitFlags)
188 {
189 FETCH_STM();
190
191 php_stream_flush(stm->stream);
192
193 return S_OK;
194 }
195
stm_revert(IStream * This)196 static HRESULT STDMETHODCALLTYPE stm_revert(IStream *This)
197 {
198 /* NOP */
199 return S_OK;
200 }
201
stm_lock_region(IStream * This,ULARGE_INTEGER libOffset,ULARGE_INTEGER cb,DWORD lockType)202 static HRESULT STDMETHODCALLTYPE stm_lock_region(IStream *This,
203 ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD lockType)
204 {
205 return STG_E_INVALIDFUNCTION;
206 }
207
stm_unlock_region(IStream * This,ULARGE_INTEGER libOffset,ULARGE_INTEGER cb,DWORD lockType)208 static HRESULT STDMETHODCALLTYPE stm_unlock_region(IStream *This,
209 ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD lockType)
210 {
211 return STG_E_INVALIDFUNCTION;
212 }
213
stm_stat(IStream * This,STATSTG * pstatstg,DWORD grfStatFlag)214 static HRESULT STDMETHODCALLTYPE stm_stat(IStream *This,
215 STATSTG *pstatstg, DWORD grfStatFlag)
216 {
217 return STG_E_INVALIDFUNCTION;
218 }
219
stm_clone(IStream * This,IStream ** ppstm)220 static HRESULT STDMETHODCALLTYPE stm_clone(IStream *This, IStream **ppstm)
221 {
222 return STG_E_INVALIDFUNCTION;
223 }
224
225 static struct IStreamVtbl php_istream_vtbl = {
226 stm_queryinterface,
227 stm_addref,
228 stm_release,
229 stm_read,
230 stm_write,
231 stm_seek,
232 stm_set_size,
233 stm_copy_to,
234 stm_commit,
235 stm_revert,
236 stm_lock_region,
237 stm_unlock_region,
238 stm_stat,
239 stm_clone
240 };
241
istream_destructor(php_istream * stm)242 static void istream_destructor(php_istream *stm)
243 {
244 if (stm->refcount > 0) {
245 CoDisconnectObject((IUnknown*)stm, 0);
246 }
247
248 zend_list_delete(stm->stream->res);
249
250 CoTaskMemFree(stm);
251 }
252 /* }}} */
253
php_com_wrapper_export_stream(php_stream * stream)254 PHP_COM_DOTNET_API IStream *php_com_wrapper_export_stream(php_stream *stream)
255 {
256 php_istream *stm = (php_istream*)CoTaskMemAlloc(sizeof(*stm));
257
258 if (stm == NULL)
259 return NULL;
260
261 memset(stm, 0, sizeof(*stm));
262 stm->engine_thread = GetCurrentThreadId();
263 stm->lpVtbl = &php_istream_vtbl;
264 stm->refcount = 1;
265 stm->stream = stream;
266
267 GC_ADDREF(stream->res);
268
269 return (IStream*)stm;
270 }
271
272 #define CPH_METHOD(fname) PHP_METHOD(COMPersistHelper, fname)
273
274 #define CPH_FETCH() php_com_persist_helper *helper = (php_com_persist_helper*)Z_OBJ_P(ZEND_THIS);
275
276 #define CPH_NO_OBJ() if (helper->unk == NULL) { php_com_throw_exception(E_INVALIDARG, "No COM object is associated with this helper instance"); RETURN_THROWS(); }
277
278 typedef struct {
279 zend_object std;
280 long codepage;
281 IUnknown *unk;
282 IPersistStream *ips;
283 IPersistStreamInit *ipsi;
284 IPersistFile *ipf;
285 } php_com_persist_helper;
286
287 static zend_object_handlers helper_handlers;
288 static zend_class_entry *helper_ce;
289
get_persist_stream(php_com_persist_helper * helper)290 static inline HRESULT get_persist_stream(php_com_persist_helper *helper)
291 {
292 if (!helper->ips && helper->unk) {
293 return IUnknown_QueryInterface(helper->unk, &IID_IPersistStream, &helper->ips);
294 }
295 return helper->ips ? S_OK : E_NOTIMPL;
296 }
297
get_persist_stream_init(php_com_persist_helper * helper)298 static inline HRESULT get_persist_stream_init(php_com_persist_helper *helper)
299 {
300 if (!helper->ipsi && helper->unk) {
301 return IUnknown_QueryInterface(helper->unk, &IID_IPersistStreamInit, &helper->ipsi);
302 }
303 return helper->ipsi ? S_OK : E_NOTIMPL;
304 }
305
get_persist_file(php_com_persist_helper * helper)306 static inline HRESULT get_persist_file(php_com_persist_helper *helper)
307 {
308 if (!helper->ipf && helper->unk) {
309 return IUnknown_QueryInterface(helper->unk, &IID_IPersistFile, &helper->ipf);
310 }
311 return helper->ipf ? S_OK : E_NOTIMPL;
312 }
313
314
315 /* {{{ Determines the filename into which an object will be saved, or false if none is set, via IPersistFile::GetCurFile */
CPH_METHOD(GetCurFileName)316 CPH_METHOD(GetCurFileName)
317 {
318 HRESULT res;
319 OLECHAR *olename = NULL;
320 CPH_FETCH();
321
322 if (zend_parse_parameters_none() == FAILURE) {
323 RETURN_THROWS();
324 }
325
326 CPH_NO_OBJ();
327
328 res = get_persist_file(helper);
329 if (helper->ipf) {
330 res = IPersistFile_GetCurFile(helper->ipf, &olename);
331
332 if (res == S_OK) {
333 zend_string *str = php_com_olestring_to_string(olename, helper->codepage);
334 CoTaskMemFree(olename);
335 RETURN_STR(str);
336 } else if (res == S_FALSE) {
337 CoTaskMemFree(olename);
338 RETURN_FALSE;
339 }
340 php_com_throw_exception(res, NULL);
341 } else {
342 php_com_throw_exception(res, NULL);
343 }
344 }
345 /* }}} */
346
347
348 /* {{{ Persist object data to file, via IPersistFile::Save */
CPH_METHOD(SaveToFile)349 CPH_METHOD(SaveToFile)
350 {
351 HRESULT res;
352 char *filename, *fullpath = NULL;
353 size_t filename_len;
354 bool remember = TRUE;
355 OLECHAR *olefilename = NULL;
356 CPH_FETCH();
357
358 if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "p!|b",
359 &filename, &filename_len, &remember)) {
360 RETURN_THROWS();
361 }
362
363 CPH_NO_OBJ();
364
365 res = get_persist_file(helper);
366 if (helper->ipf) {
367 if (filename) {
368 fullpath = expand_filepath(filename, NULL);
369 if (!fullpath) {
370 RETURN_FALSE;
371 }
372
373 if (php_check_open_basedir(fullpath)) {
374 efree(fullpath);
375 RETURN_FALSE;
376 }
377
378 olefilename = php_com_string_to_olestring(fullpath, strlen(fullpath), helper->codepage);
379 efree(fullpath);
380 }
381 res = IPersistFile_Save(helper->ipf, olefilename, remember);
382 if (SUCCEEDED(res)) {
383 if (!olefilename) {
384 res = IPersistFile_GetCurFile(helper->ipf, &olefilename);
385 if (S_OK == res) {
386 IPersistFile_SaveCompleted(helper->ipf, olefilename);
387 CoTaskMemFree(olefilename);
388 olefilename = NULL;
389 }
390 } else if (remember) {
391 IPersistFile_SaveCompleted(helper->ipf, olefilename);
392 }
393 }
394
395 if (olefilename) {
396 efree(olefilename);
397 }
398
399 if (FAILED(res)) {
400 php_com_throw_exception(res, NULL);
401 }
402
403 } else {
404 php_com_throw_exception(res, NULL);
405 }
406 }
407 /* }}} */
408
409 /* {{{ Load object data from file, via IPersistFile::Load */
CPH_METHOD(LoadFromFile)410 CPH_METHOD(LoadFromFile)
411 {
412 HRESULT res;
413 char *filename, *fullpath;
414 size_t filename_len;
415 zend_long flags = 0;
416 OLECHAR *olefilename;
417 CPH_FETCH();
418
419 if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "p|l",
420 &filename, &filename_len, &flags)) {
421 RETURN_THROWS();
422 }
423
424 CPH_NO_OBJ();
425
426 res = get_persist_file(helper);
427 if (helper->ipf) {
428 if (!(fullpath = expand_filepath(filename, NULL))) {
429 RETURN_FALSE;
430 }
431
432 if (php_check_open_basedir(fullpath)) {
433 efree(fullpath);
434 RETURN_FALSE;
435 }
436
437 olefilename = php_com_string_to_olestring(fullpath, strlen(fullpath), helper->codepage);
438 efree(fullpath);
439
440 res = IPersistFile_Load(helper->ipf, olefilename, (DWORD)flags);
441 efree(olefilename);
442
443 if (FAILED(res)) {
444 php_com_throw_exception(res, NULL);
445 }
446
447 } else {
448 php_com_throw_exception(res, NULL);
449 }
450 }
451 /* }}} */
452
453 /* {{{ Gets maximum stream size required to store the object data, via IPersistStream::GetSizeMax (or IPersistStreamInit::GetSizeMax) */
CPH_METHOD(GetMaxStreamSize)454 CPH_METHOD(GetMaxStreamSize)
455 {
456 HRESULT res;
457 ULARGE_INTEGER size;
458 CPH_FETCH();
459
460 if (zend_parse_parameters_none() == FAILURE) {
461 RETURN_THROWS();
462 }
463
464 CPH_NO_OBJ();
465
466 res = get_persist_stream_init(helper);
467 if (helper->ipsi) {
468 res = IPersistStreamInit_GetSizeMax(helper->ipsi, &size);
469 } else {
470 res = get_persist_stream(helper);
471 if (helper->ips) {
472 res = IPersistStream_GetSizeMax(helper->ips, &size);
473 } else {
474 php_com_throw_exception(res, NULL);
475 RETURN_THROWS();
476 }
477 }
478
479 if (res != S_OK) {
480 php_com_throw_exception(res, NULL);
481 } else {
482 /* TODO: handle 64 bit properly */
483 RETURN_LONG((zend_long)size.QuadPart);
484 }
485 }
486 /* }}} */
487
488 /* {{{ Initializes the object to a default state, via IPersistStreamInit::InitNew */
CPH_METHOD(InitNew)489 CPH_METHOD(InitNew)
490 {
491 HRESULT res;
492 CPH_FETCH();
493
494 if (zend_parse_parameters_none() == FAILURE) {
495 RETURN_THROWS();
496 }
497
498 CPH_NO_OBJ();
499
500 res = get_persist_stream_init(helper);
501 if (helper->ipsi) {
502 res = IPersistStreamInit_InitNew(helper->ipsi);
503
504 if (res != S_OK) {
505 php_com_throw_exception(res, NULL);
506 } else {
507 RETURN_TRUE;
508 }
509 } else {
510 php_com_throw_exception(res, NULL);
511 }
512 }
513 /* }}} */
514
515 /* {{{ Initializes an object from the stream where it was previously saved, via IPersistStream::Load or OleLoadFromStream */
CPH_METHOD(LoadFromStream)516 CPH_METHOD(LoadFromStream)
517 {
518 zval *zstm;
519 php_stream *stream;
520 IStream *stm = NULL;
521 HRESULT res;
522 CPH_FETCH();
523
524 if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "r", &zstm)) {
525 RETURN_THROWS();
526 }
527
528 php_stream_from_zval_no_verify(stream, zstm);
529
530 if (stream == NULL) {
531 php_com_throw_exception(E_INVALIDARG, "expected a stream");
532 RETURN_THROWS();
533 }
534
535 stm = php_com_wrapper_export_stream(stream);
536 if (stm == NULL) {
537 php_com_throw_exception(E_UNEXPECTED, "failed to wrap stream");
538 RETURN_THROWS();
539 }
540
541 res = S_OK;
542 RETVAL_TRUE;
543
544 if (helper->unk == NULL) {
545 IDispatch *disp = NULL;
546
547 /* we need to create an object and load using OleLoadFromStream */
548 res = OleLoadFromStream(stm, &IID_IDispatch, &disp);
549
550 if (SUCCEEDED(res)) {
551 php_com_wrap_dispatch(return_value, disp, COMG(code_page));
552 }
553 } else {
554 res = get_persist_stream_init(helper);
555 if (helper->ipsi) {
556 res = IPersistStreamInit_Load(helper->ipsi, stm);
557 } else {
558 res = get_persist_stream(helper);
559 if (helper->ips) {
560 res = IPersistStreamInit_Load(helper->ipsi, stm);
561 }
562 }
563 }
564 IStream_Release(stm);
565
566 if (FAILED(res)) {
567 php_com_throw_exception(res, NULL);
568 RETURN_THROWS();
569 }
570 }
571 /* }}} */
572
573 /* {{{ Saves the object to a stream, via IPersistStream::Save */
CPH_METHOD(SaveToStream)574 CPH_METHOD(SaveToStream)
575 {
576 zval *zstm;
577 php_stream *stream;
578 IStream *stm = NULL;
579 HRESULT res;
580 CPH_FETCH();
581
582 CPH_NO_OBJ();
583
584 if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "r", &zstm)) {
585 RETURN_THROWS();
586 }
587
588 php_stream_from_zval_no_verify(stream, zstm);
589
590 if (stream == NULL) {
591 php_com_throw_exception(E_INVALIDARG, "expected a stream");
592 RETURN_THROWS();
593 }
594
595 stm = php_com_wrapper_export_stream(stream);
596 if (stm == NULL) {
597 php_com_throw_exception(E_UNEXPECTED, "failed to wrap stream");
598 RETURN_THROWS();
599 }
600
601 res = get_persist_stream_init(helper);
602 if (helper->ipsi) {
603 res = IPersistStreamInit_Save(helper->ipsi, stm, TRUE);
604 } else {
605 res = get_persist_stream(helper);
606 if (helper->ips) {
607 res = IPersistStream_Save(helper->ips, stm, TRUE);
608 }
609 }
610
611 IStream_Release(stm);
612
613 if (FAILED(res)) {
614 php_com_throw_exception(res, NULL);
615 RETURN_THROWS();
616 }
617
618 RETURN_TRUE;
619 }
620 /* }}} */
621
622 /* {{{ Creates a persistence helper object, usually associated with a com_object */
CPH_METHOD(__construct)623 CPH_METHOD(__construct)
624 {
625 php_com_dotnet_object *obj = NULL;
626 zval *zobj = NULL;
627 CPH_FETCH();
628
629 if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "|O!",
630 &zobj, php_com_variant_class_entry)) {
631 RETURN_THROWS();
632 }
633
634 if (!zobj) {
635 return;
636 }
637
638 obj = CDNO_FETCH(zobj);
639
640 if (V_VT(&obj->v) != VT_DISPATCH || V_DISPATCH(&obj->v) == NULL) {
641 php_com_throw_exception(E_INVALIDARG, "parameter must represent an IDispatch COM object");
642 RETURN_THROWS();
643 }
644
645 /* it is always safe to cast an interface to IUnknown */
646 helper->unk = (IUnknown*)V_DISPATCH(&obj->v);
647 IUnknown_AddRef(helper->unk);
648 helper->codepage = obj->code_page;
649 }
650 /* }}} */
651
652
helper_free_storage(zend_object * obj)653 static void helper_free_storage(zend_object *obj)
654 {
655 php_com_persist_helper *object = (php_com_persist_helper*)obj;
656
657 if (object->ipf) {
658 IPersistFile_Release(object->ipf);
659 }
660 if (object->ips) {
661 IPersistStream_Release(object->ips);
662 }
663 if (object->ipsi) {
664 IPersistStreamInit_Release(object->ipsi);
665 }
666 if (object->unk) {
667 IUnknown_Release(object->unk);
668 }
669 zend_object_std_dtor(&object->std);
670 }
671
672
helper_clone(zend_object * obj)673 static zend_object* helper_clone(zend_object *obj)
674 {
675 php_com_persist_helper *clone, *object = (php_com_persist_helper*) obj;
676
677 clone = emalloc(sizeof(*object));
678 memcpy(clone, object, sizeof(*object));
679
680 zend_object_std_init(&clone->std, object->std.ce);
681
682 if (clone->ipf) {
683 IPersistFile_AddRef(clone->ipf);
684 }
685 if (clone->ips) {
686 IPersistStream_AddRef(clone->ips);
687 }
688 if (clone->ipsi) {
689 IPersistStreamInit_AddRef(clone->ipsi);
690 }
691 if (clone->unk) {
692 IUnknown_AddRef(clone->unk);
693 }
694 return (zend_object*)clone;
695 }
696
helper_new(zend_class_entry * ce)697 static zend_object* helper_new(zend_class_entry *ce)
698 {
699 php_com_persist_helper *helper;
700
701 helper = emalloc(sizeof(*helper));
702 memset(helper, 0, sizeof(*helper));
703
704 zend_object_std_init(&helper->std, helper_ce);
705
706 return &helper->std;
707 }
708
php_com_persist_minit(INIT_FUNC_ARGS)709 void php_com_persist_minit(INIT_FUNC_ARGS)
710 {
711 memcpy(&helper_handlers, &std_object_handlers, sizeof(helper_handlers));
712 helper_handlers.free_obj = helper_free_storage;
713 helper_handlers.clone_obj = helper_clone;
714
715 helper_ce = register_class_COMPersistHelper();
716 helper_ce->create_object = helper_new;
717 helper_ce->default_object_handlers = &helper_handlers;
718 }
719