Skip to content
Snippets Groups Projects
Executors.cxx 23.47 KiB
// @(#)root/pyroot:$Id$
// Author: Wim Lavrijsen, Jan 2005

// Bindings
#include "PyROOT.h"
#include "PyStrings.h"
#include "Executors.h"
#include "ObjectProxy.h"
#include "TPyBufferFactory.h"
#include "RootWrapper.h"
#include "Utility.h"

// ROOT
#include "TClass.h"
#include "TClassEdit.h"
#include "TInterpreter.h"
#include "TInterpreterValue.h"

// Standard
#include <cstring>
#include <utility>
#include <sstream>


//- data ______________________________________________________________________
PyROOT::ExecFactories_t PyROOT::gExecFactories;


//- helpers -------------------------------------------------------------------
static inline
void PRCallFuncExec( CallFunc_t* func, void* self, Bool_t release_gil ) {
   if ( release_gil ) {
      Py_BEGIN_ALLOW_THREADS
      gInterpreter->CallFunc_Exec( func, self );
      Py_END_ALLOW_THREADS
   } else
      gInterpreter->CallFunc_Exec( func, self );
}

static inline
Long_t PRCallFuncExecInt( CallFunc_t* func, void* self, Bool_t release_gil ) {
   Long_t result;
   if ( release_gil ) {
      Py_BEGIN_ALLOW_THREADS
      result = (Long_t)gInterpreter->CallFunc_ExecInt( func, self );
      Py_END_ALLOW_THREADS
   } else
      result = (Long_t)gInterpreter->CallFunc_ExecInt( func, self );
   return result;
}

static inline
Double_t PRCallFuncExecDouble( CallFunc_t* func, void* self, Bool_t release_gil ) {
   Double_t result;
   if ( release_gil ) {
      Py_BEGIN_ALLOW_THREADS
      result = (Double_t)gInterpreter->CallFunc_ExecDouble( func, self );
      Py_END_ALLOW_THREADS
   } else
      result = (Double_t)gInterpreter->CallFunc_ExecDouble( func, self );
   return result;
}

static inline
void PRCallFuncExecValue(  CallFunc_t* func, void* self, TInterpreterValue& value, Bool_t release_gil ) {
   if ( release_gil ) {
      Py_BEGIN_ALLOW_THREADS
      gInterpreter->CallFunc_Exec( func, self, value );
      Py_END_ALLOW_THREADS
   } else
      gInterpreter->CallFunc_Exec( func, self, value );
}

//- executors for built-ins ---------------------------------------------------
PyObject* PyROOT::TBoolExecutor::Execute( CallFunc_t* func, void* self, Bool_t release_gil )
{
// execute <func> with argument <self>, construct python bool return value
   PyObject* result =
      (Bool_t)PRCallFuncExecInt( func, self, release_gil ) ? Py_True : Py_False;
   Py_INCREF( result );
   return result;
}

PyObject* PyROOT::TLongExecutor::Execute( CallFunc_t* func, void* self, Bool_t release_gil )
{
// execute <func> with argument <self>, construct python long return value
   return PyLong_FromLong( (Long_t)PRCallFuncExecInt( func, self, release_gil ) );
}

//____________________________________________________________________________
PyObject* PyROOT::TCharExecutor::Execute( CallFunc_t* func, void* self, Bool_t release_gil )
{
// execute <func> with argument <self>, construct python string return value
   return PyROOT_PyUnicode_FromFormat( "%c", (Int_t)PRCallFuncExecInt( func, self, release_gil ) );
}

//____________________________________________________________________________
PyObject* PyROOT::TIntExecutor::Execute( CallFunc_t* func, void* self, Bool_t release_gil )
{
// execute <func> with argument <self>, construct python int return value
   return PyInt_FromLong( (Int_t)PRCallFuncExecInt( func, self, release_gil ) );
}

//____________________________________________________________________________
PyObject* PyROOT::TULongExecutor::Execute( CallFunc_t* func, void* self, Bool_t release_gil )
{
// execute <func> with argument <self>, construct python unsigned long return value
   return PyLong_FromUnsignedLong( (ULong_t)PRCallFuncExecInt( func, self, release_gil ) );
}

