view PyExtensionModule.cpp @ 34:c905122f79e7 vampy2

* Fixes from OpenSolaris build
author cannam
date Wed, 23 Sep 2009 12:07:32 +0000
parents 4f1894c7591b
children
line wrap: on
line source
#include <Python.h>
#include "PyExtensionModule.h"
#include "PyRealTime.h"
#include "PyFeature.h"
#include "PyFeatureSet.h"
#include "PyParameterDescriptor.h"
#include "PyOutputDescriptor.h"
#include "vamp-sdk/Plugin.h"

using namespace std;
using namespace Vamp;
using Vamp::Plugin;
using Vamp::RealTime;

/* Simple Example Function */

static int five=5;

PyObject*
get_five(PyObject *self, PyObject *args)
{
    if(!PyArg_ParseTuple(args, ":five")) return NULL;
    return Py_BuildValue("i", five);
}


/*			 Functions Exposed by Vampy 					*/

/*			 Creating PyRealTime Objects 					*/


/* New RealTime object from Frame (with given samplerate) */
static PyObject *
RealTime_frame2RealTime(PyObject *ignored, PyObject *args)
{
	long frame;
	unsigned int sampleRate;

    if (!PyArg_ParseTuple(args, "lI:realtime.frame2RealTime ", 
	&frame,&sampleRate))
		return NULL;

	RealTimeObject *self;
	self = PyObject_New(RealTimeObject, &RealTime_Type); 
	if (self == NULL) return NULL;

	self->rt = new RealTime(
	RealTime::frame2RealTime(frame,sampleRate));

	return (PyObject *) self;
}

/*			 Creating PyParameterDescriptor Objects 			  	*/

/* New ParameterDescriptor object 
static PyObject *
ParameterDescriptor_new(PyObject *ignored, PyObject *args)
{

    if (!PyArg_ParseTuple(args, ":ParameterDescriptor")) { 
		PyErr_SetString(PyExc_TypeError, 
		"Error: ParameterDescriptor initialised with arguments.");
		return NULL; 
	  }

	ParameterDescriptorObject *self = 
	PyObject_New(ParameterDescriptorObject, &ParameterDescriptor_Type); 
	if (self == NULL) return NULL;
    self->dict = PyDict_New();
	if (self->dict == NULL) return NULL;
	return (PyObject *) self;
}
*/

/*			 Creating PyOutputDescriptor Objects 					*/

/* New OutputDescriptor object 
static PyObject *
OutputDescriptor_new(PyObject *ignored, PyObject *args)
{

	if (!PyArg_ParseTuple(args, ":OutputDescriptor")) { 
		PyErr_SetString(PyExc_TypeError, 
		"Error: OutputDescriptor initialised with arguments.");
		return NULL; 
	  }

	OutputDescriptorObject *self = 
	PyObject_New(OutputDescriptorObject, &OutputDescriptor_Type); 
	if (self == NULL) return NULL;
    self->dict = PyDict_New();
	if (self->dict == NULL) return NULL;
	return (PyObject *) self;
}
*/

/*			 Creating PyOutputList Objects 					*/

/* New OutputList object */
static PyObject *
OutputList_new(PyObject *ignored, PyObject *args)
{
	if (!PyArg_ParseTuple(args, ":OutputList")) { 
		PyErr_SetString(PyExc_TypeError, 
		"Error: OutputList initialised with arguments.");
		return NULL; 
	  }

	return (PyObject *) PyList_New(0);
}


/*			 Creating PyParameterList Objects 					*/

/* New ParameterList object */
static PyObject *
ParameterList_new(PyObject *ignored, PyObject *args)
{
    if (!PyArg_ParseTuple(args, ":ParameterList")) { 
		PyErr_SetString(PyExc_TypeError, 
		"Error: ParameterList initialised with arguments.");
		return NULL; 
	  }
	return (PyObject *) PyList_New(0);
}


/*			 Creating PyFeatureList Objects 					*/

/* New FeatureList object 
static PyObject *
FeatureList_new(PyObject *ignored, PyObject *args)
{
	if (!PyArg_ParseTuple(args, ":FeatureList")) { 
		PyErr_SetString(PyExc_TypeError, 
		"Error: FeatureList initialised with arguments.");
		return NULL; 
	  }
	return (PyObject *) PyList_New(0);
}
*/

/*			 Creating PyFeatureSet Objects 					*/

/* New FeatureSet object 
static PyObject *
FeatureSet_new(PyObject *ignored, PyObject *args)
{
	if (!PyArg_ParseTuple(args, ":FeatureSet")) { 
		PyErr_SetString(PyExc_TypeError, 
		"Error: FeatureSet initialised with arguments.");
		return NULL; 
	  }
	return (PyObject *) PyDict_New();
}
*/


/*		 	Declare the methods exposed by the vampy module 		*/


