874 lines
28 KiB
C++
874 lines
28 KiB
C++
/**
|
|
* @file py_panda.cxx
|
|
* @author drose
|
|
* @date 2005-07-04
|
|
*/
|
|
|
|
#include "py_panda.h"
|
|
#include "config_interrogatedb.h"
|
|
#include "executionEnvironment.h"
|
|
|
|
#ifdef HAVE_PYTHON
|
|
|
|
#define _STRINGIFY_VERSION(a, b) (#a "." #b)
|
|
#define STRINGIFY_VERSION(a, b) _STRINGIFY_VERSION(a, b)
|
|
|
|
using std::string;
|
|
|
|
/**
|
|
|
|
*/
|
|
void DTOOL_Call_ExtractThisPointerForType(PyObject *self, Dtool_PyTypedObject *classdef, void **answer) {
|
|
if (DtoolInstance_Check(self)) {
|
|
*answer = DtoolInstance_UPCAST(self, *classdef);
|
|
} else {
|
|
*answer = nullptr;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This is a support function for the Python bindings: it extracts the
|
|
* underlying C++ pointer of the given type for a given Python object. If it
|
|
* was of the wrong type, raises an AttributeError.
|
|
*/
|
|
bool Dtool_Call_ExtractThisPointer(PyObject *self, Dtool_PyTypedObject &classdef, void **answer) {
|
|
if (self == nullptr || !DtoolInstance_Check(self) || DtoolInstance_VOID_PTR(self) == nullptr) {
|
|
Dtool_Raise_TypeError("C++ object is not yet constructed, or already destructed.");
|
|
return false;
|
|
}
|
|
|
|
*answer = DtoolInstance_UPCAST(self, classdef);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* The same thing as Dtool_Call_ExtractThisPointer, except that it performs
|
|
* the additional check that the pointer is a non-const pointer. This is
|
|
* called by function wrappers for functions of which all overloads are non-
|
|
* const, and saves a bit of code.
|
|
*
|
|
* The extra method_name argument is used in formatting the error message.
|
|
*/
|
|
bool Dtool_Call_ExtractThisPointer_NonConst(PyObject *self, Dtool_PyTypedObject &classdef,
|
|
void **answer, const char *method_name) {
|
|
|
|
if (self == nullptr || !DtoolInstance_Check(self) || DtoolInstance_VOID_PTR(self) == nullptr) {
|
|
Dtool_Raise_TypeError("C++ object is not yet constructed, or already destructed.");
|
|
return false;
|
|
}
|
|
|
|
if (DtoolInstance_IS_CONST(self)) {
|
|
// All overloads of this function are non-const.
|
|
PyErr_Format(PyExc_TypeError,
|
|
"Cannot call %s() on a const object.",
|
|
method_name);
|
|
return false;
|
|
}
|
|
|
|
*answer = DtoolInstance_UPCAST(self, classdef);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Extracts the C++ pointer for an object, given its Python wrapper object,
|
|
* for passing as the parameter to a C++ function.
|
|
*
|
|
* self is the Python wrapper object in question.
|
|
*
|
|
* classdef is the Python class wrapper for the C++ class in which the this
|
|
* pointer should be returned. (This may require an upcast operation, if self
|
|
* is not already an instance of classdef.)
|
|
*
|
|
* param and function_name are used for error reporting only, and describe the
|
|
* particular function and parameter index for this parameter.
|
|
*
|
|
* const_ok is true if the function is declared const and can therefore be
|
|
* called with either a const or non-const "this" pointer, or false if the
|
|
* function is declared non-const, and can therefore be called with only a
|
|
* non-const "this" pointer.
|
|
*
|
|
* The return value is the C++ pointer that was extracted, or NULL if there
|
|
* was a problem (in which case the Python exception state will have been
|
|
* set).
|
|
*/
|
|
void *
|
|
DTOOL_Call_GetPointerThisClass(PyObject *self, Dtool_PyTypedObject *classdef,
|
|
int param, const string &function_name, bool const_ok,
|
|
bool report_errors) {
|
|
// if (PyErr_Occurred()) { return nullptr; }
|
|
if (self == nullptr) {
|
|
if (report_errors) {
|
|
return Dtool_Raise_TypeError("self is nullptr");
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
if (DtoolInstance_Check(self)) {
|
|
void *result = DtoolInstance_UPCAST(self, *classdef);
|
|
|
|
if (result != nullptr) {
|
|
if (const_ok || !DtoolInstance_IS_CONST(self)) {
|
|
return result;
|
|
}
|
|
|
|
if (report_errors) {
|
|
return PyErr_Format(PyExc_TypeError,
|
|
"%s() argument %d may not be const",
|
|
function_name.c_str(), param);
|
|
}
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
if (report_errors) {
|
|
return Dtool_Raise_ArgTypeError(self, param, function_name.c_str(), classdef->_PyType.tp_name);
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* This is similar to a PyErr_Occurred() check, except that it also checks
|
|
* Notify to see if an assertion has occurred. If that is the case, then it
|
|
* raises an AssertionError.
|
|
*
|
|
* Returns true if there is an active exception, false otherwise.
|
|
*
|
|
* In the NDEBUG case, this is simply a #define to PyErr_Occurred().
|
|
*/
|
|
bool _Dtool_CheckErrorOccurred() {
|
|
if (PyErr_Occurred()) {
|
|
return true;
|
|
}
|
|
if (Notify::ptr()->has_assert_failed()) {
|
|
Dtool_Raise_AssertionError();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Raises an AssertionError containing the last thrown assert message, and
|
|
* clears the assertion flag. Returns NULL.
|
|
*/
|
|
PyObject *Dtool_Raise_AssertionError() {
|
|
Notify *notify = Notify::ptr();
|
|
#if PY_MAJOR_VERSION >= 3
|
|
PyObject *message = PyUnicode_FromString(notify->get_assert_error_message().c_str());
|
|
#else
|
|
PyObject *message = PyString_FromString(notify->get_assert_error_message().c_str());
|
|
#endif
|
|
PyErr_SetObject(PyExc_AssertionError, message);
|
|
notify->clear_assert_failed();
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Raises a TypeError with the given message, and returns NULL.
|
|
*/
|
|
PyObject *Dtool_Raise_TypeError(const char *message) {
|
|
PyErr_SetString(PyExc_TypeError, message);
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Raises a TypeError of the form: function_name() argument n must be type,
|
|
* not type for a given object passed to a function.
|
|
*
|
|
* Always returns NULL so that it can be conveniently used as a return
|
|
* expression for wrapper functions that return a PyObject pointer.
|
|
*/
|
|
PyObject *Dtool_Raise_ArgTypeError(PyObject *obj, int param, const char *function_name, const char *type_name) {
|
|
#if PY_MAJOR_VERSION >= 3
|
|
PyObject *message = PyUnicode_FromFormat(
|
|
#else
|
|
PyObject *message = PyString_FromFormat(
|
|
#endif
|
|
"%s() argument %d must be %s, not %s",
|
|
function_name, param, type_name,
|
|
Py_TYPE(obj)->tp_name);
|
|
|
|
PyErr_SetObject(PyExc_TypeError, message);
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Raises an AttributeError of the form: 'type' has no attribute 'attr'
|
|
*
|
|
* Always returns NULL so that it can be conveniently used as a return
|
|
* expression for wrapper functions that return a PyObject pointer.
|
|
*/
|
|
PyObject *Dtool_Raise_AttributeError(PyObject *obj, const char *attribute) {
|
|
#if PY_MAJOR_VERSION >= 3
|
|
PyObject *message = PyUnicode_FromFormat(
|
|
#else
|
|
PyObject *message = PyString_FromFormat(
|
|
#endif
|
|
"'%.100s' object has no attribute '%.200s'",
|
|
Py_TYPE(obj)->tp_name, attribute);
|
|
|
|
PyErr_SetObject(PyExc_AttributeError, message);
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Raises a TypeError of the form: Arguments must match: <list of overloads>
|
|
*
|
|
* However, in release builds, this instead is defined to a function that just
|
|
* prints out a generic message, to help reduce the amount of strings in the
|
|
* compiled library.
|
|
*
|
|
* Always returns NULL so that it can be conveniently used as a return
|
|
* expression for wrapper functions that return a PyObject pointer.
|
|
*/
|
|
PyObject *_Dtool_Raise_BadArgumentsError() {
|
|
return Dtool_Raise_TypeError("arguments do not match any function overload");
|
|
}
|
|
|
|
/**
|
|
* Convenience method that checks for exceptions, and if one occurred, returns
|
|
* NULL, otherwise Py_None.
|
|
*/
|
|
PyObject *_Dtool_Return_None() {
|
|
if (UNLIKELY(PyErr_Occurred())) {
|
|
return nullptr;
|
|
}
|
|
#ifndef NDEBUG
|
|
if (UNLIKELY(Notify::ptr()->has_assert_failed())) {
|
|
return Dtool_Raise_AssertionError();
|
|
}
|
|
#endif
|
|
Py_INCREF(Py_None);
|
|
return Py_None;
|
|
}
|
|
|
|
/**
|
|
* Convenience method that checks for exceptions, and if one occurred, returns
|
|
* NULL, otherwise the given boolean value as a PyObject *.
|
|
*/
|
|
PyObject *Dtool_Return_Bool(bool value) {
|
|
if (UNLIKELY(PyErr_Occurred())) {
|
|
return nullptr;
|
|
}
|
|
#ifndef NDEBUG
|
|
if (UNLIKELY(Notify::ptr()->has_assert_failed())) {
|
|
return Dtool_Raise_AssertionError();
|
|
}
|
|
#endif
|
|
PyObject *result = (value ? Py_True : Py_False);
|
|
Py_INCREF(result);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Convenience method that checks for exceptions, and if one occurred, returns
|
|
* NULL, otherwise the given return value. Its reference count is not
|
|
* increased.
|
|
*/
|
|
PyObject *_Dtool_Return(PyObject *value) {
|
|
if (UNLIKELY(PyErr_Occurred())) {
|
|
return nullptr;
|
|
}
|
|
#ifndef NDEBUG
|
|
if (UNLIKELY(Notify::ptr()->has_assert_failed())) {
|
|
return Dtool_Raise_AssertionError();
|
|
}
|
|
#endif
|
|
return value;
|
|
}
|
|
|
|
#if PY_VERSION_HEX < 0x03040000
|
|
/**
|
|
* This function converts an int value to the appropriate enum instance.
|
|
*/
|
|
static PyObject *Dtool_EnumType_New(PyTypeObject *subtype, PyObject *args, PyObject *kwds) {
|
|
PyObject *arg;
|
|
if (!Dtool_ExtractArg(&arg, args, kwds, "value")) {
|
|
return PyErr_Format(PyExc_TypeError,
|
|
"%s() missing 1 required argument: 'value'",
|
|
subtype->tp_name);
|
|
}
|
|
|
|
if (Py_TYPE(arg) == subtype) {
|
|
Py_INCREF(arg);
|
|
return arg;
|
|
}
|
|
|
|
PyObject *value2member = PyDict_GetItemString(subtype->tp_dict, "_value2member_map_");
|
|
nassertr_always(value2member != nullptr, nullptr);
|
|
|
|
PyObject *member = PyDict_GetItem(value2member, arg);
|
|
if (member != nullptr) {
|
|
Py_INCREF(member);
|
|
return member;
|
|
}
|
|
|
|
PyObject *repr = PyObject_Repr(arg);
|
|
PyErr_Format(PyExc_ValueError, "%s is not a valid %s",
|
|
#if PY_MAJOR_VERSION >= 3
|
|
PyUnicode_AS_STRING(repr),
|
|
#else
|
|
PyString_AS_STRING(repr),
|
|
#endif
|
|
subtype->tp_name);
|
|
Py_DECREF(repr);
|
|
return nullptr;
|
|
}
|
|
|
|
static PyObject *Dtool_EnumType_Str(PyObject *self) {
|
|
PyObject *name = PyObject_GetAttrString(self, "name");
|
|
#if PY_MAJOR_VERSION >= 3
|
|
PyObject *repr = PyUnicode_FromFormat("%s.%s", Py_TYPE(self)->tp_name, PyString_AS_STRING(name));
|
|
#else
|
|
PyObject *repr = PyString_FromFormat("%s.%s", Py_TYPE(self)->tp_name, PyString_AS_STRING(name));
|
|
#endif
|
|
Py_DECREF(name);
|
|
return repr;
|
|
}
|
|
|
|
static PyObject *Dtool_EnumType_Repr(PyObject *self) {
|
|
PyObject *name = PyObject_GetAttrString(self, "name");
|
|
PyObject *value = PyObject_GetAttrString(self, "value");
|
|
#if PY_MAJOR_VERSION >= 3
|
|
PyObject *repr = PyUnicode_FromFormat("<%s.%s: %ld>", Py_TYPE(self)->tp_name, PyString_AS_STRING(name), PyLongOrInt_AS_LONG(value));
|
|
#else
|
|
PyObject *repr = PyString_FromFormat("<%s.%s: %ld>", Py_TYPE(self)->tp_name, PyString_AS_STRING(name), PyLongOrInt_AS_LONG(value));
|
|
#endif
|
|
Py_DECREF(name);
|
|
Py_DECREF(value);
|
|
return repr;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Creates a Python 3.4-style enum type. Steals reference to 'names', which
|
|
* should be a tuple of (name, value) pairs.
|
|
*/
|
|
PyTypeObject *Dtool_EnumType_Create(const char *name, PyObject *names, const char *module) {
|
|
static PyObject *enum_class = nullptr;
|
|
#if PY_VERSION_HEX >= 0x03040000
|
|
static PyObject *enum_meta = nullptr;
|
|
static PyObject *enum_create = nullptr;
|
|
if (enum_meta == nullptr) {
|
|
PyObject *enum_module = PyImport_ImportModule("enum");
|
|
nassertr_always(enum_module != nullptr, nullptr);
|
|
|
|
enum_class = PyObject_GetAttrString(enum_module, "Enum");
|
|
enum_meta = PyObject_GetAttrString(enum_module, "EnumMeta");
|
|
enum_create = PyObject_GetAttrString(enum_meta, "_create_");
|
|
nassertr(enum_meta != nullptr, nullptr);
|
|
}
|
|
|
|
PyObject *result = PyObject_CallFunction(enum_create, (char *)"OsN", enum_class, name, names);
|
|
nassertr(result != nullptr, nullptr);
|
|
#else
|
|
static PyObject *name_str;
|
|
static PyObject *name_sunder_str;
|
|
static PyObject *value_str;
|
|
static PyObject *value_sunder_str;
|
|
static PyObject *value2member_map_sunder_str;
|
|
// Emulate something vaguely like the enum module.
|
|
if (enum_class == nullptr) {
|
|
#if PY_MAJOR_VERSION >= 3
|
|
name_str = PyUnicode_InternFromString("name");
|
|
value_str = PyUnicode_InternFromString("value");
|
|
name_sunder_str = PyUnicode_InternFromString("_name_");
|
|
value_sunder_str = PyUnicode_InternFromString("_value_");
|
|
value2member_map_sunder_str = PyUnicode_InternFromString("_value2member_map_");
|
|
#else
|
|
name_str = PyString_InternFromString("name");
|
|
value_str = PyString_InternFromString("value");
|
|
name_sunder_str = PyString_InternFromString("_name_");
|
|
value_sunder_str = PyString_InternFromString("_value_");
|
|
value2member_map_sunder_str = PyString_InternFromString("_value2member_map_");
|
|
#endif
|
|
PyObject *name_value_tuple = PyTuple_New(4);
|
|
PyTuple_SET_ITEM(name_value_tuple, 0, name_str);
|
|
PyTuple_SET_ITEM(name_value_tuple, 1, value_str);
|
|
PyTuple_SET_ITEM(name_value_tuple, 2, name_sunder_str);
|
|
PyTuple_SET_ITEM(name_value_tuple, 3, value_sunder_str);
|
|
Py_INCREF(name_str);
|
|
Py_INCREF(value_str);
|
|
|
|
PyObject *slots_dict = PyDict_New();
|
|
PyDict_SetItemString(slots_dict, "__slots__", name_value_tuple);
|
|
Py_DECREF(name_value_tuple);
|
|
|
|
enum_class = PyObject_CallFunction((PyObject *)&PyType_Type, (char *)"s()N", "Enum", slots_dict);
|
|
nassertr(enum_class != nullptr, nullptr);
|
|
}
|
|
|
|
// Create a subclass of this generic Enum class we just created.
|
|
PyObject *value2member = PyDict_New();
|
|
PyObject *dict = PyDict_New();
|
|
PyDict_SetItem(dict, value2member_map_sunder_str, value2member);
|
|
PyObject *result = PyObject_CallFunction((PyObject *)&PyType_Type, (char *)"s(O)N", name, enum_class, dict);
|
|
nassertr(result != nullptr, nullptr);
|
|
|
|
((PyTypeObject *)result)->tp_new = Dtool_EnumType_New;
|
|
((PyTypeObject *)result)->tp_str = Dtool_EnumType_Str;
|
|
((PyTypeObject *)result)->tp_repr = Dtool_EnumType_Repr;
|
|
|
|
PyObject *empty_tuple = PyTuple_New(0);
|
|
|
|
// Copy the names as instances of the above to the class dict, and create a
|
|
// reverse mapping in the _value2member_map_ dict.
|
|
Py_ssize_t size = PyTuple_GET_SIZE(names);
|
|
for (Py_ssize_t i = 0; i < size; ++i) {
|
|
PyObject *item = PyTuple_GET_ITEM(names, i);
|
|
PyObject *name = PyTuple_GET_ITEM(item, 0);
|
|
PyObject *value = PyTuple_GET_ITEM(item, 1);
|
|
PyObject *member = PyType_GenericNew((PyTypeObject *)result, empty_tuple, nullptr);
|
|
PyObject_SetAttr(member, name_str, name);
|
|
PyObject_SetAttr(member, name_sunder_str, name);
|
|
PyObject_SetAttr(member, value_str, value);
|
|
PyObject_SetAttr(member, value_sunder_str, value);
|
|
PyObject_SetAttr(result, name, member);
|
|
PyDict_SetItem(value2member, value, member);
|
|
Py_DECREF(member);
|
|
}
|
|
Py_DECREF(names);
|
|
Py_DECREF(value2member);
|
|
Py_DECREF(empty_tuple);
|
|
#endif
|
|
|
|
if (module != nullptr) {
|
|
PyObject *modstr = PyUnicode_FromString(module);
|
|
PyObject_SetAttrString(result, "__module__", modstr);
|
|
Py_DECREF(modstr);
|
|
}
|
|
nassertr(PyType_Check(result), nullptr);
|
|
return (PyTypeObject *)result;
|
|
}
|
|
|
|
/**
|
|
|
|
*/
|
|
PyObject *DTool_CreatePyInstanceTyped(void *local_this_in, Dtool_PyTypedObject &known_class_type, bool memory_rules, bool is_const, int type_index) {
|
|
// We can't do the NULL check here like in DTool_CreatePyInstance, since the
|
|
// caller will have to get the type index to pass to this function to begin
|
|
// with. That code probably would have crashed by now if it was really NULL
|
|
// for whatever reason.
|
|
nassertr(local_this_in != nullptr, nullptr);
|
|
|
|
// IF the class is possibly a run time typed object
|
|
if (type_index > 0) {
|
|
// get best fit class...
|
|
Dtool_PyTypedObject *target_class = (Dtool_PyTypedObject *)TypeHandle::from_index(type_index).get_python_type();
|
|
if (target_class != nullptr) {
|
|
// cast to the type...
|
|
Dtool_PyInstDef *self = target_class->_Dtool_WrapInterface(local_this_in, &known_class_type);
|
|
if (self != nullptr) {
|
|
self->_memory_rules = memory_rules;
|
|
self->_is_const = is_const;
|
|
return (PyObject *)self;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if we get this far .. just wrap the thing in the known type ?? better
|
|
// than aborting...I guess....
|
|
Dtool_PyInstDef *self = (Dtool_PyInstDef *)PyType_GenericAlloc(&known_class_type._PyType, 0);
|
|
if (self != nullptr) {
|
|
self->_signature = PY_PANDA_SIGNATURE;
|
|
self->_My_Type = &known_class_type;
|
|
self->_ptr_to_object = local_this_in;
|
|
self->_memory_rules = memory_rules;
|
|
self->_is_const = is_const;
|
|
}
|
|
return (PyObject *)self;
|
|
}
|
|
|
|
// DTool_CreatePyInstance .. wrapper function to finalize the existance of a
|
|
// general dtool py instance..
|
|
PyObject *DTool_CreatePyInstance(void *local_this, Dtool_PyTypedObject &in_classdef, bool memory_rules, bool is_const) {
|
|
if (local_this == nullptr) {
|
|
// This is actually a very common case, so let's allow this, but return
|
|
// Py_None consistently. This eliminates code in the wrappers.
|
|
Py_INCREF(Py_None);
|
|
return Py_None;
|
|
}
|
|
|
|
Dtool_PyInstDef *self = (Dtool_PyInstDef *)PyType_GenericAlloc(&in_classdef._PyType, 0);
|
|
if (self != nullptr) {
|
|
self->_signature = PY_PANDA_SIGNATURE;
|
|
self->_My_Type = &in_classdef;
|
|
self->_ptr_to_object = local_this;
|
|
self->_memory_rules = memory_rules;
|
|
self->_is_const = is_const;
|
|
self->_My_Type = &in_classdef;
|
|
}
|
|
return (PyObject *)self;
|
|
}
|
|
|
|
/**
|
|
* Returns a borrowed reference to the global type dictionary.
|
|
*/
|
|
Dtool_TypeMap *Dtool_GetGlobalTypeMap() {
|
|
PyObject *capsule = PySys_GetObject((char *)"_interrogate_types");
|
|
if (capsule != nullptr) {
|
|
return (Dtool_TypeMap *)PyCapsule_GetPointer(capsule, nullptr);
|
|
} else {
|
|
Dtool_TypeMap *type_map = new Dtool_TypeMap;
|
|
capsule = PyCapsule_New((void *)type_map, nullptr, nullptr);
|
|
PySys_SetObject((char *)"_interrogate_types", capsule);
|
|
Py_DECREF(capsule);
|
|
return type_map;
|
|
}
|
|
}
|
|
|
|
#define PY_MAJOR_VERSION_STR #PY_MAJOR_VERSION "." #PY_MINOR_VERSION
|
|
|
|
#if PY_MAJOR_VERSION >= 3
|
|
PyObject *Dtool_PyModuleInitHelper(const LibraryDef *defs[], PyModuleDef *module_def) {
|
|
#else
|
|
PyObject *Dtool_PyModuleInitHelper(const LibraryDef *defs[], const char *modulename) {
|
|
#endif
|
|
// Check the version so we can print a helpful error if it doesn't match.
|
|
string version = Py_GetVersion();
|
|
size_t version_len = version.find('.', 2);
|
|
if (version_len != string::npos) {
|
|
version.resize(version_len);
|
|
}
|
|
|
|
if (version != STRINGIFY_VERSION(PY_MAJOR_VERSION, PY_MINOR_VERSION)) {
|
|
// Raise a helpful error message. We can safely do this because the
|
|
// signature and behavior for PyErr_SetString has remained consistent.
|
|
std::ostringstream errs;
|
|
errs << "this module was compiled for Python "
|
|
<< PY_MAJOR_VERSION << "." << PY_MINOR_VERSION << ", which is "
|
|
<< "incompatible with Python " << version;
|
|
string error = errs.str();
|
|
PyErr_SetString(PyExc_ImportError, error.c_str());
|
|
return nullptr;
|
|
}
|
|
|
|
Dtool_TypeMap *type_map = Dtool_GetGlobalTypeMap();
|
|
|
|
// the module level function inits....
|
|
MethodDefmap functions;
|
|
for (size_t i = 0; defs[i] != nullptr; i++) {
|
|
const LibraryDef &def = *defs[i];
|
|
|
|
// Accumulate method definitions.
|
|
for (PyMethodDef *meth = def._methods; meth->ml_name != nullptr; meth++) {
|
|
if (functions.find(meth->ml_name) == functions.end()) {
|
|
functions[meth->ml_name] = meth;
|
|
}
|
|
}
|
|
|
|
// Define exported types.
|
|
const Dtool_TypeDef *types = def._types;
|
|
if (types != nullptr) {
|
|
while (types->name != nullptr) {
|
|
(*type_map)[std::string(types->name)] = types->type;
|
|
++types;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Resolve external types, in a second pass.
|
|
for (size_t i = 0; defs[i] != nullptr; i++) {
|
|
const LibraryDef &def = *defs[i];
|
|
|
|
Dtool_TypeDef *types = def._external_types;
|
|
if (types != nullptr) {
|
|
while (types->name != nullptr) {
|
|
auto it = type_map->find(std::string(types->name));
|
|
if (it != type_map->end()) {
|
|
types->type = it->second;
|
|
} else {
|
|
return PyErr_Format(PyExc_NameError, "name '%s' is not defined", types->name);
|
|
}
|
|
++types;
|
|
}
|
|
}
|
|
}
|
|
|
|
PyMethodDef *newdef = new PyMethodDef[functions.size() + 1];
|
|
MethodDefmap::iterator mi;
|
|
int offset = 0;
|
|
for (mi = functions.begin(); mi != functions.end(); mi++, offset++) {
|
|
newdef[offset] = *mi->second;
|
|
}
|
|
newdef[offset].ml_doc = nullptr;
|
|
newdef[offset].ml_name = nullptr;
|
|
newdef[offset].ml_meth = nullptr;
|
|
newdef[offset].ml_flags = 0;
|
|
|
|
#if PY_MAJOR_VERSION >= 3
|
|
module_def->m_methods = newdef;
|
|
PyObject *module = PyModule_Create(module_def);
|
|
#else
|
|
PyObject *module = Py_InitModule((char *)modulename, newdef);
|
|
#endif
|
|
|
|
if (module == nullptr) {
|
|
#if PY_MAJOR_VERSION >= 3
|
|
return Dtool_Raise_TypeError("PyModule_Create returned NULL");
|
|
#else
|
|
return Dtool_Raise_TypeError("Py_InitModule returned NULL");
|
|
#endif
|
|
}
|
|
|
|
// MAIN_DIR needs to be set very early; this seems like a convenient place
|
|
// to do that. Perhaps we'll find a better place for this in the future.
|
|
static bool initialized_main_dir = false;
|
|
if (!initialized_main_dir) {
|
|
if (interrogatedb_cat.is_debug()) {
|
|
// Good opportunity to print this out once, at startup.
|
|
interrogatedb_cat.debug()
|
|
<< "Python " << version << "\n";
|
|
}
|
|
|
|
if (!ExecutionEnvironment::has_environment_variable("MAIN_DIR")) {
|
|
// Grab the __main__ module.
|
|
PyObject *main_module = PyImport_ImportModule("__main__");
|
|
if (main_module == NULL) {
|
|
interrogatedb_cat.warning() << "Unable to import __main__\n";
|
|
}
|
|
|
|
// Extract the __file__ attribute, if present.
|
|
Filename main_dir;
|
|
PyObject *file_attr = nullptr;
|
|
if (main_module != nullptr) {
|
|
file_attr = PyObject_GetAttrString(main_module, "__file__");
|
|
}
|
|
if (file_attr == nullptr) {
|
|
// Must be running in the interactive interpreter. Use the CWD.
|
|
main_dir = ExecutionEnvironment::get_cwd();
|
|
} else {
|
|
#if PY_MAJOR_VERSION >= 3
|
|
Py_ssize_t length;
|
|
wchar_t *buffer = PyUnicode_AsWideCharString(file_attr, &length);
|
|
if (buffer != nullptr) {
|
|
main_dir = Filename::from_os_specific_w(std::wstring(buffer, length));
|
|
main_dir.make_absolute();
|
|
main_dir = main_dir.get_dirname();
|
|
PyMem_Free(buffer);
|
|
}
|
|
#else
|
|
char *buffer;
|
|
Py_ssize_t length;
|
|
if (PyString_AsStringAndSize(file_attr, &buffer, &length) != -1) {
|
|
main_dir = Filename::from_os_specific(std::string(buffer, length));
|
|
main_dir.make_absolute();
|
|
main_dir = main_dir.get_dirname();
|
|
}
|
|
#endif
|
|
else {
|
|
interrogatedb_cat.warning() << "Invalid string for __main__.__file__\n";
|
|
}
|
|
}
|
|
ExecutionEnvironment::shadow_environment_variable("MAIN_DIR", main_dir.to_os_specific());
|
|
PyErr_Clear();
|
|
}
|
|
initialized_main_dir = true;
|
|
|
|
// Also, while we are at it, initialize the thread swap hook.
|
|
#if defined(HAVE_THREADS) && defined(SIMPLE_THREADS)
|
|
global_thread_state_swap = PyThreadState_Swap;
|
|
#endif
|
|
}
|
|
|
|
PyModule_AddIntConstant(module, "Dtool_PyNativeInterface", 1);
|
|
return module;
|
|
}
|
|
|
|
// HACK.... Be careful Dtool_BorrowThisReference This function can be used to
|
|
// grab the "THIS" pointer from an object and use it Required to support
|
|
// historical inheritance in the form of "is this instance of"..
|
|
PyObject *Dtool_BorrowThisReference(PyObject *self, PyObject *args) {
|
|
PyObject *from_in = nullptr;
|
|
PyObject *to_in = nullptr;
|
|
if (PyArg_UnpackTuple(args, "Dtool_BorrowThisReference", 2, 2, &to_in, &from_in)) {
|
|
|
|
if (DtoolInstance_Check(from_in) && DtoolInstance_Check(to_in)) {
|
|
Dtool_PyInstDef *from = (Dtool_PyInstDef *) from_in;
|
|
Dtool_PyInstDef *to = (Dtool_PyInstDef *) to_in;
|
|
|
|
// if (PyObject_TypeCheck(to_in, Py_TYPE(from_in))) {
|
|
if (from->_My_Type == to->_My_Type) {
|
|
to->_memory_rules = false;
|
|
to->_is_const = from->_is_const;
|
|
to->_ptr_to_object = from->_ptr_to_object;
|
|
|
|
Py_INCREF(Py_None);
|
|
return Py_None;
|
|
}
|
|
|
|
return PyErr_Format(PyExc_TypeError, "types %s and %s do not match",
|
|
Py_TYPE(from)->tp_name, Py_TYPE(to)->tp_name);
|
|
} else {
|
|
return Dtool_Raise_TypeError("One of these does not appear to be DTOOL Instance ??");
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// We do expose a dictionay for dtool classes .. this should be removed at
|
|
// some point..
|
|
EXPCL_PYPANDA PyObject *
|
|
Dtool_AddToDictionary(PyObject *self1, PyObject *args) {
|
|
PyObject *self;
|
|
PyObject *subject;
|
|
PyObject *key;
|
|
if (PyArg_ParseTuple(args, "OSO", &self, &key, &subject)) {
|
|
PyObject *dict = ((PyTypeObject *)self)->tp_dict;
|
|
if (dict == nullptr || !PyDict_Check(dict)) {
|
|
return Dtool_Raise_TypeError("No dictionary On Object");
|
|
} else {
|
|
PyDict_SetItem(dict, key, subject);
|
|
}
|
|
}
|
|
if (PyErr_Occurred()) {
|
|
return nullptr;
|
|
}
|
|
Py_INCREF(Py_None);
|
|
return Py_None;
|
|
}
|
|
|
|
/**
|
|
* This is a support function for a synthesized __copy__() method from a C++
|
|
* make_copy() method.
|
|
*/
|
|
PyObject *copy_from_make_copy(PyObject *self, PyObject *noargs) {
|
|
PyObject *callable = PyObject_GetAttrString(self, "make_copy");
|
|
if (callable == nullptr) {
|
|
return nullptr;
|
|
}
|
|
PyObject *result = PyObject_CallNoArgs(callable);
|
|
Py_DECREF(callable);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* This is a support function for a synthesized __copy__() method from a C++
|
|
* copy constructor.
|
|
*/
|
|
PyObject *copy_from_copy_constructor(PyObject *self, PyObject *noargs) {
|
|
PyObject *callable = (PyObject *)Py_TYPE(self);
|
|
return PyObject_CallOneArg(callable, self);
|
|
}
|
|
|
|
/**
|
|
* This is a support function for a synthesized __deepcopy__() method for any
|
|
* class that has a __copy__() method. The sythethic method simply invokes
|
|
* __copy__().
|
|
*/
|
|
PyObject *map_deepcopy_to_copy(PyObject *self, PyObject *args) {
|
|
PyObject *callable = PyObject_GetAttrString(self, "__copy__");
|
|
if (callable == nullptr) {
|
|
return nullptr;
|
|
}
|
|
PyObject *result = PyObject_CallNoArgs(callable);
|
|
Py_DECREF(callable);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* A more efficient version of PyArg_ParseTupleAndKeywords for the special
|
|
* case where there is only a single PyObject argument.
|
|
*/
|
|
bool Dtool_ExtractArg(PyObject **result, PyObject *args, PyObject *kwds,
|
|
const char *keyword) {
|
|
|
|
if (PyTuple_GET_SIZE(args) == 1) {
|
|
if (kwds == nullptr || PyDict_GET_SIZE(kwds) == 0) {
|
|
*result = PyTuple_GET_ITEM(args, 0);
|
|
return true;
|
|
}
|
|
}
|
|
else if (!keyword || !keyword[0]) {
|
|
return false;
|
|
}
|
|
else if (PyTuple_GET_SIZE(args) == 0) {
|
|
PyObject *key;
|
|
Py_ssize_t ppos = 0;
|
|
if (kwds != nullptr && PyDict_GET_SIZE(kwds) == 1 &&
|
|
PyDict_Next(kwds, &ppos, &key, result)) {
|
|
// We got the item, we just need to make sure that it had the right key.
|
|
#if PY_VERSION_HEX >= 0x03060000
|
|
return PyUnicode_CheckExact(key) && _PyUnicode_EqualToASCIIString(key, keyword);
|
|
#elif PY_MAJOR_VERSION >= 3
|
|
return PyUnicode_CheckExact(key) && PyUnicode_CompareWithASCIIString(key, keyword) == 0;
|
|
#else
|
|
return PyString_CheckExact(key) && strcmp(PyString_AS_STRING(key), keyword) == 0;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Variant of Dtool_ExtractArg that does not accept a keyword argument.
|
|
*/
|
|
bool Dtool_ExtractArg(PyObject **result, PyObject *args, PyObject *kwds) {
|
|
if (PyTuple_GET_SIZE(args) == 1 &&
|
|
(kwds == nullptr || PyDict_GET_SIZE(kwds) == 0)) {
|
|
*result = PyTuple_GET_ITEM(args, 0);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* A more efficient version of PyArg_ParseTupleAndKeywords for the special
|
|
* case where there is only a single optional PyObject argument.
|
|
*
|
|
* Returns true if valid (including if there were 0 items), false if there was
|
|
* an error, such as an invalid number of parameters.
|
|
*/
|
|
bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args, PyObject *kwds,
|
|
const char *keyword) {
|
|
|
|
if (PyTuple_GET_SIZE(args) == 1) {
|
|
if (kwds == nullptr || PyDict_GET_SIZE(kwds) == 0) {
|
|
*result = PyTuple_GET_ITEM(args, 0);
|
|
return true;
|
|
}
|
|
}
|
|
else if (!keyword || !keyword[0]) {
|
|
return (kwds == nullptr || PyDict_GET_SIZE(kwds) == 0);
|
|
}
|
|
else if (PyTuple_GET_SIZE(args) == 0) {
|
|
if (kwds != nullptr && PyDict_GET_SIZE(kwds) == 1) {
|
|
PyObject *key;
|
|
Py_ssize_t ppos = 0;
|
|
if (!PyDict_Next(kwds, &ppos, &key, result)) {
|
|
return true;
|
|
}
|
|
|
|
// We got the item, we just need to make sure that it had the right key.
|
|
#if PY_VERSION_HEX >= 0x03060000
|
|
return PyUnicode_CheckExact(key) && _PyUnicode_EqualToASCIIString(key, keyword);
|
|
#elif PY_MAJOR_VERSION >= 3
|
|
return PyUnicode_CheckExact(key) && PyUnicode_CompareWithASCIIString(key, keyword) == 0;
|
|
#else
|
|
return PyString_CheckExact(key) && strcmp(PyString_AS_STRING(key), keyword) == 0;
|
|
#endif
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Variant of Dtool_ExtractOptionalArg that does not accept a keyword argument.
|
|
*/
|
|
bool Dtool_ExtractOptionalArg(PyObject **result, PyObject *args, PyObject *kwds) {
|
|
if (kwds != nullptr && PyDict_GET_SIZE(kwds) != 0) {
|
|
return false;
|
|
}
|
|
if (PyTuple_GET_SIZE(args) == 1) {
|
|
*result = PyTuple_GET_ITEM(args, 0);
|
|
return true;
|
|
}
|
|
return (PyTuple_GET_SIZE(args) == 0);
|
|
}
|
|
|
|
#endif // HAVE_PYTHON
|