//____________________________________________________________________________
PyObject* PyROOT::TLongLongExecutor::Execute( CallFunc_t* func, void* self, Bool_t release_gil )
{
// execute <func> with argument <self>, construct python long long return value
// (CLING) TODO: this was returning a G__value
   Long64_t result;
   if ( release_gil ) {
      Py_BEGIN_ALLOW_THREADS
      result = (Long64_t)gInterpreter->CallFunc_ExecInt64( func, self );
      Py_END_ALLOW_THREADS
   } else
      result = (Long64_t)gInterpreter->CallFunc_ExecInt64( func, self );

   return PyLong_FromLongLong( result );
}

//____________________________________________________________________________
PyObject* PyROOT::TULongLongExecutor::Execute( CallFunc_t* func, void* self, Bool_t release_gil )
{
// execute <func> with argument <self>, construct python unsigned long long return value
// (CLING) TODO: this was returning a G__value
   ULong64_t result;
   if ( release_gil ) {
      Py_BEGIN_ALLOW_THREADS
      result = (ULong64_t)gInterpreter->CallFunc_ExecInt64( func, self );
      Py_END_ALLOW_THREADS
   } else
      result = (ULong64_t)gInterpreter->CallFunc_ExecInt64( func, self );

   return PyLong_FromUnsignedLongLong( result );
}

//____________________________________________________________________________
PyObject* PyROOT::TDoubleExecutor::Execute( CallFunc_t* func, void* self, Bool_t release_gil )
{
// execute <func> with argument <self>, construct python float return value
   return PyFloat_FromDouble( (Double_t)PRCallFuncExecDouble( func, self, release_gil ) );
}

//____________________________________________________________________________
Bool_t PyROOT::TRefExecutor::SetAssignable( PyObject* pyobject )
{
// prepare "buffer" for by-ref returns, used with __setitem__
   if ( pyobject != 0 ) {
      Py_INCREF( pyobject );
      fAssignable = pyobject;
      return kTRUE;
   }

   fAssignable = 0;
   return kFALSE;
}

//____________________________________________________________________________
#define PYROOT_IMPLEMENT_BASIC_REFEXECUTOR( name, type, stype, F1, F2, CF )  \
PyObject* PyROOT::T##name##RefExecutor::Execute( CallFunc_t* func, void* self, Bool_t release_gil )\
{                                                                            \
   type* ref = (type*)PRCallFuncExecInt( func, self, release_gil );          \
   if ( ! fAssignable )                                                      \
      return F1( (stype)*ref );                                              \
   else {                                                                    \
      *ref = (type)F2( fAssignable );                                        \
      Py_DECREF( fAssignable );                                              \
      fAssignable = 0;                                                       \
      Py_INCREF( Py_None );                                                  \
      return Py_None;                                                        \
   }                                                                         \
}

PYROOT_IMPLEMENT_BASIC_REFEXECUTOR( Short,  Short_t,  Long_t,   PyInt_FromLong,     PyLong_AsLong,    PRCallFuncExecInt )
PYROOT_IMPLEMENT_BASIC_REFEXECUTOR( UShort, UShort_t, ULong_t,  PyInt_FromLong,     PyLongOrInt_AsULong, PRCallFuncExecInt )
PYROOT_IMPLEMENT_BASIC_REFEXECUTOR( Int,    Int_t,    Long_t,   PyInt_FromLong,     PyLong_AsLong,    PRCallFuncExecInt )
PYROOT_IMPLEMENT_BASIC_REFEXECUTOR( UInt,   UInt_t,   ULong_t,  PyLong_FromUnsignedLong, PyLongOrInt_AsULong, PRCallFuncExecInt )
PYROOT_IMPLEMENT_BASIC_REFEXECUTOR( Long,   Long_t,   Long_t,   PyLong_FromLong,    PyLong_AsLong,    PRCallFuncExecInt )
PYROOT_IMPLEMENT_BASIC_REFEXECUTOR( ULong,  ULong_t,  ULong_t,  PyLong_FromUnsignedLong, PyLongOrInt_AsULong, PRCallFuncExecInt )
PYROOT_IMPLEMENT_BASIC_REFEXECUTOR( Float,  Float_t,  Double_t, PyFloat_FromDouble, PyFloat_AsDouble, PRCallFuncExecDouble )
PYROOT_IMPLEMENT_BASIC_REFEXECUTOR( Double, Double_t, Double_t, PyFloat_FromDouble, PyFloat_AsDouble, PRCallFuncExecDouble )

