forked from root-project/root
-
Notifications
You must be signed in to change notification settings - Fork 0
/
RootWrapper.cxx
971 lines (798 loc) · 35.6 KB
/
RootWrapper.cxx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
// @(#)root/pyroot:$Id$
// Author: Wim Lavrijsen, Apr 2004
// Bindings
#include "PyROOT.h"
#include "PyStrings.h"
#include "RootWrapper.h"
#include "PyRootType.h"
#include "ObjectProxy.h"
#include "MethodProxy.h"
#include "TemplateProxy.h"
#include "PropertyProxy.h"
#include "Pythonize.h"
#include "TMethodHolder.h"
#include "TConstructorHolder.h"
#include "TClassMethodHolder.h"
#include "TFunctionHolder.h"
#include "TSetItemHolder.h"
#include "TMemoryRegulator.h"
#include "TTupleOfInstances.h"
#include "Utility.h"
// ROOT
#include "TROOT.h"
#include "TSystem.h"
#include "TDataMember.h"
#include "TClassEdit.h"
#include "TEnum.h"
#include "TEnumConstant.h"
#include "TInterpreter.h"
#include "TGlobal.h"
#include "DllImport.h"
// Standard
#include <map>
#include <set>
#include <string>
#include <algorithm>
#include <vector>
//- FOR CLING WORKAROUND
#include "TError.h"
//
//- data _______________________________________________________________________
namespace PyROOT {
R__EXTERN PyObject* gRootModule;
// TODO: move this to Cppyy.cxx (if possible) (and gPinnedTypes should be a hashmap)
R__EXTERN std::vector<std::pair<Cppyy::TCppType_t, Cppyy::TCppType_t> > gPinnedTypes;
R__EXTERN std::vector<Cppyy::TCppType_t> gIgnorePinnings;
}
namespace {
// to prevent having to walk scopes, track python classes by ROOT class
typedef std::map< Cppyy::TCppScope_t, PyObject* > PyClassMap_t;
PyClassMap_t gPyClasses;
// helper for creating new ROOT python types
PyObject* CreateNewROOTPythonClass( const std::string& name, PyObject* pybases )
{
// Create a new python shadow class with the required hierarchy and meta-classes.
Py_XINCREF( pybases );
if ( ! pybases ) {
pybases = PyTuple_New( 1 );
Py_INCREF( (PyObject*)(void*)&PyROOT::ObjectProxy_Type );
PyTuple_SET_ITEM( pybases, 0, (PyObject*)(void*)&PyROOT::ObjectProxy_Type );
}
PyObject* pymetabases = PyTuple_New( PyTuple_GET_SIZE( pybases ) );
for ( int i = 0; i < PyTuple_GET_SIZE( pybases ); ++i ) {
PyObject* btype = (PyObject*)Py_TYPE( PyTuple_GetItem( pybases, i ) );
Py_INCREF( btype );
PyTuple_SET_ITEM( pymetabases, i, btype );
}
PyObject* args = Py_BuildValue( (char*)"sO{}", (name+"_meta").c_str(), pymetabases );
Py_DECREF( pymetabases );
PyObject* pymeta = PyType_Type.tp_new( &PyROOT::PyRootType_Type, args, NULL );
Py_DECREF( args );
if ( ! pymeta ) {
PyErr_Print();
Py_DECREF( pybases );
return 0;
}
args = Py_BuildValue( (char*)"sO{}", name.c_str(), pybases );
PyObject* pyclass = ((PyTypeObject*)pymeta)->tp_new( (PyTypeObject*)pymeta, args, NULL );
Py_DECREF( args );
Py_DECREF( pymeta );
Py_DECREF( pybases );
return pyclass;
}
inline void AddPropertyToClass1(
PyObject* pyclass, PyROOT::PropertyProxy* property, Bool_t isStatic )
{
// allow access at the instance level
PyObject_SetAttrString( pyclass,
const_cast< char* >( property->GetName().c_str() ), (PyObject*)property );
// allow access at the class level (always add after setting instance level)
if ( isStatic ) {
PyObject_SetAttrString( (PyObject*)Py_TYPE(pyclass),
const_cast< char* >( property->GetName().c_str() ), (PyObject*)property );
}
}
void AddPropertyToClass( PyObject* pyclass,
Cppyy::TCppScope_t scope, Cppyy::TCppIndex_t idata )
{
PyROOT::PropertyProxy* property = PyROOT::PropertyProxy_New( scope, idata );
AddPropertyToClass1( pyclass, property, Cppyy::IsStaticData( scope, idata ) );
Py_DECREF( property );
}
void AddPropertyToClass( PyObject* pyclass,
Cppyy::TCppScope_t scope, const std::string& name, void* address )
{
PyROOT::PropertyProxy* property =
PyROOT::PropertyProxy_NewConstant( scope, name, address );
AddPropertyToClass1( pyclass, property, kTRUE );
Py_DECREF( property );
}
} // unnamed namespace
//- helpers --------------------------------------------------------------------
namespace {
using namespace PyROOT;
inline void AddToGlobalScope(
const char* label, const char* /* hdr */, TObject* obj, Cppyy::TCppType_t klass )
{
// Bind the given object with the given class in the global scope with the
// given label for its reference.
PyModule_AddObject( gRootModule, const_cast< char* >( label ),
PyROOT::BindCppObjectNoCast( obj, klass ) );
}
std::set< std::string > gSTLTypes, gSTLExceptions;
struct InitSTLTypes_t {
InitSTLTypes_t()
{
// Initialize the sets of known STL (container) types.
const std::string nss = "std::";
const char* stlTypes[] = { "complex", "exception",
"deque", "list", "queue", "stack", "vector",
"map", "multimap", "set", "multiset" };
for ( int i = 0; i < int(sizeof(stlTypes)/sizeof(stlTypes[0])); ++i ) {
gSTLTypes.insert( stlTypes[ i ] );
gSTLTypes.insert( nss + stlTypes[ i ] );
}
const char* stlExceptions[] = { "logic_error", "domain_error",
"invalid_argument", "length_error", "out_of_range", "runtime_error",
"range_error", "overflow_error", "underflow_error" };
for ( int i = 0; i < int(sizeof(stlExceptions)/sizeof(stlExceptions[0])); ++i ) {
gSTLExceptions.insert( stlExceptions[ i ] );
gSTLExceptions.insert( nss + stlExceptions[ i ] );
}
}
} initSTLTypes_;
Bool_t LoadDictionaryForSTLType( const std::string& tname, void* /* klass */ )
{
// if name is of a known STL class, tell CINT to load the dll(s), always reset klass
std::string sub = tname.substr( 0, tname.find( "<" ) );
if ( gSTLTypes.find( sub ) != gSTLTypes.end() ) {
// strip std:: part as needed to form proper file name
if ( sub.substr( 0, 5 ) == "std::" )
sub = sub.substr( 5, std::string::npos );
// tell CINT to go for it
gROOT->ProcessLine( (std::string( "#include <" ) + sub + ">").c_str() );
// prevent second attempt to load by erasing name
gSTLTypes.erase( gSTLTypes.find( sub ) );
gSTLTypes.erase( gSTLTypes.find( "std::" + sub ) );
return kTRUE;
} else if ( gSTLExceptions.find( sub ) != gSTLExceptions.end() ) {
// removal is required or the dictionary can't be updated properly
// TODO: WORK HERE if ( klass != 0 )
// TClass::RemoveClass( (TClass*)klass );
// load stdexcept, which contains all std exceptions
gROOT->ProcessLine( "#include <stdexcept>" );
gSTLExceptions.clear(); // completely done with std exceptions
// <stdexcept> will load <exception> for the std::exception base class
std::set< std::string >::iterator excpos = gSTLTypes.find( "exception" );
if ( excpos != gSTLTypes.end() ) {
gSTLTypes.erase( excpos );
gSTLTypes.erase( gSTLTypes.find( "std::exception" ) );
}
return kTRUE;
}
// this point is only reached if this is not an STL class, notify that no
// changes were made
return kFALSE;
}
} // unnamed namespace
//- public functions ---------------------------------------------------------
void PyROOT::InitRoot()
{
// setup interpreter locks to allow for threading in ROOT
PyEval_InitThreads();
// memory management
static TMemoryRegulator m;
gROOT->GetListOfCleanups()->Add( &m );
// bind ROOT globals that are needed in ROOT.py
AddToGlobalScope( "gROOT", "TROOT.h", gROOT, Cppyy::GetScope( gROOT->IsA()->GetName() ) );
AddToGlobalScope( "gSystem", "TSystem.h", gSystem, Cppyy::GetScope( gSystem->IsA()->GetName() ) );
AddToGlobalScope( "gInterpreter", "TInterpreter.h", gInterpreter, Cppyy::GetScope( gInterpreter->IsA()->GetName() ) );
}
////////////////////////////////////////////////////////////////////////////////
/// Collect methods and data for the given scope, and add them to the given python
/// proxy object.
static int BuildScopeProxyDict( Cppyy::TCppScope_t scope, PyObject* pyclass ) {
// some properties that'll affect building the dictionary
Bool_t isNamespace = Cppyy::IsNamespace( scope );
Bool_t hasConstructor = kFALSE;
// load all public methods and data members
typedef std::vector< PyCallable* > Callables_t;
typedef std::map< std::string, Callables_t > CallableCache_t;
CallableCache_t cache;
// bypass custom __getattr__ for efficiency
getattrofunc oldgetattro = Py_TYPE(pyclass)->tp_getattro;
Py_TYPE(pyclass)->tp_getattro = PyType_Type.tp_getattro;
// functions in namespaces are properly found through lazy lookup, so do not
// create them until needed (the same is not true for data members)
const Cppyy::TCppIndex_t nMethods =
Cppyy::IsNamespace( scope ) ? 0 : Cppyy::GetNumMethods( scope );
for ( Cppyy::TCppIndex_t imeth = 0; imeth < nMethods; ++imeth ) {
Cppyy::TCppMethod_t method = Cppyy::GetMethod( scope, imeth );
// process the method based on its name
std::string mtName = Cppyy::GetMethodName( method );
// special case trackers
Bool_t setupSetItem = kFALSE;
Bool_t isConstructor = Cppyy::IsConstructor( method );
// filter empty names (happens for namespaces, is bug?)
if ( mtName == "" )
continue;
// filter C++ destructors
if ( mtName[0] == '~' )
continue;
// translate operators
mtName = Utility::MapOperatorName( mtName, Cppyy::GetMethodNumArgs( method ) );
// operator[]/() returning a reference type will be used for __setitem__
if ( mtName == "__call__" || mtName == "__getitem__" ) {
const std::string& qual_return = Cppyy::ResolveName( Cppyy::GetMethodResultType( method ) );
if ( qual_return.find( "const", 0, 5 ) == std::string::npos ) {
const std::string& cpd = Utility::Compound( qual_return );
if ( ! cpd.empty() && cpd[ cpd.size() - 1 ] == '&' ) {
setupSetItem = kTRUE;
}
}
}
// decide on method type: member or static (which includes globals)
Bool_t isStatic = Cppyy::IsStaticMethod( method );
// template members; handled by adding a dispatcher to the class
std::string tmplName = "";
if ( ! (isNamespace || isStatic || isConstructor) && mtName[mtName.size()-1] == '>' ) {
tmplName = mtName.substr( 0, mtName.find('<') );
// TODO: the following is incorrect if both base and derived have the same
// templated method (but that is an unlikely scenario anyway)
PyObject* attr = PyObject_GetAttrString( pyclass, const_cast< char* >( tmplName.c_str() ) );
if ( ! TemplateProxy_Check( attr ) ) {
PyErr_Clear();
TemplateProxy* pytmpl = TemplateProxy_New( tmplName, pyclass );
if ( MethodProxy_Check( attr ) ) pytmpl->AddOverload( (MethodProxy*)attr );
PyObject_SetAttrString(
pyclass, const_cast< char* >( tmplName.c_str() ), (PyObject*)pytmpl );
Py_DECREF( pytmpl );
}
Py_XDECREF( attr );
// continue processing to actually add the method so that the proxy can find
// it on the class when called explicitly
}
// public methods are normally visible, private methods are mangled python-wise
// note the overload implications which are name based, and note that rootcint
// does not create the interface methods for private/protected methods ...
if ( ! Cppyy::IsPublicMethod( method ) ) {
if ( isConstructor ) // don't expose private ctors
continue;
else { // mangle private methods
const std::string& clName = TClassEdit::ShortType(
Cppyy::GetFinalName( scope ).c_str(), TClassEdit::kDropAlloc );
mtName = "_" + clName + "__" + mtName;
}
}
// construct the holder
PyCallable* pycall = 0;
if ( isStatic ) // class method
pycall = new TClassMethodHolder( scope, method );
else if ( isNamespace ) // free function
pycall = new TFunctionHolder( scope, method );
else if ( isConstructor ) { // constructor
pycall = new TConstructorHolder( scope, method );
mtName = "__init__";
hasConstructor = kTRUE;
} else // member function
pycall = new TMethodHolder( scope, method );
// lookup method dispatcher and store method
Callables_t& md = (*(cache.insert(
std::make_pair( mtName, Callables_t() ) ).first)).second;
md.push_back( pycall );
// special case for operator[]/() that returns by ref, use for getitem/call and setitem
if ( setupSetItem ) {
Callables_t& setitem = (*(cache.insert(
std::make_pair( std::string( "__setitem__" ), Callables_t() ) ).first)).second;
setitem.push_back( new TSetItemHolder( scope, method ) );
}
// special case for templates, add another call for the template name
if ( ! tmplName.empty() ) {
PyObject* attr = PyObject_GetAttrString( pyclass, const_cast< char* >( tmplName.c_str() ) );
((TemplateProxy*)attr)->AddTemplate( pycall->Clone() );
Py_DECREF( attr );
}
}
// add a pseudo-default ctor, if none defined
if ( ! isNamespace && ! hasConstructor )
cache[ "__init__" ].push_back( new TConstructorHolder( scope, (Cppyy::TCppMethod_t)0 ) );
// add the methods to the class dictionary
for ( CallableCache_t::iterator imd = cache.begin(); imd != cache.end(); ++imd ) {
// in order to prevent removing templated editions of this method (which were set earlier,
// above, as a different proxy object), we'll check and add this method flagged as a generic
// one (to be picked up by the templated one as appropriate) if a template exists
PyObject* attr = PyObject_GetAttrString( pyclass, const_cast< char* >( imd->first.c_str() ) );
if ( TemplateProxy_Check( attr ) ) {
// template exists, supply it with the non-templated method overloads
for ( Callables_t::iterator cit = imd->second.begin(); cit != imd->second.end(); ++cit )
((TemplateProxy*)attr)->AddOverload( *cit );
} else {
if ( ! attr ) PyErr_Clear();
// normal case, add a new method
MethodProxy* method = MethodProxy_New( imd->first, imd->second );
PyObject_SetAttrString(
pyclass, const_cast< char* >( method->GetName().c_str() ), (PyObject*)method );
Py_DECREF( method );
}
Py_XDECREF( attr ); // could have be found in base class or non-existent
}
// collect enums; this must happen before data members, so that we can check on their existence
TClass* klass = TClass::GetClass( Cppyy::GetFinalName( scope ).c_str() );
TList* enums = klass->GetListOfEnums();
TIter ienum( enums );
TEnum* e = 0;
while ( (e = (TEnum*)ienum.Next()) ) {
const TSeqCollection* seq = e->GetConstants();
for ( Int_t i = 0; i < seq->GetSize(); i++ ) {
TEnumConstant* ec = (TEnumConstant*)seq->At( i );
AddPropertyToClass( pyclass, scope, ec->GetName(), ec->GetAddress() );
}
}
// collect data members
const Cppyy::TCppIndex_t nDataMembers = Cppyy::GetNumDatamembers( scope );
for ( Cppyy::TCppIndex_t idata = 0; idata < nDataMembers; ++idata ) {
// allow only public members
if ( ! Cppyy::IsPublicData( scope, idata ) )
continue;
// enum datamembers (this in conjunction with previously collected enums above)
if ( Cppyy::IsEnumData( scope, idata ) && Cppyy::IsStaticData( scope, idata ) ) {
// some implementation-specific data members have no address: ignore them
if ( ! Cppyy::GetDatamemberOffset( scope, idata ) )
continue;
// two options: this is a static variable, or it is the enum value, the latter
// already exists, so check for it and move on if set
PyObject* eset = PyObject_GetAttrString( pyclass,
const_cast<char*>( Cppyy::GetDatamemberName( scope, idata ).c_str()) );
if ( eset ) {
Py_DECREF( eset );
continue;
}
PyErr_Clear();
// it could still be that this is an anonymous enum, which is not in the list
// provided by the class
if ( strstr( Cppyy::GetDatamemberType( scope, idata ).c_str(), "(anonymous)" ) != 0 ) {
AddPropertyToClass( pyclass, scope, idata );
continue;
}
}
// properties (aka public (static) data members)
AddPropertyToClass( pyclass, scope, idata );
}
// restore custom __getattr__
Py_TYPE(pyclass)->tp_getattro = oldgetattro;
// all ok, done
return 0;
}
////////////////////////////////////////////////////////////////////////////////
/// Build a tuple of python shadow classes of all the bases of the given 'klass'.
static PyObject* BuildCppClassBases( Cppyy::TCppType_t klass )
{
size_t nbases = Cppyy::GetNumBases( klass );
// collect bases while removing duplicates
std::vector< std::string > uqb;
uqb.reserve( nbases );
for ( size_t ibase = 0; ibase < nbases; ++ibase ) {
const std::string& name = Cppyy::GetBaseName( klass, ibase );
if ( std::find( uqb.begin(), uqb.end(), name ) == uqb.end() ) {
uqb.push_back( name );
}
}
// allocate a tuple for the base classes, special case for first base
nbases = uqb.size();
PyObject* pybases = PyTuple_New( nbases ? nbases : 1 );
if ( ! pybases )
return 0;
// build all the bases
if ( nbases == 0 ) {
Py_INCREF( (PyObject*)(void*)&ObjectProxy_Type );
PyTuple_SET_ITEM( pybases, 0, (PyObject*)(void*)&ObjectProxy_Type );
} else {
for ( std::vector< std::string >::size_type ibase = 0; ibase < nbases; ++ibase ) {
PyObject* pyclass = CreateScopeProxy( uqb[ ibase ] );
if ( ! pyclass ) {
Py_DECREF( pybases );
return 0;
}
PyTuple_SET_ITEM( pybases, ibase, pyclass );
}
// special case, if true python types enter the hierarchy, make sure that
// the first base seen is still the ObjectProxy_Type
if ( ! PyObject_IsSubclass( PyTuple_GET_ITEM( pybases, 0 ), (PyObject*)&ObjectProxy_Type ) ) {
PyObject* newpybases = PyTuple_New( nbases + 1 );
Py_INCREF( (PyObject*)(void*)&ObjectProxy_Type );
PyTuple_SET_ITEM( newpybases, 0, (PyObject*)(void*)&ObjectProxy_Type );
for ( int ibase = 0; ibase < (int)nbases; ++ibase ) {
PyObject* pyclass = PyTuple_GET_ITEM( pybases, ibase );
Py_INCREF( pyclass );
PyTuple_SET_ITEM( newpybases, ibase + 1, pyclass );
}
Py_DECREF( pybases );
pybases = newpybases;
}
}
return pybases;
}
////////////////////////////////////////////////////////////////////////////////
/// Retrieve scope proxy from the known ones.
PyObject* PyROOT::GetScopeProxy( Cppyy::TCppScope_t scope )
{
PyClassMap_t::iterator pci = gPyClasses.find( scope );
if ( pci != gPyClasses.end() ) {
PyObject* pyclass = PyWeakref_GetObject( pci->second );
if ( pyclass ) {
Py_INCREF( pyclass );
return pyclass;
}
}
return nullptr;
}
////////////////////////////////////////////////////////////////////////////////
/// Convenience function with a lookup first through the known existing proxies.
PyObject* PyROOT::CreateScopeProxy( Cppyy::TCppScope_t scope )
{
PyObject* pyclass = GetScopeProxy( scope );
if ( pyclass )
return pyclass;
return CreateScopeProxy( Cppyy::GetScopedFinalName( scope ) );
}
////////////////////////////////////////////////////////////////////////////////
/// Build a python shadow class for the named C++ class.
PyObject* PyROOT::CreateScopeProxy( PyObject*, PyObject* args )
{
std::string cname = PyROOT_PyUnicode_AsString( PyTuple_GetItem( args, 0 ) );
if ( PyErr_Occurred() )
return nullptr;
return CreateScopeProxy( cname );
}
////////////////////////////////////////////////////////////////////////////////
/// Build a python shadow class for the named C++ class.
PyObject* PyROOT::CreateScopeProxy( const std::string& scope_name, PyObject* parent )
{
if ( scope_name.empty() || scope_name == "std" ) {
// special cases, as gbl and gbl.std are defined in cppyy.py
PyObject* mods = PyImport_GetModuleDict();
PyObject* gbl = PyDict_GetItemString( mods, "cppyy.gbl" );
if ( gbl ) {
if ( scope_name.empty() ) {
Py_INCREF( gbl );
return gbl;
} else
return PyObject_GetAttrString( gbl, "std" );
}
PyErr_SetString( PyExc_SystemError, "could not locate global namespace" );
return nullptr;
}
// force building of the class if a parent is specified (prevents loops)
Bool_t force = parent != 0;
// working copy
std::string name = scope_name;
// determine complete scope name, if a python parent has been given
std::string scName = "";
if ( parent ) {
PyObject* pyparent = PyObject_GetAttr( parent, PyStrings::gName );
if ( ! pyparent ) {
PyErr_Format( PyExc_SystemError, "given scope has no name for %s", name.c_str() );
return 0;
}
// should be a string
scName = PyROOT_PyUnicode_AsString( pyparent );
Py_DECREF( pyparent );
if ( PyErr_Occurred() )
return 0;
// accept this parent scope and use it's name for prefixing
Py_INCREF( parent );
}
// retrieve ROOT class (this verifies name, and is therefore done first)
const std::string& lookup = parent ? (scName+"::"+name) : name;
Cppyy::TCppScope_t klass = Cppyy::GetScope( lookup );
if ( ! (Bool_t)klass || Cppyy::GetNumMethods( klass ) == 0 ) {
// special action for STL classes to enforce loading dict lib
// TODO: LoadDictionaryForSTLType should not be necessary with Cling
if ( LoadDictionaryForSTLType( name, (void*)klass /* TODO: VERY WRONG */ ) ) {
// lookup again, we (may) now have a full dictionary
klass = Cppyy::GetScope( lookup );
}
}
if ( ! (Bool_t)klass && gInterpreter->CheckClassTemplate( lookup.c_str() ) ) {
// a "naked" templated class is requested: return callable proxy for instantiations
PyObject* pytcl = PyObject_GetAttr( gRootModule, PyStrings::gTemplate );
PyObject* pytemplate = PyObject_CallFunction(
pytcl, const_cast< char* >( "s" ), const_cast< char* >( lookup.c_str() ) );
Py_DECREF( pytcl );
// cache the result
PyObject_SetAttrString( parent ? parent : gRootModule, (char*)name.c_str(), pytemplate );
// done, next step should be a call into this template
Py_XDECREF( parent );
return pytemplate;
}
if ( ! (Bool_t)klass ) { // if so, all options have been exhausted: it doesn't exist as such
if ( ! parent && scope_name.find( "ROOT::" ) == std::string::npos ) { // not already in ROOT::
// final attempt, for convenience, the "ROOT" namespace isn't required, try again ...
klass = Cppyy::GetScope( "ROOT::"+scope_name );
if ( (Bool_t)klass ) {
PyObject* rtns = PyObject_GetAttr( gRootModule, PyStrings::gROOTns );
PyObject* pyclass = CreateScopeProxy( scope_name, rtns );
Py_DECREF( rtns );
return pyclass;
}
}
PyErr_Format( PyExc_TypeError, "requested class \'%s\' does not exist", lookup.c_str() );
Py_XDECREF( parent );
return 0;
}
// locate class by ID, if possible, to prevent parsing scopes/templates anew
PyObject* pyscope = GetScopeProxy( klass );
if ( pyscope ) {
if ( parent ) PyObject_SetAttrString( parent, (char*)scope_name.c_str(), pyscope );
return pyscope;
}
// locate the parent, if necessary, for building the class if not specified
std::string::size_type last = 0;
if ( ! parent ) {
// need to deal with template paremeters that can have scopes themselves
Int_t tpl_open = 0;
for ( std::string::size_type pos = 0; pos < name.size(); ++pos ) {
std::string::value_type c = name[ pos ];
// count '<' and '>' to be able to skip template contents
if ( c == '<' )
++tpl_open;
else if ( c == '>' )
--tpl_open;
// by only checking for "::" the last part (class name) is dropped
else if ( tpl_open == 0 &&\
c == ':' && pos+1 < name.size() && name[ pos+1 ] == ':' ) {
// found a new scope part
const std::string& part = name.substr( last, pos-last );
PyObject* next = PyObject_GetAttrString(
parent ? parent : gRootModule, const_cast< char* >( part.c_str() ) );
if ( ! next ) { // lookup failed, try to create it
PyErr_Clear();
next = CreateScopeProxy( part, parent );
}
Py_XDECREF( parent );
if ( ! next ) // create failed, give up
return 0;
// found scope part
parent = next;
// done with part (note that pos is moved one ahead here)
last = pos+2; ++pos;
}
}
if ( parent && !PyRootType_Check( parent ) ) {
// Special case: parent found is not one of ours (it's e.g. a pure Python module), so
// continuing would fail badly. One final lookup, then out of here ...
std::string unscoped = scope_name.substr( last, std::string::npos );
return PyObject_GetAttrString( parent, unscoped.c_str() );
}
}
// use global scope if no inner scope found
if ( ! parent ) {
parent = gRootModule;
Py_INCREF( parent );
}
// use actual class name for binding
const std::string& actual = Cppyy::GetFinalName( klass );
// first try to retrieve an existing class representation
PyObject* pyactual = PyROOT_PyUnicode_FromString( actual.c_str() );
PyObject* pyclass = force ? 0 : PyObject_GetAttr( parent, pyactual );
Bool_t bClassFound = pyclass ? kTRUE : kFALSE;
// build if the class does not yet exist
if ( ! pyclass ) {
// ignore error generated from the failed lookup
PyErr_Clear();
// construct the base classes
PyObject* pybases = BuildCppClassBases( klass );
if ( pybases != 0 ) {
// create a fresh Python class, given bases, name, and empty dictionary
pyclass = CreateNewROOTPythonClass( actual, pybases );
Py_DECREF( pybases );
}
// fill the dictionary, if successful
if ( pyclass != 0 ) {
if ( BuildScopeProxyDict( klass, pyclass ) != 0 ) {
// something failed in building the dictionary
Py_DECREF( pyclass );
pyclass = 0;
} else
PyObject_SetAttr( parent, pyactual, pyclass );
}
}
if ( pyclass && name != actual ) // class exists, but is typedef-ed: simply map reference
PyObject_SetAttrString( parent, const_cast< char* >( name.c_str() ), pyclass );
Py_DECREF( pyactual );
Py_DECREF( parent );
if ( pyclass && ! bClassFound ) {
// store a ref from ROOT TClass to new python class
gPyClasses[ klass ] = PyWeakref_NewRef( pyclass, NULL );
// add a ref in the class to its scope
PyObject_SetAttrString( pyclass, "__scope__", PyROOT_PyUnicode_FromString( scName.c_str() ) );
}
if ( ! bClassFound ) { // add python-style features to newly minted classes
if ( ! Pythonize( pyclass, actual ) ) {
Py_XDECREF( pyclass );
pyclass = 0;
}
}
if ( pyclass && Cppyy::IsNamespace( klass ) && actual != "ROOT" ) {
// add to sys.modules to allow importing from this module
std::string pyfullname = lookup;
std::string::size_type pos = pyfullname.find( "::" );
while ( pos != std::string::npos ) {
pyfullname = pyfullname.replace( pos, 2, "." );
pos = pyfullname.find( "::", pos );
}
PyObject* modules = PySys_GetObject( const_cast<char*>("modules") );
if ( modules && PyDict_Check( modules) ) {
PyDict_SetItemString( modules,
const_cast<char*>(("ROOT."+pyfullname).c_str()), pyclass );
}
}
// all done
return pyclass;
}
////////////////////////////////////////////////////////////////////////////////
/// get the requested name
PyObject* PyROOT::GetCppGlobal( PyObject*, PyObject* args )
{
std::string ename = PyROOT_PyUnicode_AsString( PyTuple_GetItem( args, 0 ) );
if ( PyErr_Occurred() )
return 0;
return GetCppGlobal( ename );
}
////////////////////////////////////////////////////////////////////////////////
/// try named global variable/enum (first ROOT, then Cling: sync is too slow)
PyObject* PyROOT::GetCppGlobal( const std::string& name )
{
Cppyy::TCppIndex_t idata = Cppyy::GetDatamemberIndex( Cppyy::gGlobalScope, name );
if ( 0 <= idata )
return (PyObject*)PropertyProxy_New( Cppyy::gGlobalScope, idata );
// still here ... try functions (sync has been fixed, so is okay)
const std::vector< Cppyy::TCppMethod_t >& methods =
Cppyy::GetMethodsFromName( Cppyy::gGlobalScope, name );
if ( ! methods.empty() ) {
std::vector< PyCallable* > overloads;
for ( auto method : methods )
overloads.push_back( new TFunctionHolder( Cppyy::gGlobalScope, method ) );
return (PyObject*)MethodProxy_New( name, overloads );
}
// allow lookup into std as if global (historic)
TDataMember* dm = TClass::GetClass( "std" )->GetDataMember( name.c_str() );
if ( dm ) {
Cppyy::TCppType_t klass = Cppyy::GetScope( dm->GetTrueTypeName() );
return BindCppObjectNoCast( (void*)dm->GetOffset(), klass, kFALSE );
}
// nothing found
PyErr_Format( PyExc_LookupError, "no such global: %s", name.c_str() );
return 0;
}
////////////////////////////////////////////////////////////////////////////////
/// only known or knowable objects will be bound (null object is ok)
PyObject* PyROOT::BindCppObjectNoCast(
Cppyy::TCppObject_t address, Cppyy::TCppType_t klass, Bool_t isRef, Bool_t isValue ) {
if ( ! klass ) {
PyErr_SetString( PyExc_TypeError, "attempt to bind ROOT object w/o class" );
return 0;
}
// retrieve python class
PyObject* pyclass = CreateScopeProxy( klass );
if ( ! pyclass )
return 0; // error has been set in CreateScopeProxy
// instantiate an object of this class
PyObject* args = PyTuple_New(0);
ObjectProxy* pyobj =
(ObjectProxy*)((PyTypeObject*)pyclass)->tp_new( (PyTypeObject*)pyclass, args, NULL );
Py_DECREF( args );
Py_DECREF( pyclass );
// bind, register and return if successful
if ( pyobj != 0 ) { // fill proxy value?
// TODO: take flags directly instead of separate Bool_t args
unsigned flags = (isRef ? ObjectProxy::kIsReference : 0) | (isValue ? ObjectProxy::kIsValue : 0);
pyobj->Set( address, (ObjectProxy::EFlags)flags );
}
// successful completion
return (PyObject*)pyobj;
}
////////////////////////////////////////////////////////////////////////////////
/// if the object is a null pointer, return a typed one (as needed for overloading)
PyObject* PyROOT::BindCppObject( Cppyy::TCppObject_t address, Cppyy::TCppType_t klass, Bool_t isRef )
{
if ( ! address )
return BindCppObjectNoCast( address, klass, kFALSE );
// only known or knowable objects will be bound
if ( ! klass ) {
PyErr_SetString( PyExc_TypeError, "attempt to bind ROOT object w/o class" );
return 0;
}
// get actual class for recycling checking and/or downcasting
// CLING WORKAROUND -- silence:
// Error in <TStreamerInfo::Build>: __gnu_cxx::__normal_iterator<int*,vector<int> >, discarding: int* _M_current, no [dimension]
Int_t oldval = gErrorIgnoreLevel;
gErrorIgnoreLevel = 5000;
Cppyy::TCppType_t clActual = isRef ? 0 : Cppyy::GetActualClass( klass, address );
gErrorIgnoreLevel = oldval;
// obtain pointer to TObject base class (if possible) for memory mgmt; this is
// done before downcasting, as upcasting from the current class may be easier and
// downcasting is unnecessary if the python side object gets recycled by the
// memory regulator
TObject* object = 0;
static Cppyy::TCppScope_t sTObjectScope = Cppyy::GetScope( "TObject" );
if ( ! isRef && Cppyy::IsSubtype( klass, sTObjectScope) ) {
object = (TObject*)((Long_t)address + \
Cppyy::GetBaseOffset( klass, sTObjectScope, address, 1 /* up-cast */ ) );
// use the old reference if the object already exists
PyObject* oldPyObject = TMemoryRegulator::RetrieveObject( object, clActual ? clActual : klass );
if ( oldPyObject )
return oldPyObject;
}
// downcast to real class for object returns
if ( clActual && klass != clActual ) {
ptrdiff_t offset = Cppyy::GetBaseOffset(
clActual, klass, address, -1 /* down-cast */, true /* report errors */ );
if ( offset != -1 ) { // may fail if clActual not fully defined
address = (void*)((Long_t)address + offset);
klass = clActual;
}
}
// check if type is pinned
Bool_t ignore_pin = std::find(
gIgnorePinnings.begin(), gIgnorePinnings.end(), klass ) != gIgnorePinnings.end();
if ( ! ignore_pin ) {
for ( auto it = gPinnedTypes.cbegin(); it != gPinnedTypes.cend(); ++it ) {
if ( klass == std::get<0>(*it) || Cppyy::IsSubtype( klass, std::get<0>(*it) ) )
klass = std::get<1>(*it);
}
}
// actual binding
ObjectProxy* pyobj = (ObjectProxy*)BindCppObjectNoCast( address, klass, isRef );
// memory management, for TObject's only (for referenced objects, it is assumed
// that the (typically global) reference itself is zeroed out (or replaced) on
// destruction; it can't thus be reliably zeroed out from the python side)
if ( object && !(pyobj->fFlags & ObjectProxy::kIsReference) ) {
TMemoryRegulator::RegisterObject( pyobj, object );
}
// completion (returned object may be zero w/ a python exception set)
return (PyObject*)pyobj;
}
////////////////////////////////////////////////////////////////////////////////
/// TODO: this function exists for symmetry; need to figure out if it's useful
PyObject* PyROOT::BindCppObjectArray(
Cppyy::TCppObject_t address, Cppyy::TCppType_t klass, Int_t size ) {
return TTupleOfInstances_New( address, klass, size );
}
////////////////////////////////////////////////////////////////////////////////
/// gbl == 0 means global does not exist (rather than gbl is NULL pointer)
PyObject* PyROOT::BindCppGlobal( TGlobal* gbl )
{
if ( ! gbl || strcmp(gbl->GetName(), "") == 0 ) {
Py_INCREF( Py_None );
return Py_None;
}
// determine type and cast as appropriate
Cppyy::TCppType_t klass = Cppyy::GetScope( gbl->GetTypeName() );
if ( klass != 0 ) {
// handle array of objects
if ( gbl->GetArrayDim() == 1 ) {
return BindCppObjectArray( (void*)gbl->GetAddress(), klass, gbl->GetMaxIndex(0) );
} else if ( gbl->GetArrayDim() ) {
PyErr_SetString( PyExc_NotImplementedError,
"larger than 1D arrays of objects not supported" );
return 0;
}
// special case where there should be no casting:
// TODO: WORK HERE ... restore cast
//if ( klass->InheritsFrom( "ios_base" ) )
//return BindCppObjectNoCast( (void*)gbl->GetAddress(), klass );
// pointer types are bound "by-reference"
if ( Utility::Compound( gbl->GetFullTypeName() ) != "" )
return BindCppObject( (void*)gbl->GetAddress(), klass, kTRUE );
}
if ( gbl->GetAddress() && // check for enums and consts
(unsigned long)gbl->GetAddress() != (unsigned long)-1 && // Cling (??)
( gInterpreter->ClassInfo_IsEnum( gbl->GetTypeName() ) ) ) {
return PyInt_FromLong( (long)*((int*)gbl->GetAddress()) );
}
// no class and no enum: for built-in types, to ensure setability
PyObject* result = (PyObject*)PropertyProxy_New(
Cppyy::gGlobalScope, Cppyy::GetDatamemberIndex( Cppyy::gGlobalScope, gbl->GetName() ) );
return result;
}