From: Alf P. Steinbach /Usenet on
The code below, very much work in progress, just trying things, is C++.

Sorry about the formatting, I had to reformat manually for this posting:


<code>
class Module
{
private:
Ptr p_;

public:
Module( PyModuleDef const& def )
: p_( ::PyModule_Create( const_cast< PyModuleDef* >( &def ) ) )
{
(p_.get() != 0) || cppx::throwX( "Module::<init>: failed" );
}

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( "PyUnicode_FromWideChar failed" );
int const _ = ::PyObject_SetAttrString(
p_.get(), "__doc__", v.get()
);
(_ != -1 )
|| cppx::throwX( "PyObject_SetAttrString failed" );
}

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

Ptr const pyName = ::PyUnicode_FromString( name );
Ptr r = ::PyCFunction_NewEx(
&def, p_.get(), pyName.get()
);

int const _ = ::PyModule_AddObject( p_.get(), name, r.release() );
(_ != -1 )
|| cppx::throwX( "PyModule_AddObject failed" );
}
};
</code>


Problem: when a routine installed by 'setRoutine' above is called from Python,
then it fails with e.g. "SystemError: Bad call flags in PyCFunction_Call.
METH_OLDARGS is no longer supported!"

And since things work for a single method when I declare 'def' as 'static', I
suspect that means that the function object created by PyCFunction_NewEx holds
on to a pointer to the PyMethodDef structure?

I'm unable to find documentation of PyCFunction_NewEx, and more criticially, I'm
unable to find any documented way to turn a C or C++ function into a Python
function object?


Cheers,

- Alf

--
blog at <url: http://alfps.wordpress.com>
From: Martin v. Loewis on
> And since things work for a single method when I declare 'def' as
> 'static', I suspect that means that the function object created by
> PyCFunction_NewEx holds on to a pointer to the PyMethodDef structure?

Correct; it doesn't make a copy of the struct. So when you want the
function object to outlive the setRoutine call, you need to allocate
the PyMethodDef on the heap.

Regards,
Martin
From: Alf P. Steinbach /Usenet on
* Martin v. Loewis, on 08.07.2010 07:23:
>> And since things work for a single method when I declare 'def' as
>> 'static', I suspect that means that the function object created by
>> PyCFunction_NewEx holds on to a pointer to the PyMethodDef structure?
>
> Correct; it doesn't make a copy of the struct. So when you want the
> function object to outlive the setRoutine call, you need to allocate
> the PyMethodDef on the heap.

Thanks!

That's the direction I tentatively had started to investigate.

But second problem now is cleanup: I'd like to deallocate when the module is
freed. 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.

Perhaps I don't need to to clean up?

Anyway, current looks like this:


<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 {
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
NULL // m_free
};

return &moduleDef;
}
}

class Module
{
private:
struct InstanceData
{
std::list< PyMethodDef > methodDefs;
};

void* instanceMemory() const
{
void* const result = PyModule_GetState( p_.get() );
assert( result != 0 );
return result;
}

InstanceData*& instanceDataPtr() const
{
return *reinterpret_cast< InstanceData** >( instanceMemory() );
}

Ptr p_;
InstanceData* data_;

public:
Module()
: p_( ::PyModule_Create( detail::moduleDefPtr() ) )
{
assert( def.m_size == sizeof( void* ) );
(p_.get() != 0) || cppx::throwX( "Module::<init>: failed" );
instanceDataPtr() = data_ = new 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* pDef = &data_->methodDefs.back();

Ptr const pyName = ::PyUnicode_FromString( name );
Ptr r = ::PyCFunction_NewEx( pDef, p_.get(),
pyName.get());

::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>


I changed the module name from "pyni*" to "cppy"... ;-)


Cheers & thanks!, but how to clean up, or must I?

- Alf

--
blog at <url: http://alfps.wordpress.com>
From: Martin v. Loewis on
> 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.

Regards,
Martin
From: Alf P. Steinbach /Usenet on
* 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>