//____________________________________________________________________________
PyObject* PyROOT::TSTLStringRefExecutor::Execute( CallFunc_t* func, void* self, Bool_t release_gil )
{
// execute <func> with argument <self>, return python string return value
   if ( ! fAssignable ) {
      std::string* result = (std::string*)PRCallFuncExecInt( func, self, release_gil );
      return PyROOT_PyUnicode_FromStringAndSize( result->c_str(), result->size() );
   } else {
      std::string* result = (std::string*)PRCallFuncExecInt( func, self, release_gil );
      *result = std::string(
         PyROOT_PyUnicode_AsString( fAssignable ), PyROOT_PyUnicode_GET_SIZE( fAssignable ) );

      Py_DECREF( fAssignable );
      fAssignable = 0;

      Py_INCREF( Py_None );
      return Py_None;
   }
}

//____________________________________________________________________________
PyObject* PyROOT::TVoidExecutor::Execute( CallFunc_t* func, void* self, Bool_t release_gil )
{
// execute <func> with argument <self>, return None
   PRCallFuncExec( func, self, release_gil );
   Py_INCREF( Py_None );
   return Py_None;
}

//____________________________________________________________________________
PyObject* PyROOT::TCStringExecutor::Execute( CallFunc_t* func, void* self, Bool_t release_gil )
{
// execute <func> with argument <self>, construct python string return value
   char* result = (char*)PRCallFuncExecInt( func, self, release_gil );
   if ( ! result ) {
      Py_INCREF( PyStrings::gEmptyString );
      return PyStrings::gEmptyString;
   }

   return PyROOT_PyUnicode_FromString( result );
}


//- pointer/array executors ---------------------------------------------------
PyObject* PyROOT::TVoidArrayExecutor::Execute( CallFunc_t* func, void* self, Bool_t release_gil )
{
// execute <func> with argument <self>, construct python long return value
   return BufFac_t::Instance()->PyBuffer_FromMemory( (Long_t*)PRCallFuncExecInt( func, self, release_gil ), 1 );
}

//____________________________________________________________________________
#define PYROOT_IMPLEMENT_ARRAY_EXECUTOR( name, type )                        \
PyObject* PyROOT::T##name##ArrayExecutor::Execute( CallFunc_t* func, void* self, Bool_t release_gil )\
{                                                                            \
   return BufFac_t::Instance()->PyBuffer_FromMemory( (type*)PRCallFuncExecInt( func, self, release_gil ) );\
}

PYROOT_IMPLEMENT_ARRAY_EXECUTOR( Bool,   Bool_t )
PYROOT_IMPLEMENT_ARRAY_EXECUTOR( Short,  Short_t )
PYROOT_IMPLEMENT_ARRAY_EXECUTOR( UShort, UShort_t )
PYROOT_IMPLEMENT_ARRAY_EXECUTOR( Int,    Int_t )
PYROOT_IMPLEMENT_ARRAY_EXECUTOR( UInt,   UInt_t )
PYROOT_IMPLEMENT_ARRAY_EXECUTOR( Long,   Long_t )
PYROOT_IMPLEMENT_ARRAY_EXECUTOR( ULong,  ULong_t )
PYROOT_IMPLEMENT_ARRAY_EXECUTOR( Float,  Float_t )
PYROOT_IMPLEMENT_ARRAY_EXECUTOR( Double, Double_t )


//- special cases ------------------------------------------------------------
PyObject* PyROOT::TSTLStringExecutor::Execute( CallFunc_t* func, void* self, Bool_t release_gil )
{
// TODO: Cling can not handle return by value for now
   PyErr_SetString(PyExc_NotImplementedError, "CLING DOES NOT SUPPORT RETURN BY VALUE!" );
   return 0;

// execute <func> with argument <self>, construct python string return value
   std::string* result = (std::string*)PRCallFuncExecInt( func, self, release_gil );
   if ( ! result ) {
      Py_INCREF( PyStrings::gEmptyString );
      return PyStrings::gEmptyString;
   }

   PyObject* pyresult =
      PyROOT_PyUnicode_FromStringAndSize( result->c_str(), result->size() );

// TODO: take over ownership from Cling somehow (old:  G__pop_tempobject_nodel(); delete result;)

   return pyresult;
}

//____________________________________________________________________________
PyObject* PyROOT::TTGlobalExecutor::Execute( CallFunc_t* func, void* self, Bool_t release_gil )
{
// execute <func> with argument <self>, construct python ROOT object return value
   return BindRootGlobal( (TGlobal*)PRCallFuncExecInt( func, self, release_gil ) );
}

