How do I add method dynamically to module using C API?

Alf P. Steinbach /Usenet alf.p.steinbach+usenet at gmail.com
Thu Jul 8 04:48:54 EDT 2010


* Martin v. Loewis, on 08.07.2010 09:13:
>> I tried (1) adding a __del__, but no dice, I guess
>> because it wasn't really an object method but just a free function in a
>> module; and (2) the m_free callback in the module definition structure,
>> but it was not called.
>
> m_free will be called if the module object gets deallocated. So if
> m_free really isn't called, the module got never deallocated.

Thanks again. I don't know what I did wrong. Now it's called. :-)

But I wasted much time googling to try to find out the /responsibilities/ of the 
m_free callback, and what its void* argument was. E.g., should it deallocate the 
module object, and if so, via what deallocation routine? I found some info, but 
even your PEP, otherwise clear, was silent about this fine point.

Finally I looked at the source code that invokes it and found that it has no 
responsibilities whatsoever, just a use-as-you-wish finalization callback. Nice!

But I think that could be more clear in the docs...

Code, for those who might be interested:


<code>
// progrock.cppy  --  "C++ plus Python"
// A simple C++ framework for writing Python 3.x extensions.
//
// Copyright (C) Alf P. Steinbach, 2010.

#ifndef CPPY_MODULE_H
#define CPPY_MODULE_H
#include <progrock/cppx/devsupport/better_experience.h>


//----------------------------------------- Dependencies:

#include "Ptr.h"
#include <progrock/cppx/exception/throwing.h>
#include <list>



//----------------------------------------- Interface:

namespace progrock{ namespace cppy {

     namespace detail {
         struct InstanceData
         {
             std::list< PyMethodDef >    methodDefs;
         };

         inline void* instanceMemoryOf( PyObject* pObject )
         {
             void* const result = PyModule_GetState( pObject );
             assert( result != 0 );
             return result;
         }

         inline InstanceData*& instanceDataPtrOf( PyObject* pObject )
         {
             return *reinterpret_cast< InstanceData** >(
                 instanceMemoryOf( pObject )
                 );
         }

         inline void on_freeModule( void* p )
         {
             PyObject* const     pModule = reinterpret_cast< PyObject* >( p );
             InstanceData* const pData   = instanceDataPtrOf( pModule );

             delete pData;
             printf( "Deallocated!\n" );     // TODO: Remove.
         }

         inline PyModuleDef* moduleDefPtr()
         {
             static PyMethodDef methodDefs[] = {
                 //...
                 //{ "system", &pyni_system, METH_VARARGS, "Execute a shell 
command." },
                 //{ "__del__", &onModuleDestroy, METH_NOARGS, "Destructor" },
                 //...
                 { NULL, NULL, 0, NULL }     // Sentinel
             };

             static PyModuleDef moduleDef = {
                 PyModuleDef_HEAD_INIT,
                 "cppy",         // name of module
                 NULL,           // m_doc,   // module documentation in UTF-8
                 sizeof(void*),         // size of per-interpreter state of the 
module,
                                 //    or -1 if the module keeps state in global 
variables.
                 methodDefs,
                 NULL,           // m_reload
                 NULL,           // m_traverse
                 NULL,           // m_clear
                 &on_freeModule  // m_free
             };

             return &moduleDef;
         }
     }

     class Module
     {
     private:
         Ptr                     p_;
         detail::InstanceData*   data_;

         detail::InstanceData*& instanceDataPtr() const
         {
             return detail::instanceDataPtrOf( p_.get() );
         }

     public:
         Module()
             : p_( ::PyModule_Create( detail::moduleDefPtr() ) )
             , data_( 0 )
         {
             assert( detail::moduleDefPtr()->m_size == sizeof( void* ) );
             (p_.get() != 0) || cppx::throwX( "Module::<init>: failed" );
             instanceDataPtr() = data_ = new detail::InstanceData();
         }

         PyObject* rawPtr() const    { return p_.get(); }
         PyObject* release()         { return p_.release(); }

         void setDocString( wchar_t const s[] )
         {
             Ptr const v = ::PyUnicode_FromWideChar( s, -1 );
             (v.get() != 0)
                 || cppx::throwX( "Module::setDocString: PyUnicode_FromWideChar 
failed" );

             ::PyObject_SetAttrString( p_.get(), "__doc__", v.get() )
                 >> cppx::is( cppx::notMinusOne )
                 || cppx::throwX( "Module::setDocString: PyObject_SetAttrString 
failed" );
         }

         void addRoutine( char const name[], PyCFunction f, char const doc[] = "" )
         {
             PyMethodDef const defData = { name, f, METH_VARARGS, doc };

             data_->methodDefs.push_back( defData );
             try
             {
                 PyMethodDef* const  pDef    = &data_->methodDefs.back();

                 Ptr const   pyName  = ::PyUnicode_FromString( name );
                 (pyName.get() != 0)
                     || cppx::throwX( "Module::addRoutine: PyUnicode_FromString 
failed" );

                 Ptr         r       = ::PyCFunction_NewEx( pDef, p_.get(), 
pyName.get());
                 (r.get() != 0)
                     || cppx::throwX( "Module::addRoutine: PyCFunction_NewEx 
failed" );

                 ::PyModule_AddObject( p_.get(), name, r.release() )
                     >> cppx::is( cppx::notMinusOne )
                     || cppx::throwX( "Module::addRoutine: PyModule_AddObject 
failed" );
             }
             catch( ... )
             {
                 data_->methodDefs.pop_back();
                 throw;
             }
         }
     };

} }  // namespace progrock::cppy


#endif
</code>


Cheers,

- Alf

-- 
blog at <url: http://alfps.wordpress.com>



More information about the Python-list mailing list