]> git.saurik.com Git - wxWidgets.git/blame_incremental - src/common/xtistrm.cpp
fixing overrelease and out-of-bounds write, fixes #13725
[wxWidgets.git] / src / common / xtistrm.cpp
... / ...
CommitLineData
1/////////////////////////////////////////////////////////////////////////////
2// Name: src/common/xtistrm.cpp
3// Purpose: streaming runtime metadata information
4// Author: Stefan Csomor
5// Modified by:
6// Created: 27/07/03
7// RCS-ID: $Id$
8// Copyright: (c) 2003 Stefan Csomor
9// Licence: wxWindows licence
10/////////////////////////////////////////////////////////////////////////////
11
12// For compilers that support precompilation, includes "wx.h".
13#include "wx/wxprec.h"
14
15#ifdef __BORLANDC__
16 #pragma hdrstop
17#endif
18
19#if wxUSE_EXTENDED_RTTI
20
21#include "wx/xtistrm.h"
22
23#ifndef WX_PRECOMP
24 #include "wx/object.h"
25 #include "wx/hash.h"
26 #include "wx/event.h"
27#endif
28
29#include "wx/tokenzr.h"
30#include "wx/txtstrm.h"
31
32// STL headers:
33
34#include "wx/beforestd.h"
35#include <map>
36#include <vector>
37#include <string>
38#include "wx/afterstd.h"
39using namespace std;
40
41
42// ----------------------------------------------------------------------------
43// wxObjectWriter
44// ----------------------------------------------------------------------------
45
46struct wxObjectWriter::wxObjectWriterInternal
47{
48 map< const wxObject*, int > m_writtenObjects;
49 int m_nextId;
50};
51
52wxObjectWriter::wxObjectWriter()
53{
54 m_data = new wxObjectWriterInternal;
55 m_data->m_nextId = 0;
56}
57
58wxObjectWriter::~wxObjectWriter()
59{
60 delete m_data;
61}
62
63struct wxObjectWriter::wxObjectWriterInternalPropertiesData
64{
65 char nothing;
66};
67
68void wxObjectWriter::ClearObjectContext()
69{
70 delete m_data;
71 m_data = new wxObjectWriterInternal();
72 m_data->m_nextId = 0;
73}
74
75void wxObjectWriter::WriteObject(const wxObject *object, const wxClassInfo *classInfo,
76 wxObjectWriterCallback *writercallback, const wxString &name,
77 const wxStringToAnyHashMap &metadata )
78{
79 DoBeginWriteTopLevelEntry( name );
80 WriteObject( object, classInfo, writercallback, false, metadata);
81 DoEndWriteTopLevelEntry( name );
82}
83
84void wxObjectWriter::WriteObject(const wxObject *object, const wxClassInfo *classInfo,
85 wxObjectWriterCallback *writercallback, bool isEmbedded,
86 const wxStringToAnyHashMap &metadata )
87{
88 if ( !classInfo->BeforeWriteObject( object, this, writercallback, metadata) )
89 return;
90
91 if ( writercallback->BeforeWriteObject( this, object, classInfo, metadata) )
92 {
93 if ( object == NULL )
94 DoWriteNullObject();
95 else if ( IsObjectKnown( object ) )
96 DoWriteRepeatedObject( GetObjectID(object) );
97 else
98 {
99 int oid = m_data->m_nextId++;
100 if ( !isEmbedded )
101 m_data->m_writtenObjects[object] = oid;
102
103 // in case this object is a wxDynamicObject we also have to insert is superclass
104 // instance with the same id, so that object relations are streamed out correctly
105 const wxDynamicObject* dynobj = wx_dynamic_cast(const wxDynamicObject*, object);
106 if ( !isEmbedded && dynobj )
107 m_data->m_writtenObjects[dynobj->GetSuperClassInstance()] = oid;
108
109 DoBeginWriteObject( object, classInfo, oid, metadata );
110 wxObjectWriterInternalPropertiesData data;
111 WriteAllProperties( object, classInfo, writercallback, &data );
112 DoEndWriteObject( object, classInfo, oid );
113 }
114 writercallback->AfterWriteObject( this,object, classInfo );
115 }
116}
117
118void wxObjectWriter::FindConnectEntry(const wxEvtHandler * evSource,
119 const wxEventSourceTypeInfo* dti,
120 const wxObject* &sink,
121 const wxHandlerInfo *&handler)
122{
123 wxList *dynamicEvents = evSource->GetDynamicEventTable();
124
125 if ( dynamicEvents )
126 {
127 for ( wxList::const_iterator node = dynamicEvents->begin(); node != dynamicEvents->end(); ++node )
128 {
129 wxDynamicEventTableEntry *entry = (wxDynamicEventTableEntry*)(*node);
130
131 // find the match
132 if ( entry->m_fn &&
133 (dti->GetEventType() == entry->m_eventType) &&
134 (entry->m_id == -1 ) &&
135 (entry->m_fn->GetEvtHandler() != NULL ) )
136 {
137 sink = entry->m_fn->GetEvtHandler();
138 const wxClassInfo* sinkClassInfo = sink->GetClassInfo();
139 const wxHandlerInfo* sinkHandler = sinkClassInfo->GetFirstHandler();
140 while ( sinkHandler )
141 {
142 if ( sinkHandler->GetEventFunction() == entry->m_fn->GetEvtMethod() )
143 {
144 handler = sinkHandler;
145 break;
146 }
147 sinkHandler = sinkHandler->GetNext();
148 }
149 break;
150 }
151 }
152 }
153}
154void wxObjectWriter::WriteAllProperties( const wxObject * obj, const wxClassInfo* ci,
155 wxObjectWriterCallback *writercallback,
156 wxObjectWriterInternalPropertiesData * data )
157{
158 wxPropertyInfoMap map;
159 ci->GetProperties( map );
160 for ( int i = 0; i < ci->GetCreateParamCount(); ++i )
161 {
162 wxString name = ci->GetCreateParamName(i);
163 wxPropertyInfoMap::const_iterator iter = map.find(name);
164 const wxPropertyInfo* prop = iter == map.end() ? NULL : iter->second;
165 if ( prop )
166 {
167 WriteOneProperty( obj, prop->GetDeclaringClass(), prop, writercallback, data );
168 }
169 else
170 {
171 wxLogError( _("Create Parameter %s not found in declared RTTI Parameters"), name.c_str() );
172 }
173 map.erase( name );
174 }
175 { // Extra block for broken compilers
176 for( wxPropertyInfoMap::iterator iter = map.begin(); iter != map.end(); ++iter )
177 {
178 const wxPropertyInfo* prop = iter->second;
179 if ( prop->GetFlags() & wxPROP_OBJECT_GRAPH )
180 {
181 WriteOneProperty( obj, prop->GetDeclaringClass(), prop, writercallback, data );
182 }
183 }
184 }
185 { // Extra block for broken compilers
186 for( wxPropertyInfoMap::iterator iter = map.begin(); iter != map.end(); ++iter )
187 {
188 const wxPropertyInfo* prop = iter->second;
189 if ( !(prop->GetFlags() & wxPROP_OBJECT_GRAPH) )
190 {
191 WriteOneProperty( obj, prop->GetDeclaringClass(), prop, writercallback, data );
192 }
193 }
194 }
195}
196
197class WXDLLIMPEXP_BASE wxObjectPropertyWriter: public wxObjectWriterFunctor
198{
199public:
200 wxObjectPropertyWriter(const wxClassTypeInfo* cti,
201 wxObjectWriterCallback *writercallback,
202 wxObjectWriter* writer,
203 wxStringToAnyHashMap &props) :
204 m_cti(cti),m_persister(writercallback),m_writer(writer),m_props(props)
205 {}
206
207 virtual void operator()(const wxObject *vobj)
208 {
209 m_writer->WriteObject( vobj, (vobj ? vobj->GetClassInfo() : m_cti->GetClassInfo() ),
210 m_persister, m_cti->GetKind()== wxT_OBJECT, m_props );
211 }
212private:
213 const wxClassTypeInfo* m_cti;
214 wxObjectWriterCallback *m_persister;
215 wxObjectWriter* m_writer;
216 wxStringToAnyHashMap& m_props;
217};
218
219void wxObjectWriter::WriteOneProperty( const wxObject *obj, const wxClassInfo* ci,
220 const wxPropertyInfo* pi, wxObjectWriterCallback *writercallback,
221 wxObjectWriterInternalPropertiesData *WXUNUSED(data) )
222{
223 if ( pi->GetFlags() & wxPROP_DONT_STREAM )
224 return;
225
226 // make sure that we are picking the correct object for accessing the property
227 const wxDynamicObject* dynobj = wx_dynamic_cast(const wxDynamicObject*, obj );
228 if ( dynobj && (wx_dynamic_cast(const wxDynamicClassInfo*, ci) == NULL) )
229 obj = dynobj->GetSuperClassInstance();
230
231 if ( pi->GetTypeInfo()->GetKind() == wxT_COLLECTION )
232 {
233 wxAnyList data;
234 pi->GetAccessor()->GetPropertyCollection(obj, data);
235 const wxTypeInfo * elementType =
236 wx_dynamic_cast( const wxCollectionTypeInfo*, pi->GetTypeInfo() )->GetElementType();
237 if ( !data.empty() )
238 {
239 DoBeginWriteProperty( pi );
240 for ( wxAnyList::const_iterator iter = data.begin(); iter != data.end(); ++iter )
241 {
242 DoBeginWriteElement();
243 const wxAny* valptr = *iter;
244 if ( writercallback->BeforeWriteProperty( this, obj, pi, *valptr ) )
245 {
246 const wxClassTypeInfo* cti =
247 wx_dynamic_cast( const wxClassTypeInfo*, elementType );
248 if ( cti )
249 {
250 wxStringToAnyHashMap md;
251 wxObjectPropertyWriter pw(cti,writercallback,this, md);
252
253 const wxClassInfo* pci = cti->GetClassInfo();
254 pci->CallOnAny( *valptr, &pw);
255 }
256 else
257 {
258 DoWriteSimpleType( *valptr );
259 }
260 }
261 DoEndWriteElement();
262 }
263 DoEndWriteProperty( pi );
264 }
265 }
266 else
267 {
268 const wxEventSourceTypeInfo* dti =
269 wx_dynamic_cast( const wxEventSourceTypeInfo* , pi->GetTypeInfo() );
270 if ( dti )
271 {
272 const wxObject* sink = NULL;
273 const wxHandlerInfo *handler = NULL;
274
275 const wxEvtHandler * evSource = wx_dynamic_cast(const wxEvtHandler *, obj);
276 if ( evSource )
277 {
278 FindConnectEntry( evSource, dti, sink, handler );
279 if ( writercallback->BeforeWriteDelegate( this, obj, ci, pi, sink, handler ) )
280 {
281 if ( sink != NULL && handler != NULL )
282 {
283 DoBeginWriteProperty( pi );
284 if ( IsObjectKnown( sink ) )
285 {
286 DoWriteDelegate( obj, ci, pi, sink, GetObjectID( sink ),
287 sink->GetClassInfo(), handler );
288 DoEndWriteProperty( pi );
289 }
290 else
291 {
292 wxLogError( wxT("Streaming delegates for not already ")
293 wxT("streamed objects not yet supported") );
294 }
295 }
296 }
297 }
298 else
299 {
300 wxLogError(_("Illegal Object Class (Non-wxEvtHandler) as Event Source") );
301 }
302 }
303 else
304 {
305 wxAny value;
306 pi->GetAccessor()->GetProperty(obj, value);
307
308 // avoid streaming out void objects
309 // TODO Verify
310 if( value.IsNull() )
311 return;
312
313 if ( pi->GetFlags() & wxPROP_ENUM_STORE_LONG )
314 {
315 const wxEnumTypeInfo *eti =
316 wx_dynamic_cast(const wxEnumTypeInfo*, pi->GetTypeInfo() );
317 if ( eti )
318 {
319 eti->ConvertFromLong( wxANY_AS(value, long ), value );
320 }
321 else
322 {
323 wxLogError( _("Type must have enum - long conversion") );
324 }
325 }
326
327 // avoid streaming out default values
328 if ( pi->GetTypeInfo()->HasStringConverters() &&
329 !pi->GetDefaultValue().IsNull() ) // TODO Verify
330 {
331 if ( wxAnyGetAsString(value) == wxAnyGetAsString(pi->GetDefaultValue()) )
332 return;
333 }
334
335 // avoid streaming out null objects
336 const wxClassTypeInfo* cti =
337 wx_dynamic_cast( const wxClassTypeInfo* , pi->GetTypeInfo() );
338
339 if ( cti && cti->GetKind() == wxT_OBJECT_PTR && wxAnyGetAsObjectPtr(value) == NULL )
340 return;
341
342 if ( writercallback->BeforeWriteProperty( this, obj, pi, value ) )
343 {
344 DoBeginWriteProperty( pi );
345 if ( cti )
346 {
347 if ( cti->HasStringConverters() )
348 {
349 wxString stringValue;
350 cti->ConvertToString( value, stringValue );
351 wxAny convertedValue(stringValue);
352 DoWriteSimpleType( convertedValue );
353 }
354 else
355 {
356 wxStringToAnyHashMap md;
357 wxObjectPropertyWriter pw(cti,writercallback,this, md);
358
359 const wxClassInfo* pci = cti->GetClassInfo();
360 pci->CallOnAny(value, &pw);
361 }
362 }
363 else
364 {
365 DoWriteSimpleType( value );
366 }
367 DoEndWriteProperty( pi );
368 }
369 }
370 }
371}
372
373int wxObjectWriter::GetObjectID(const wxObject *obj)
374{
375 if ( !IsObjectKnown( obj ) )
376 return wxInvalidObjectID;
377
378 return m_data->m_writtenObjects[obj];
379}
380
381bool wxObjectWriter::IsObjectKnown( const wxObject *obj )
382{
383 return m_data->m_writtenObjects.find( obj ) != m_data->m_writtenObjects.end();
384}
385
386
387// ----------------------------------------------------------------------------
388// wxObjectReader
389// ----------------------------------------------------------------------------
390
391struct wxObjectReader::wxObjectReaderInternal
392{
393 map<int,wxClassInfo*> m_classInfos;
394};
395
396wxObjectReader::wxObjectReader()
397{
398 m_data = new wxObjectReaderInternal;
399}
400
401wxObjectReader::~wxObjectReader()
402{
403 delete m_data;
404}
405
406wxClassInfo* wxObjectReader::GetObjectClassInfo(int objectID)
407{
408 if ( objectID == wxNullObjectID || objectID == wxInvalidObjectID )
409 {
410 wxLogError( _("Invalid or Null Object ID passed to GetObjectClassInfo" ) );
411 return NULL;
412 }
413 if ( m_data->m_classInfos.find(objectID) == m_data->m_classInfos.end() )
414 {
415 wxLogError( _("Unknown Object passed to GetObjectClassInfo" ) );
416 return NULL;
417 }
418 return m_data->m_classInfos[objectID];
419}
420
421void wxObjectReader::SetObjectClassInfo(int objectID, wxClassInfo *classInfo )
422{
423 if ( objectID == wxNullObjectID || objectID == wxInvalidObjectID )
424 {
425 wxLogError( _("Invalid or Null Object ID passed to GetObjectClassInfo" ) );
426 return;
427 }
428 if ( m_data->m_classInfos.find(objectID) != m_data->m_classInfos.end() )
429 {
430 wxLogError( _("Already Registered Object passed to SetObjectClassInfo" ) );
431 return;
432 }
433 m_data->m_classInfos[objectID] = classInfo;
434}
435
436bool wxObjectReader::HasObjectClassInfo( int objectID )
437{
438 if ( objectID == wxNullObjectID || objectID == wxInvalidObjectID )
439 {
440 wxLogError( _("Invalid or Null Object ID passed to HasObjectClassInfo" ) );
441 return false;
442 }
443 return m_data->m_classInfos.find(objectID) != m_data->m_classInfos.end();
444}
445
446
447// ----------------------------------------------------------------------------
448// reading xml in
449// ----------------------------------------------------------------------------
450
451/*
452Reading components has not to be extended for components
453as properties are always sought by typeinfo over all levels
454and create params are always toplevel class only
455*/
456
457
458// ----------------------------------------------------------------------------
459// wxObjectRuntimeReaderCallback - depersisting to memory
460// ----------------------------------------------------------------------------
461
462struct wxObjectRuntimeReaderCallback::wxObjectRuntimeReaderCallbackInternal
463{
464 map<int,wxObject *> m_objects;
465
466 void SetObject(int objectID, wxObject *obj )
467 {
468 if ( m_objects.find(objectID) != m_objects.end() )
469 {
470 wxLogError( _("Passing a already registered object to SetObject") );
471 return ;
472 }
473 m_objects[objectID] = obj;
474 }
475 wxObject* GetObject( int objectID )
476 {
477 if ( objectID == wxNullObjectID )
478 return NULL;
479 if ( m_objects.find(objectID) == m_objects.end() )
480 {
481 wxLogError( _("Passing an unknown object to GetObject") );
482 return NULL;
483 }
484
485 return m_objects[objectID];
486 }
487};
488
489wxObjectRuntimeReaderCallback::wxObjectRuntimeReaderCallback()
490{
491 m_data = new wxObjectRuntimeReaderCallbackInternal();
492}
493
494wxObjectRuntimeReaderCallback::~wxObjectRuntimeReaderCallback()
495{
496 delete m_data;
497}
498
499void wxObjectRuntimeReaderCallback::AllocateObject(int objectID, wxClassInfo *classInfo,
500 wxStringToAnyHashMap &WXUNUSED(metadata))
501{
502 wxObject *O;
503 O = classInfo->CreateObject();
504 m_data->SetObject(objectID, O);
505}
506
507void wxObjectRuntimeReaderCallback::CreateObject(int objectID,
508 const wxClassInfo *classInfo,
509 int paramCount,
510 wxAny *params,
511 int *objectIdValues,
512 const wxClassInfo **objectClassInfos,
513 wxStringToAnyHashMap &WXUNUSED(metadata))
514{
515 wxObject *o;
516 o = m_data->GetObject(objectID);
517 for ( int i = 0; i < paramCount; ++i )
518 {
519 if ( objectIdValues[i] != wxInvalidObjectID )
520 {
521 wxObject *o;
522 o = m_data->GetObject(objectIdValues[i]);
523 // if this is a dynamic object and we are asked for another class
524 // than wxDynamicObject we cast it down manually.
525 wxDynamicObject *dyno = wx_dynamic_cast( wxDynamicObject *, o);
526 if ( dyno!=NULL && (objectClassInfos[i] != dyno->GetClassInfo()) )
527 {
528 o = dyno->GetSuperClassInstance();
529 }
530 params[i] = objectClassInfos[i]->ObjectPtrToAny(o);
531 }
532 }
533 classInfo->Create(o, paramCount, params);
534}
535
536void wxObjectRuntimeReaderCallback::ConstructObject(int objectID,
537 const wxClassInfo *classInfo,
538 int paramCount,
539 wxAny *params,
540 int *objectIdValues,
541 const wxClassInfo **objectClassInfos,
542 wxStringToAnyHashMap &WXUNUSED(metadata))
543{
544 wxObject *o;
545 for ( int i = 0; i < paramCount; ++i )
546 {
547 if ( objectIdValues[i] != wxInvalidObjectID )
548 {
549 wxObject *o;
550 o = m_data->GetObject(objectIdValues[i]);
551 // if this is a dynamic object and we are asked for another class
552 // than wxDynamicObject we cast it down manually.
553 wxDynamicObject *dyno = wx_dynamic_cast( wxDynamicObject *, o);
554 if ( dyno!=NULL && (objectClassInfos[i] != dyno->GetClassInfo()) )
555 {
556 o = dyno->GetSuperClassInstance();
557 }
558 params[i] = objectClassInfos[i]->ObjectPtrToAny(o);
559 }
560 }
561 o = classInfo->ConstructObject(paramCount, params);
562 m_data->SetObject(objectID, o);
563}
564
565
566void wxObjectRuntimeReaderCallback::DestroyObject(int objectID, wxClassInfo *WXUNUSED(classInfo))
567{
568 wxObject *o;
569 o = m_data->GetObject(objectID);
570 delete o;
571}
572
573void wxObjectRuntimeReaderCallback::SetProperty(int objectID,
574 const wxClassInfo *classInfo,
575 const wxPropertyInfo* propertyInfo,
576 const wxAny &value)
577{
578 wxObject *o;
579 o = m_data->GetObject(objectID);
580 classInfo->SetProperty( o, propertyInfo->GetName().c_str(), value );
581}
582
583void wxObjectRuntimeReaderCallback::SetPropertyAsObject(int objectID,
584 const wxClassInfo *classInfo,
585 const wxPropertyInfo* propertyInfo,
586 int valueObjectId)
587{
588 wxObject *o, *valo;
589 o = m_data->GetObject(objectID);
590 valo = m_data->GetObject(valueObjectId);
591 const wxClassInfo* valClassInfo =
592 (wx_dynamic_cast(const wxClassTypeInfo*,propertyInfo->GetTypeInfo()))->GetClassInfo();
593
594 // if this is a dynamic object and we are asked for another class
595 // than wxDynamicObject we cast it down manually.
596 wxDynamicObject *dynvalo = wx_dynamic_cast( wxDynamicObject *, valo);
597 if ( dynvalo!=NULL && (valClassInfo != dynvalo->GetClassInfo()) )
598 {
599 valo = dynvalo->GetSuperClassInstance();
600 }
601
602 classInfo->SetProperty( o, propertyInfo->GetName().c_str(),
603 valClassInfo->ObjectPtrToAny(valo) );
604}
605
606void wxObjectRuntimeReaderCallback::SetConnect(int eventSourceObjectID,
607 const wxClassInfo *WXUNUSED(eventSourceClassInfo),
608 const wxPropertyInfo *delegateInfo,
609 const wxClassInfo *WXUNUSED(eventSinkClassInfo),
610 const wxHandlerInfo* handlerInfo,
611 int eventSinkObjectID )
612{
613 wxEvtHandler *ehsource =
614 wx_dynamic_cast( wxEvtHandler* , m_data->GetObject( eventSourceObjectID ) );
615 wxEvtHandler *ehsink =
616 wx_dynamic_cast( wxEvtHandler *,m_data->GetObject(eventSinkObjectID) );
617
618 if ( ehsource && ehsink )
619 {
620 const wxEventSourceTypeInfo *delegateTypeInfo =
621 wx_dynamic_cast(const wxEventSourceTypeInfo*,delegateInfo->GetTypeInfo());
622 if( delegateTypeInfo && delegateTypeInfo->GetLastEventType() == -1 )
623 {
624 ehsource->Connect( -1, delegateTypeInfo->GetEventType(),
625 handlerInfo->GetEventFunction(), NULL /*user data*/,
626 ehsink );
627 }
628 else
629 {
630 for ( wxEventType iter = delegateTypeInfo->GetEventType();
631 iter <= delegateTypeInfo->GetLastEventType(); ++iter )
632 {
633 ehsource->Connect( -1, iter,
634 handlerInfo->GetEventFunction(), NULL /*user data*/,
635 ehsink );
636 }
637 }
638 }
639}
640
641wxObject *wxObjectRuntimeReaderCallback::GetObject(int objectID)
642{
643 return m_data->GetObject( objectID );
644}
645
646void wxObjectRuntimeReaderCallback::AddToPropertyCollection( int objectID,
647 const wxClassInfo *classInfo,
648 const wxPropertyInfo* propertyInfo,
649 const wxAny &value)
650{
651 wxObject *o;
652 o = m_data->GetObject(objectID);
653 classInfo->AddToPropertyCollection( o, propertyInfo->GetName().c_str(), value );
654}
655
656void wxObjectRuntimeReaderCallback::AddToPropertyCollectionAsObject(int objectID,
657 const wxClassInfo *classInfo,
658 const wxPropertyInfo* propertyInfo,
659 int valueObjectId)
660{
661 wxObject *o, *valo;
662 o = m_data->GetObject(objectID);
663 valo = m_data->GetObject(valueObjectId);
664 const wxCollectionTypeInfo * collectionTypeInfo =
665 wx_dynamic_cast( const wxCollectionTypeInfo *, propertyInfo->GetTypeInfo() );
666 const wxClassInfo* valClassInfo =
667 (wx_dynamic_cast(const wxClassTypeInfo*,collectionTypeInfo->GetElementType()))->GetClassInfo();
668
669 // if this is a dynamic object and we are asked for another class
670 // than wxDynamicObject we cast it down manually.
671 wxDynamicObject *dynvalo = wx_dynamic_cast( wxDynamicObject *, valo);
672 if ( dynvalo!=NULL && (valClassInfo != dynvalo->GetClassInfo()) )
673 {
674 valo = dynvalo->GetSuperClassInstance();
675 }
676
677 classInfo->AddToPropertyCollection( o, propertyInfo->GetName().c_str(),
678 valClassInfo->ObjectPtrToAny(valo) );
679}
680
681#endif // wxUSE_EXTENDED_RTTI