//____________________________________________________________________________
PyObject* PyROOT::TRootObjectExecutor::Execute( CallFunc_t* func, void* self, Bool_t release_gil )
{
// execute <func> with argument <self>, construct python ROOT object return value
   return BindRootObject( (void*)PRCallFuncExecInt( func, self, release_gil ), fClass );
}

//____________________________________________________________________________
PyObject* PyROOT::TRootObjectByValueExecutor::Execute( CallFunc_t* func, void* self, Bool_t release_gil )
{
// execution will bring a temporary in existence
   void* result = 0;

// CLING WORKAROUND (ROOT-5202): it's not clear when an object is returned as
// struct, and when as a builtin value; for 64b, the cut-off appears to be 16 bytes
   if ( fClass->Size() <= 16 ) {   // return by value
      result = (void*)PRCallFuncExecInt( func, self, release_gil );
   } else {
// -- CLING WORKAROUND
      TInterpreterValue value;
      PRCallFuncExecValue( func, self, value, release_gil );
      result = value.GetAsPointer();
   }

   if ( ! result ) {
      if ( ! PyErr_Occurred() )         // callee may have set a python error itself
         PyErr_SetString( PyExc_ValueError, "NULL result where temporary expected" );
      return 0;
   }

// TODO: there's no guarantee that bit-wise copy is correct ...
   void* fresh = malloc( fClass->Size() );
   std::memcpy( fresh, result, fClass->Size() );

// TODO: there's no guarantee that we're the only user of temp objects; this as well as
// the bitwise-copy issue means that it'd be better if the ownership could be transferred
   gInterpreter->ClearStack();     // currently a no-op for Cling (?)
                            
// the result can then be bound
   ObjectProxy* pyobj = (ObjectProxy*)BindRootObjectNoCast( fresh, fClass );
   if ( ! pyobj )
      return 0;

// python ref counting will now control this object's life span
   pyobj->fFlags |= ObjectProxy::kIsOwner;
   return (PyObject*)pyobj;
}

//____________________________________________________________________________
PyObject* PyROOT::TRootObjectRefExecutor::Execute( CallFunc_t* func, void* self, Bool_t release_gil )
{
// executor binds the result to the left-hand side, overwriting if an old object
   PyObject* result = BindRootObject( (void*)PRCallFuncExecInt( func, self, release_gil ), fClass );
   if ( ! result || ! fAssignable )
      return result;
   else {
   // this generic code is quite slow compared to its C++ equivalent ...
      PyObject* res2 = PyObject_CallMethod( result,
         const_cast< char* >( "__assign__" ), const_cast< char* >( "O" ), fAssignable );

      Py_DECREF( result );
      Py_DECREF( fAssignable );
      fAssignable = 0;

      if ( res2 ) {
         Py_DECREF( res2 );             // typically, *this from operator=()
         Py_INCREF( Py_None );
         return Py_None;
      }

      return 0;
   }
}

//____________________________________________________________________________
PyObject* PyROOT::TRootObjectPtrExecutor::Execute( CallFunc_t* func, void* self, Bool_t release_gil )
{
// execute <func> with argument <self>, construct python ROOT object return ptr value
   return BindRootObject( (void*)PRCallFuncExecInt( func, self, release_gil ), fClass, kTRUE );
}

//____________________________________________________________________________
PyObject* PyROOT::TConstructorExecutor::Execute( CallFunc_t* func, void* klass, Bool_t release_gil )
{
// package return address in PyObject* for caller to handle appropriately
   return (PyObject*)PRCallFuncExecInt( func, klass, release_gil );
}

//____________________________________________________________________________
PyObject* PyROOT::TPyObjectExecutor::Execute( CallFunc_t* func, void* self, Bool_t release_gil )
{
// execute <func> with argument <self>, return python object
   return (PyObject*)PRCallFuncExecInt( func, self, release_gil );
}