PyMethodDef VampyMethods[] = {
/*NOTE: This is conventionally static, but limiting the scope
	here will cause seg fault if the declared functions are 
	called back from a Python function wrapped in a C++ class.*/
    {"five", get_five, METH_VARARGS, "Return a number."},

	{"frame2RealTime",	(PyCFunction)RealTime_frame2RealTime,	METH_VARARGS,
		PyDoc_STR("frame2RealTime((int64)frame, (uint32)sampleRate ) -> returns new RealTime object from frame.")},

	/*{"RealTime",	RealTime_new,		METH_VARARGS,
		PyDoc_STR("RealTime() -> returns new RealTime object")},*/

	/*{"Feature",	Feature_new,		METH_VARARGS,
		PyDoc_STR("Feature() -> returns new Feature object")},*/

	/*{"ParameterDescriptor",	ParameterDescriptor_new,		METH_VARARGS,
		PyDoc_STR("ParameterDescriptor() -> returns new ParameterDescriptor object")},

	{"OutputDescriptor",	OutputDescriptor_new,		METH_VARARGS,
		PyDoc_STR("OutputDescriptor() -> returns new OutputDescriptor object")},

	{"FeatureList",	FeatureList_new,		METH_VARARGS,
		PyDoc_STR("FeatureList() -> returns new FeatureList object")},*/

	{"OutputList",	OutputList_new,		METH_VARARGS,
		PyDoc_STR("OutputList() -> returns new OutputList object")},

	{"ParameterList",	ParameterList_new,		METH_VARARGS,
		PyDoc_STR("ParameterList() -> returns new ParameterList object")},

    {NULL, NULL, 0, NULL} 
};

/* Module Documentation */
// PyDoc_STRVAR(vampy_doc,"This module exposes Vamp plugin data type wrappers.");

PyMODINIT_FUNC
initvampy(void)
{
	PyObject* module;

	// if (PyType_Ready(&Feature_Type) < 0) return;
	/// Why do we get a segfault if this is initialised here?
	/*PyType_Ready adds these object to the GC.
	This is OK for an extension module, but it is a mistake here, 
	because the reference count will be decremented in the Vamp 
	wrapper plugin outside the interpreter.
	When the GC tries to visit a deallocated object, it will throw up.*/
	
	RealTime_Type.ob_type = &PyType_Type;
	Feature_Type.ob_type = &PyType_Type;
	OutputDescriptor_Type.ob_type = &PyType_Type;
	ParameterDescriptor_Type.ob_type = &PyType_Type;
	initFeatureSetType(); /// this is derived from the builtin dict

	PyImport_AddModule("vampy");
	module = Py_InitModule("vampy", VampyMethods);
	if (!module) return;
	
	Py_INCREF(&RealTime_Type);
    PyModule_AddObject(module,"RealTime",(PyObject*)&RealTime_Type);
	// Py_INCREF(&RealTime_Type);

	Py_INCREF((PyObject*)&Feature_Type);
	PyModule_AddObject(module,"Feature",(PyObject*)&Feature_Type);
	// Py_INCREF((PyObject*)&Feature_Type);

	Py_INCREF((PyObject*)&FeatureSet_Type);
	PyModule_AddObject(module,"FeatureSet",(PyObject*)&FeatureSet_Type);
	// Py_INCREF((PyObject*)&FeatureSet_Type);

	Py_INCREF((PyObject*)&OutputDescriptor_Type);
	PyModule_AddObject(module,"OutputDescriptor",(PyObject*)&OutputDescriptor_Type);

	Py_INCREF((PyObject*)&ParameterDescriptor_Type);
	PyModule_AddObject(module,"ParameterDescriptor",(PyObject*)&ParameterDescriptor_Type);
	
	cerr << "Vampy: extension module initialised." << endl;
}

/*
NOTE: Why do we need to clean up the module?

The module exposed by Vampy to the embedded interpreter
contains callback functions. These functions are accessed
via function pointers stored in the extension module dictionary.

Unfortunately, when the Vampy shared library is unloaded and
reloaded again during a host session, these addresses might
change. Therefore, we reinitialise the module dict before 
each use. However, this will cause garbage collection errors
or segmentation faults, when elements of the dict of the 
previous session are attempted to free. Therefore, we hold
a global reference count to all initialised Vampy plugins,
and when this reaches zero, we clean up the module dict.

This is an attempt to catch the moment when the shared lib
is finally unloaded and the references are still point to valid
memory addresses.

Why doesn't the GC clean this up correctly?

In a normal Python session the GC would deallocate the module
dict at the end. In embedded python, although the GC appears
to be called when the shared lib is unloaded, the interpreter
is reused. Since there is no C/API call to unload modules,
and at the time of unloading vampy the wrapped function pointers
are still valid, the GC doesn't collect them, nor are they freed
by the interpreter. When vampy is reloaded however, the module
dict will contain invalid addresses. The above procedure solves
this problem.


*/

void cleanModule(void)
{
	PyObject *m = PyImport_AddModule("vampy");
	if (!m) cerr << "Destr: PyImport_AddModule returned NULL!" << endl;
	else {
		// cerr << "Destr: Add module found existing." << endl;
		PyObject *dict = PyModule_GetDict(m);
		Py_ssize_t ln = PyDict_Size(dict);
		cerr << "Destr: Size of module dict = " << (int) ln << endl;
		/// Clean the module dictionary.
		PyDict_Clear(dict);
	    ln = PyDict_Size(dict);
		cerr << "Destr: Cleaned size of module dict = " << (int) ln << endl;
	}
}