//- factories -----------------------------------------------------------------
PyROOT::TExecutor* PyROOT::CreateExecutor( const std::string& fullType )
{
// The matching of the fulltype to an executor factory goes through up to 4 levels:
//   1) full, qualified match
//   2) drop '&' as by ref/full type is often pretty much the same python-wise
//   3) ROOT classes, either by ref/ptr or by value
//   4) additional special case for enums
//
// If all fails, void is used, which will cause the return type to be ignored on use

// an exactly matching executor is best
   ExecFactories_t::iterator h = gExecFactories.find( fullType );
   if ( h != gExecFactories.end() )
      return (h->second)();

// resolve typedefs etc., and collect qualifiers
   std::string resolvedType = TClassEdit::ResolveTypedef( fullType.c_str(), true );

// a full, qualified matching executor is preferred
   h = gExecFactories.find( resolvedType );
   if ( h != gExecFactories.end() )
      return (h->second)();

//-- nothing? ok, collect information about the type and possible qualifiers/decorators
   const std::string& cpd = Utility::Compound( resolvedType );
   std::string realType = TClassEdit::ShortType( resolvedType.c_str(), 1 );

// const-ness (dropped by TClassEdit::ShortType) is in general irrelevant
   h = gExecFactories.find( realType + cpd );
   if ( h != gExecFactories.end() )
      return (h->second)();

// ROOT classes and special cases (enum)
   TExecutor* result = 0;
   if ( TClass* klass = TClass::GetClass( realType.c_str() ) ) {
      if ( cpd == "" )
         result = new TRootObjectByValueExecutor( klass );
      else if ( cpd == "&" )
         result = new TRootObjectRefExecutor( klass );
      else if ( cpd == "**" || cpd == "*&" || cpd == "&*" )
         result = new TRootObjectPtrExecutor( klass );
      else
         result = new TRootObjectExecutor( klass );
   } else if ( gInterpreter->ClassInfo_IsEnum( realType.c_str() ) ) {
   // could still be an enum ...
      h = gExecFactories.find( "UInt_t" );
   } else {
   // handle (with warning) unknown types
      std::stringstream s;
      s << "creating executor for unknown type \"" << fullType << "\"" << std::ends;
      PyErr_Warn( PyExc_RuntimeWarning, (char*)s.str().c_str() );
   // void* may work ("user knows best"), void will fail on use of return value
      h = (cpd == "") ? gExecFactories.find( "void" ) : gExecFactories.find( "void*" );
   }

   if ( ! result && h != gExecFactories.end() )
   // executor factory available, use it to create executor
      result = (h->second)();

   return result;                  // may still be null
}

//____________________________________________________________________________
#define PYROOT_EXECUTOR_FACTORY( name )                \
TExecutor* Create##name##Executor()                    \
{                                                      \
   return new T##name##Executor;                       \
}

namespace {

   using namespace PyROOT;

// use macro rather than template for portability ...
   PYROOT_EXECUTOR_FACTORY( Bool )
   PYROOT_EXECUTOR_FACTORY( Char )
   PYROOT_EXECUTOR_FACTORY( ShortRef )
   PYROOT_EXECUTOR_FACTORY( UShortRef )
   PYROOT_EXECUTOR_FACTORY( Int )
   PYROOT_EXECUTOR_FACTORY( IntRef )
   PYROOT_EXECUTOR_FACTORY( UIntRef )
   PYROOT_EXECUTOR_FACTORY( ULong )
   PYROOT_EXECUTOR_FACTORY( ULongRef )
   PYROOT_EXECUTOR_FACTORY( Long )
   PYROOT_EXECUTOR_FACTORY( LongRef )
   PYROOT_EXECUTOR_FACTORY( FloatRef )
   PYROOT_EXECUTOR_FACTORY( Double )
   PYROOT_EXECUTOR_FACTORY( DoubleRef )
   PYROOT_EXECUTOR_FACTORY( Void )
   PYROOT_EXECUTOR_FACTORY( LongLong )
   PYROOT_EXECUTOR_FACTORY( ULongLong )
   PYROOT_EXECUTOR_FACTORY( CString )
   PYROOT_EXECUTOR_FACTORY( VoidArray )
   PYROOT_EXECUTOR_FACTORY( BoolArray )
   PYROOT_EXECUTOR_FACTORY( ShortArray )
   PYROOT_EXECUTOR_FACTORY( UShortArray )
   PYROOT_EXECUTOR_FACTORY( IntArray )
   PYROOT_EXECUTOR_FACTORY( UIntArray )
   PYROOT_EXECUTOR_FACTORY( LongArray )
   PYROOT_EXECUTOR_FACTORY( ULongArray )
   PYROOT_EXECUTOR_FACTORY( FloatArray )
   PYROOT_EXECUTOR_FACTORY( DoubleArray )
   PYROOT_EXECUTOR_FACTORY( STLString )
   PYROOT_EXECUTOR_FACTORY( STLStringRef )
   PYROOT_EXECUTOR_FACTORY( TGlobal )
   PYROOT_EXECUTOR_FACTORY( Constructor )
   PYROOT_EXECUTOR_FACTORY( PyObject )
// executor factories for ROOT types
   typedef std::pair< const char*, ExecutorFactory_t > NFp_t;

   NFp_t factories_[] = {
   // factories for built-ins
      NFp_t( "char",               &CreateCharExecutor                ),
      NFp_t( "unsigned char",      &CreateCharExecutor                ),
      NFp_t( "short",              &CreateIntExecutor                 ),
      NFp_t( "short&",             &CreateShortRefExecutor            ),
      NFp_t( "unsigned short",     &CreateIntExecutor                 ),
      NFp_t( "unsigned short&",    &CreateUShortRefExecutor           ),
      NFp_t( "int",                &CreateIntExecutor                 ),
      NFp_t( "int&",               &CreateIntRefExecutor              ),
      NFp_t( "unsigned int",       &CreateULongExecutor               ),
      NFp_t( "unsigned int&",      &CreateUIntRefExecutor             ),
      NFp_t( "UInt_t", /* enum */  &CreateULongExecutor               ),
      NFp_t( "long",               &CreateLongExecutor                ),
      NFp_t( "long&",              &CreateLongRefExecutor             ),
      NFp_t( "unsigned long",      &CreateULongExecutor               ),
      NFp_t( "unsigned long&",     &CreateULongRefExecutor            ),
      NFp_t( "long long",          &CreateLongLongExecutor            ),
      NFp_t( "Long64_t",           &CreateLongLongExecutor            ),
      NFp_t( "unsigned long long", &CreateULongLongExecutor           ),
      NFp_t( "ULong64_t",          &CreateULongLongExecutor           ),
      NFp_t( "float",              &CreateDoubleExecutor              ),
      NFp_t( "float&",             &CreateFloatRefExecutor            ),
      NFp_t( "double",             &CreateDoubleExecutor              ),
      NFp_t( "double&",            &CreateDoubleRefExecutor           ),
      NFp_t( "void",               &CreateVoidExecutor                ),
      NFp_t( "bool",               &CreateBoolExecutor                ),
      NFp_t( "const char*",        &CreateCStringExecutor             ),
      NFp_t( "char*",              &CreateCStringExecutor             ),

   // pointer/array factories
      NFp_t( "void*",              &CreateVoidArrayExecutor           ),
      NFp_t( "bool*",              &CreateBoolArrayExecutor           ),
      NFp_t( "short*",             &CreateShortArrayExecutor          ),
      NFp_t( "unsigned short*",    &CreateUShortArrayExecutor         ),
      NFp_t( "int*",               &CreateIntArrayExecutor            ),
      NFp_t( "unsigned int*",      &CreateUIntArrayExecutor           ),
      NFp_t( "long*",              &CreateLongArrayExecutor           ),
      NFp_t( "unsigned long*",     &CreateULongArrayExecutor          ),
      NFp_t( "float*",             &CreateFloatArrayExecutor          ),
      NFp_t( "double*",            &CreateDoubleArrayExecutor         ),

   // factories for special cases
      NFp_t( "std::string",        &CreateSTLStringExecutor           ),
      NFp_t( "string",             &CreateSTLStringExecutor           ),
      NFp_t( "std::string&",       &CreateSTLStringRefExecutor        ),
      NFp_t( "string&",            &CreateSTLStringRefExecutor        ),
      NFp_t( "TGlobal*",           &CreateTGlobalExecutor             ),
      NFp_t( "__init__",           &CreateConstructorExecutor         ),
      NFp_t( "PyObject*",          &CreatePyObjectExecutor            ),
      NFp_t( "_object*",           &CreatePyObjectExecutor            ),
      NFp_t( "FILE*",              &CreateVoidArrayExecutor           )
   };

   struct InitExecFactories_t {
   public:
      InitExecFactories_t()
      {
      // load all executor factories in the global map 'gExecFactories'
         int nf = sizeof( factories_ ) / sizeof( factories_[ 0 ] );
         for ( int i = 0; i < nf; ++i ) {
            gExecFactories[ factories_[ i ].first ] = factories_[ i ].second;
         }
      }
   } initExecvFactories_;

} // unnamed namespace