Files
pybindgen/pybindgen/cppclass.py
T
2020-03-15 20:23:09 +00:00

3920 lines
172 KiB
Python

import sys
PY3 = (sys.version_info[0] >= 3)
if PY3:
string_types = str,
else:
string_types = basestring,
from pybindgen.utils import any, mangle_name
import warnings
import traceback
from pybindgen.typehandlers.base import Parameter, ReturnValue, \
join_ctype_and_name, CodeGenerationError, \
param_type_matcher, return_type_matcher, CodegenErrorBase, \
DeclarationsScope, CodeBlock, NotSupportedError, ForwardWrapperBase, ReverseWrapperBase, \
TypeConfigurationError
from pybindgen.typehandlers.codesink import NullCodeSink, MemoryCodeSink
from pybindgen.cppattribute import CppInstanceAttributeGetter, CppInstanceAttributeSetter, \
CppStaticAttributeGetter, CppStaticAttributeSetter, \
PyGetSetDef, PyMetaclass
from pybindgen.pytypeobject import PyTypeObject, PyNumberMethods, PySequenceMethods
from pybindgen.cppcustomattribute import CppCustomInstanceAttributeGetter, CppCustomInstanceAttributeSetter
from pybindgen import settings
from pybindgen import utils
from pybindgen.cppclass_container import CppClassContainerTraits
from . import function
import collections
# Prepare for python 3.9
try:
collectionsCallable = collections.Callable
except AttributeError:
collectionsCallable = collections
try:
set
except NameError:
from sets import Set as set
def _type_no_ref(value_type):
if value_type.type_traits.type_is_reference:
return str(value_type.type_traits.target)
else:
return str(value_type.type_traits.ctype_no_modifiers)
def get_python_to_c_converter(value, root_module, code_sink):
if isinstance(value, CppClass):
val_converter = root_module.generate_python_to_c_type_converter(value.ThisClassReturn(value.full_name), code_sink)
val_name = value.full_name
elif isinstance(value, ReturnValue):
val_name = _type_no_ref(value)
if val_name != value.ctype:
value = ReturnValue.new(val_name)
val_converter = root_module.generate_python_to_c_type_converter(value, code_sink)
elif isinstance(value, Parameter):
val_name = _type_no_ref(value)
val_return_type = ReturnValue.new(val_name)
val_converter = root_module.generate_python_to_c_type_converter(val_return_type, code_sink)
else:
raise ValueError("Don't know how to convert %r" % (value,))
return val_converter, val_name
def get_c_to_python_converter(value, root_module, code_sink):
if isinstance(value, CppClass):
val_converter = root_module.generate_c_to_python_type_converter(value.ThisClassReturn(value.full_name), code_sink)
val_name = value.full_name
elif isinstance(value, ReturnValue):
val_converter = root_module.generate_c_to_python_type_converter(value, code_sink)
val_name = _type_no_ref(value)
elif isinstance(value, Parameter):
val_return_type = ReturnValue.new(value.ctype)
val_converter = root_module.generate_c_to_python_type_converter(val_return_type, code_sink)
val_name = _type_no_ref(value)
else:
raise ValueError("Don't know how to convert %s" % str(value))
return val_converter, val_name
class MemoryPolicy(object):
"""memory management policy for a C++ class or C/C++ struct"""
def __init__(self):
if type(self) is MemoryPolicy:
raise NotImplementedError("class is abstract")
def get_free_code(self, object_expression):
"""
Return a code statement to free an underlying C/C++ object.
"""
raise NotImplementedError
def get_pointer_type(self, class_full_name):
return "%s *" % (class_full_name,)
def get_pointer_to_void_name(self, object_name):
return "%s" % object_name
def get_instance_creation_function(self):
return default_instance_creation_function
def get_delete_code(self, cpp_class):
raise NotImplementedError
def get_pystruct_init_code(self, cpp_class, obj):
return ''
def register_ptr_parameter_and_return(self, cls, name):
class ThisClassPtrParameter(CppClassPtrParameter):
"""Register this C++ class as pass-by-pointer parameter"""
CTYPES = []
cpp_class = cls
cls.ThisClassPtrParameter = ThisClassPtrParameter
try:
param_type_matcher.register(name+'*', cls.ThisClassPtrParameter)
except ValueError:
pass
class ThisClassPtrReturn(CppClassPtrReturnValue):
"""Register this C++ class as pointer return"""
CTYPES = []
cpp_class = cls
cls.ThisClassPtrReturn = ThisClassPtrReturn
try:
return_type_matcher.register(name+'*', cls.ThisClassPtrReturn)
except ValueError:
pass
def register_ptr_alias_parameter_and_return(self, cls, alias):
cls.ThisClassPtrParameter.CTYPES.append(alias+'*')
try:
param_type_matcher.register(alias+'*', cls.ThisClassPtrParameter)
except ValueError: pass
cls.ThisClassPtrReturn.CTYPES.append(alias+'*')
try:
return_type_matcher.register(alias+'*', cls.ThisClassPtrReturn)
except ValueError: pass
class ReferenceCountingPolicy(MemoryPolicy):
def write_incref(self, code_block, obj_expr):
"""
Write code to increase the reference code of an object of this
class (the real C++ class, not the wrapper). Should only be
called if the class supports reference counting, as reported
by the attribute `CppClass.has_reference_counting`.
"""
raise NotImplementedError
def write_decref(self, code_block, obj_expr):
"""
Write code to decrease the reference code of an object of this
class (the real C++ class, not the wrapper). Should only be
called if the class supports reference counting, as reported
by the attribute `CppClass.has_reference_counting`.
"""
raise NotImplementedError
class ReferenceCountingMethodsPolicy(ReferenceCountingPolicy):
def __init__(self, incref_method, decref_method, peekref_method=None):
super(ReferenceCountingMethodsPolicy, self).__init__()
self.incref_method = incref_method
self.decref_method = decref_method
self.peekref_method = peekref_method
def write_incref(self, code_block, obj_expr):
code_block.write_code('%s->%s();' % (obj_expr, self.incref_method))
def write_decref(self, code_block, obj_expr):
code_block.write_code('%s->%s();' % (obj_expr, self.decref_method))
def get_delete_code(self, cpp_class):
delete_code = ("if (self->obj) {\n"
" %s *tmp = self->obj;\n"
" self->obj = NULL;\n"
" tmp->%s();\n"
"}"
% (cpp_class.full_name, self.decref_method))
return delete_code
def __repr__(self):
return 'cppclass.ReferenceCountingMethodsPolicy(incref_method=%r, decref_method=%r, peekref_method=%r)' \
% (self.incref_method, self.decref_method, self.peekref_method)
class ReferenceCountingFunctionsPolicy(ReferenceCountingPolicy):
def __init__(self, incref_function, decref_function, peekref_function=None):
super(ReferenceCountingFunctionsPolicy, self).__init__()
self.incref_function = incref_function
self.decref_function = decref_function
self.peekref_function = peekref_function
def write_incref(self, code_block, obj_expr):
code_block.write_code('%s(%s);' % (self.incref_function, obj_expr))
def write_decref(self, code_block, obj_expr):
code_block.write_code('%s(%s);' % (self.decref_function, obj_expr))
def get_delete_code(self, cpp_class):
delete_code = ("if (self->obj) {\n"
" %s *tmp = self->obj;\n"
" self->obj = NULL;\n"
" %s(tmp);\n"
"}"
% (cpp_class.full_name, self.decref_function))
return delete_code
def __repr__(self):
return 'cppclass.ReferenceCountingFunctionsPolicy(incref_function=%r, decref_function=%r, peekref_function=%r)' \
% (self.incref_function, self.decref_function, self.peekref_function)
class FreeFunctionPolicy(MemoryPolicy):
def __init__(self, free_function):
super(FreeFunctionPolicy, self).__init__()
self.free_function = free_function
def get_delete_code(self, cpp_class):
delete_code = ("if (self->obj) {\n"
" %s *tmp = self->obj;\n"
" self->obj = NULL;\n"
" %s(tmp);\n"
"}"
% (cpp_class.full_name, self.free_function))
return delete_code
def __repr__(self):
return 'cppclass.FreeFunctionPolicy(%r)' % self.free_function
class SmartPointerPolicy(MemoryPolicy):
pointer_template = None # class should fill this or create descriptor/getter
def default_instance_creation_function(cpp_class, code_block, lvalue,
parameters, construct_type_name):
"""
Default "instance creation function"; it is called whenever a new
C++ class instance needs to be created; this default
implementation uses a standard C++ new allocator.
:param cpp_class: the CppClass object whose instance is to be created
:param code_block: CodeBlock object on which the instance creation code should be generated
:param lvalue: lvalue expression that should hold the result in the end
:param parameters: stringified list of parameters
:param construct_type_name: actual name of type to be constructed (it is
not always the class name, sometimes it's
the python helper class)
"""
assert lvalue
assert not lvalue.startswith('None')
if cpp_class.incomplete_type:
raise CodeGenerationError("%s cannot be constructed (incomplete type)"
% cpp_class.full_name)
code_block.write_code(
"%s = new %s(%s);" % (lvalue, construct_type_name, parameters))
class CppHelperClass(object):
"""
Generates code for a C++ proxy subclass that takes care of
forwarding virtual methods from C++ to Python.
"""
def __init__(self, class_):
"""
:param class_: original CppClass wrapper object
"""
self.class_ = class_
self.name = class_.pystruct + "__PythonHelper"
self.virtual_parent_callers = {}
self.virtual_proxies = []
self.cannot_be_constructed = False
self.custom_methods = []
self.post_generation_code = []
self.virtual_methods = []
def add_virtual_method(self, method):
assert method.is_virtual
assert method.class_ is not None
for existing in self.virtual_methods:
if method.matches_signature(existing):
return # don't re-add already existing method
if isinstance(method, CppDummyMethod):
if method.is_pure_virtual:
self.cannot_be_constructed = True
else:
self.virtual_methods.append(method)
if not method.is_pure_virtual:
if settings._get_deprecated_virtuals():
vis = ['public', 'protected']
else:
vis = ['protected']
if method.visibility in vis:
parent_caller = CppVirtualMethodParentCaller(method)
#parent_caller.class_ = method.class_
parent_caller.helper_class = self
parent_caller.main_wrapper = method # XXX: need to explain this
self.add_virtual_parent_caller(parent_caller)
proxy = CppVirtualMethodProxy(method)
proxy.main_wrapper = method # XXX: need to explain this
self.add_virtual_proxy(proxy)
def add_virtual_parent_caller(self, parent_caller):
"""Add a new CppVirtualMethodParentCaller object to this helper class"""
assert isinstance(parent_caller, CppVirtualMethodParentCaller)
name = parent_caller.method_name
try:
overload = self.virtual_parent_callers[name]
except KeyError:
overload = CppOverloadedMethod(name)
## implicit conversions + virtual methods disabled
## temporarily until I can figure out how to fix the unit
## tests.
overload.enable_implicit_conversions = False
#overload.static_decl = False
overload.pystruct = self.class_.pystruct
self.virtual_parent_callers[name] = overload
assert self.class_ is not None
for existing in overload.wrappers:
if parent_caller.matches_signature(existing):
break # don't re-add already existing method
else:
overload.add(parent_caller)
def add_custom_method(self, declaration, body=None):
"""
Add a custom method to the helper class, given by a
declaration line and a body. The body can be None, in case
the whole method definition is included in the declaration
itself.
"""
self.custom_methods.append((declaration, body))
def add_post_generation_code(self, code):
"""
Add custom code to be included right after the helper class is generated.
"""
self.post_generation_code.append(code)
def add_virtual_proxy(self, virtual_proxy):
"""Add a new CppVirtualMethodProxy object to this class"""
assert isinstance(virtual_proxy, CppVirtualMethodProxy)
self.virtual_proxies.append(virtual_proxy)
def generate_forward_declarations(self, code_sink_param):
"""
Generate the proxy class (declaration only) to a given code sink
"""
code_sink = MemoryCodeSink()
if self._generate_forward_declarations(code_sink):
code_sink.flush_to(code_sink_param)
else:
self.cannot_be_constructed = True
def _generate_forward_declarations(self, code_sink):
"""
Generate the proxy class (declaration only) to a given code sink.
Returns True if all is well, False if a pure virtual method
was found that could not be generated.
"""
code_sink.writeln("class %s : public %s\n{\npublic:" %
(self.name, self.class_.full_name))
code_sink.indent()
code_sink.writeln("PyObject *m_pyself;")
if not self.class_.import_from_module:
## replicate the parent constructors in the helper class
implemented_constructor_signatures = []
for cons in self.class_.constructors:
## filter out duplicated constructors
signature = [param.ctype for param in cons.parameters]
if signature in implemented_constructor_signatures:
continue
implemented_constructor_signatures.append(signature)
params = [join_ctype_and_name(param.ctype, param.name)
for param in cons.parameters]
code_sink.writeln("%s(%s)" % (self.name, ', '.join(params)))
code_sink.indent()
code_sink.writeln(": %s(%s), m_pyself(NULL)\n{}" %
(self.class_.full_name,
', '.join([param.name for param in cons.parameters])))
code_sink.unindent()
code_sink.writeln()
## add the set_pyobj method
code_sink.writeln("""
void set_pyobj(PyObject *pyobj)
{
Py_XDECREF(m_pyself);
Py_INCREF(pyobj);
m_pyself = pyobj;
}
""")
## write a destructor
code_sink.writeln("virtual ~%s()\n{" % self.name)
code_sink.indent()
code_sink.writeln("Py_CLEAR(m_pyself);")
code_sink.unindent()
code_sink.writeln("}\n")
if not self.class_.import_from_module:
## write the parent callers (_name)
for parent_caller in self.virtual_parent_callers.values():
#parent_caller.class_ = self.class_
parent_caller.helper_class = self
parent_caller.reset_code_generation_state()
## test code generation
try:
try:
utils.call_with_error_handling(parent_caller.generate,
(NullCodeSink(),), {}, parent_caller)
except utils.SkipWrapper:
continue
finally:
parent_caller.reset_code_generation_state()
code_sink.writeln()
parent_caller.generate_class_declaration(code_sink)
for parent_caller_wrapper in parent_caller.wrappers:
parent_caller_wrapper.generate_parent_caller_method(code_sink)
## write the virtual proxies
for virtual_proxy in self.virtual_proxies:
#virtual_proxy.class_ = self.class_
virtual_proxy.helper_class = self
## test code generation
#virtual_proxy.class_ = self.class_
#virtual_proxy.helper_class = self
virtual_proxy.reset_code_generation_state()
try:
try:
utils.call_with_error_handling(virtual_proxy.generate,
(NullCodeSink(),), {}, virtual_proxy)
except utils.SkipWrapper:
if virtual_proxy.method.is_pure_virtual:
return False
continue
finally:
virtual_proxy.reset_code_generation_state()
code_sink.writeln()
virtual_proxy.generate_declaration(code_sink)
for custom_declaration, dummy in self.custom_methods:
code_sink.writeln(custom_declaration)
code_sink.unindent()
code_sink.writeln("};\n")
if not self.class_.import_from_module:
for code in self.post_generation_code:
code_sink.writeln(code)
code_sink.writeln()
return True
def generate(self, code_sink):
"""
Generate the proxy class (virtual method bodies only) to a given code sink.
returns pymethodef list of parent callers
"""
if self.class_.import_from_module:
return
## write the parent callers (_name)
method_defs = []
for name, parent_caller in self.virtual_parent_callers.items():
#parent_caller.class_ = self.class_
parent_caller.helper_class = self
code_sink.writeln()
## parent_caller.generate(code_sink)
try:
utils.call_with_error_handling(parent_caller.generate,
(code_sink,), {}, parent_caller)
except utils.SkipWrapper:
continue
if settings._get_deprecated_virtuals():
parent_caller_name = '_'+name
else:
parent_caller_name = name
method_defs.append(parent_caller.get_py_method_def(parent_caller_name))
## write the virtual proxies
for virtual_proxy in self.virtual_proxies:
#virtual_proxy.class_ = self.class_
virtual_proxy.helper_class = self
code_sink.writeln()
## virtual_proxy.generate(code_sink)
try:
utils.call_with_error_handling(virtual_proxy.generate,
(code_sink,), {}, virtual_proxy)
except utils.SkipWrapper:
assert not virtual_proxy.method.is_pure_virtual
continue
for dummy, custom_body in self.custom_methods:
if custom_body:
code_sink.writeln(custom_body)
return method_defs
class CppClass(object):
"""
A CppClass object takes care of generating the code for wrapping a C++ class
"""
def __init__(self, name, parent=None, incref_method=None, decref_method=None,
automatic_type_narrowing=None, allow_subclassing=None,
is_singleton=False, outer_class=None,
peekref_method=None,
template_parameters=(), custom_template_class_name=None,
incomplete_type=False, free_function=None,
incref_function=None, decref_function=None,
python_name=None, memory_policy=None,
foreign_cpp_namespace=None,
docstring=None,
custom_name=None,
import_from_module=None,
destructor_visibility='public'
):
"""
:param name: class name
:param parent: optional parent class wrapper, or list of
parents. Valid values are None, a CppClass
instance, or a list of CppClass instances.
:param incref_method: (deprecated in favour of memory_policy) if the class supports reference counting, the
name of the method that increments the
reference count (may be inherited from parent
if not given)
:param decref_method: (deprecated in favour of memory_policy) if the class supports reference counting, the
name of the method that decrements the
reference count (may be inherited from parent
if not given)
:param automatic_type_narrowing: if True, automatic return type
narrowing will be done on objects
of this class and its descendants
when returned by pointer from a
function or method.
:param allow_subclassing: if True, generated class wrappers will
allow subclassing in Python.
:param is_singleton: if True, the class is considered a singleton,
and so the python wrapper will never call the
C++ class destructor to free the value.
:param peekref_method: (deprecated in favour of memory_policy) if the class supports reference counting, the
name of the method that returns the current reference count.
:param free_function: (deprecated in favour of memory_policy) name of C function used to deallocate class instances
:param incref_function: (deprecated in favour of memory_policy) same as incref_method, but as a function instead of method
:param decref_function: (deprecated in favour of memory_policy) same as decref_method, but as a function instead of method
:param python_name: name of the class as it will appear from
Python side. This parameter is DEPRECATED in favour of
custom_name.
:param memory_policy: memory management policy; if None, it
inherits from the parent class. Only root classes can have a
memory policy defined.
:type memory_policy: L{MemoryPolicy}
:param foreign_cpp_namespace: if set, the class is assumed to
belong to the given C++ namespace, regardless of the C++
namespace of the python module it will be added to. For
instance, this can be useful to wrap std classes, like
std::ofstream, without having to create an extra python
submodule.
:param docstring: None or a string containing the docstring
that will be generated for the class
:param custom_name: an alternative name to give to this class
at python-side; if omitted, the name of the class in the
python module will be the same name as the class in C++
(minus namespace).
:param import_from_module: if not None, the type is imported
from a foreign Python module with the given name.
"""
assert outer_class is None or isinstance(outer_class, CppClass)
self.incomplete_type = incomplete_type
self.outer_class = outer_class
self._module = None
self.name = name
self.docstring = docstring
self.mangled_name = None
self.mangled_full_name = None
self.template_parameters = template_parameters
self.container_traits = None
self.import_from_module = import_from_module
assert destructor_visibility in ['public', 'private', 'protected']
self.destructor_visibility = destructor_visibility
self.custom_name = custom_name
if custom_template_class_name:
warnings.warn("Use the custom_name parameter.",
DeprecationWarning, stacklevel=2)
self.custom_name = custom_template_class_name
if python_name:
warnings.warn("Use the custom_name parameter.",
DeprecationWarning, stacklevel=2)
self.custom_name = python_name
self.is_singleton = is_singleton
self.foreign_cpp_namespace = foreign_cpp_namespace
self.full_name = None # full name with C++ namespaces attached and template parameters
self.methods = collections.OrderedDict() # name => OverloadedMethod
self._dummy_methods = [] # methods that have parameter/retval binding problems
self.nonpublic_methods = []
self.constructors = [] # (name, wrapper) pairs
self.pytype = PyTypeObject()
self.slots = self.pytype.slots
self.helper_class = None
self.instance_creation_function = None
self.post_instance_creation_function = None
## set to True when we become aware generating the helper
## class is not going to be possible
self.helper_class_disabled = False
self.cannot_be_constructed = '' # reason
self.has_trivial_constructor = False
self.has_copy_constructor = False
self.has_output_stream_operator = False
self._have_pure_virtual_methods = None
self._wrapper_registry = None
self.binary_comparison_operators = set()
self.binary_numeric_operators = dict()
self.inplace_numeric_operators = dict()
self.unary_numeric_operators = dict()
self.valid_sequence_methods = {"__len__" : "sq_length",
"__add__" : "sq_concat",
"__mul__" : "sq_repeat",
"__getitem__" : "sq_item",
"__getslice__" : "sq_slice",
"__setitem__" : "sq_ass_item",
"__setslice__" : "sq_ass_slice",
"__contains__" : "sq_contains",
"__iadd__" : "sq_inplace_concat",
"__imul__" : "sq_inplace_repeat"}
## list of CppClasses from which a value of this class can be
## implicitly generated; corresponds to a
## operator ThisClass(); in the other class.
self.implicitly_converts_from = []
## list of hook functions to call just prior to helper class
## code generation.
self.helper_class_hooks = []
self._pystruct = None #"***GIVE ME A NAME***"
self.metaclass_name = "***GIVE ME A NAME***"
self.pytypestruct = "***GIVE ME A NAME***"
self.instance_attributes = PyGetSetDef("%s__getsets" % self._pystruct)
self.static_attributes = PyGetSetDef("%s__getsets" % self.metaclass_name)
if isinstance(parent, list):
self.bases = list(parent)
self.parent = self.bases[0]
elif isinstance(parent, CppClass):
self.parent = parent
self.bases = [parent]
elif parent is None:
self.parent = None
self.bases = []
else:
raise TypeError("'parent' must be None, CppClass instance, or a list of CppClass instances")
if free_function:
warnings.warn("Use FreeFunctionPolicy and memory_policy parameter.", DeprecationWarning)
assert memory_policy is None
memory_policy = FreeFunctionPolicy(free_function)
elif incref_method:
warnings.warn("Use ReferenceCountingMethodsPolicy and memory_policy parameter.", DeprecationWarning)
assert memory_policy is None
memory_policy = ReferenceCountingMethodsPolicy(incref_method, decref_method, peekref_method)
elif incref_function:
warnings.warn("Use ReferenceCountingFunctionsPolicy and memory_policy parameter.", DeprecationWarning)
assert memory_policy is None
memory_policy = ReferenceCountingFunctionsPolicy(incref_function, decref_function)
if not self.bases:
assert memory_policy is None or isinstance(memory_policy, MemoryPolicy)
self.memory_policy = memory_policy
else:
for base in self.bases:
if base.memory_policy is not None:
self.memory_policy = base.memory_policy
assert memory_policy is None, \
"changing memory policy from parent (%s) to child (%s) class not permitted" \
% (base.name, self.name)
break
else:
self.memory_policy = memory_policy
if automatic_type_narrowing is None:
if not self.bases:
self.automatic_type_narrowing = settings.automatic_type_narrowing
else:
self.automatic_type_narrowing = self.parent.automatic_type_narrowing
else:
self.automatic_type_narrowing = automatic_type_narrowing
if allow_subclassing is None:
if self.parent is None:
self.allow_subclassing = settings.allow_subclassing
else:
self.allow_subclassing = self.parent.allow_subclassing
else:
if any([p.allow_subclassing for p in self.bases]) and not allow_subclassing:
raise ValueError("Cannot disable subclassing if a parent class allows it")
else:
self.allow_subclassing = allow_subclassing
if self.destructor_visibility not in ['public', 'protected']:
self.allow_subclassing = False
self.typeid_map_name = None
if name != 'dummy':
## register type handlers
class ThisClassParameter(CppClassParameter):
"""Register this C++ class as pass-by-value parameter"""
CTYPES = []
cpp_class = self
self.ThisClassParameter = ThisClassParameter
try:
param_type_matcher.register(name, self.ThisClassParameter)
except ValueError:
pass
class ThisClassRefParameter(CppClassRefParameter):
"""Register this C++ class as pass-by-reference parameter"""
CTYPES = []
cpp_class = self
self.ThisClassRefParameter = ThisClassRefParameter
try:
param_type_matcher.register(name+'&', self.ThisClassRefParameter)
except ValueError:
pass
class ThisClassReturn(CppClassReturnValue):
"""Register this C++ class as value return"""
CTYPES = []
cpp_class = self
self.ThisClassReturn = ThisClassReturn
self.ThisClassRefReturn = ThisClassReturn
try:
return_type_matcher.register(name, self.ThisClassReturn)
return_type_matcher.register(name, self.ThisClassRefReturn)
except ValueError:
pass
if self.memory_policy is not None:
self.memory_policy.register_ptr_parameter_and_return(self, name)
else: # Regular pointer
class ThisClassPtrParameter(CppClassPtrParameter):
"""Register this C++ class as pass-by-pointer parameter"""
CTYPES = []
cpp_class = self
self.ThisClassPtrParameter = ThisClassPtrParameter
try:
param_type_matcher.register(name+'*', self.ThisClassPtrParameter)
except ValueError:
pass
class ThisClassPtrReturn(CppClassPtrReturnValue):
"""Register this C++ class as pointer return"""
CTYPES = []
cpp_class = self
self.ThisClassPtrReturn = ThisClassPtrReturn
try:
return_type_matcher.register(name+'*', self.ThisClassPtrReturn)
except ValueError:
pass
class ThisClassRefReturn(CppClassRefReturnValue):
"""Register this C++ class as reference return"""
CTYPES = []
cpp_class = self
self.ThisClassRefReturn = ThisClassRefReturn
try:
return_type_matcher.register(name+'&', self.ThisClassRefReturn)
except ValueError:
pass
def __repr__(self):
return "<pybindgen.CppClass %r>" % self.full_name
def add_container_traits(self, *args, **kwargs):
assert self.container_traits is None
self.container_traits = CppClassContainerTraits(self, *args, **kwargs)
def add_binary_comparison_operator(self, operator):
"""
Add support for a C++ binary comparison operator, such as == or <.
The binary operator is assumed to operate with both operands
of the type of the class, either by reference or by value.
:param operator: string indicating the name of the operator to
support, e.g. '=='
"""
operator = utils.ascii(operator)
if not isinstance(operator, string_types):
raise TypeError("expected operator name as string")
if operator not in ['==', '!=', '<', '<=', '>', '>=']:
raise ValueError("The operator %r is invalid or not yet supported by PyBindGen" % (operator,))
self.binary_comparison_operators.add(operator)
def add_binary_numeric_operator(self, operator, result_cppclass=None,
left_cppclass=None, right=None):
"""
Add support for a C++ binary numeric operator, such as +, -, \\*, or /.
:param operator: string indicating the name of the operator to
support, e.g. '=='
:param result_cppclass: the CppClass object of the result type, assumed to be this class if omitted
:param left_cppclass: the CppClass object of the left operand type, assumed to be this class if omitted
:param right: the type of the right parameter. Can be a
CppClass, Parameter, or param spec. Assumed to be this class
if omitted
"""
operator = utils.ascii(operator)
if not isinstance(operator, string_types):
raise TypeError("expected operator name as string")
if operator not in ['+', '-', '*', '/']:
raise ValueError("The operator %r is invalid or not yet supported by PyBindGen" % (operator,))
try:
l = self.binary_numeric_operators[operator]
except KeyError:
l = []
self.binary_numeric_operators[operator] = l
if result_cppclass is None:
result_cppclass = self
if left_cppclass is None:
left_cppclass = self
if right is None:
right = self
elif isinstance(right, CppClass):
pass
else:
if isinstance(right, string_types):
right = utils.param(right, 'right')
try:
right = utils.eval_param(right, None)
except utils.SkipWrapper:
return
op = (result_cppclass, left_cppclass, right)
if op not in l:
l.append(op)
def add_inplace_numeric_operator(self, operator, right=None):
"""
Add support for a C++ inplace numeric operator, such as +=, -=, \\*=, or /=.
:param operator: string indicating the name of the operator to
support, e.g. '+='
:param right: the type of the right parameter. Can be a
CppClass, Parameter, or param spec. Assumed to be this class
if omitted
"""
operator = utils.ascii(operator)
if not isinstance(operator, string_types):
raise TypeError("expected operator name as string")
if operator not in ['+=', '-=', '*=', '/=']:
raise ValueError("The operator %r is invalid or not yet supported by PyBindGen" % (operator,))
try:
l = self.inplace_numeric_operators[operator]
except KeyError:
l = []
self.inplace_numeric_operators[operator] = l
if right is None:
right = self
else:
if isinstance(right, string_types):
right = utils.param(right, 'right')
try:
right = utils.eval_param(right, None)
except utils.SkipWrapper:
return
if right not in l:
l.append((self, self, right))
def add_unary_numeric_operator(self, operator, result_cppclass=None, left_cppclass=None):
"""
Add support for a C++ unary numeric operators, currently only -.
:param operator: string indicating the name of the operator to
support, e.g. '-'
:param result_cppclass: the CppClass object of the result type, assumed to be this class if omitted
:param left_cppclass: the CppClass object of the left operand type, assumed to be this class if omitted
"""
operator = utils.ascii(operator)
if not isinstance(operator, string_types):
raise TypeError("expected operator name as string")
if operator not in ['-']:
raise ValueError("The operator %r is invalid or not yet supported by PyBindGen" % (operator,))
try:
l = self.unary_numeric_operators[operator]
except KeyError:
l = []
self.unary_numeric_operators[operator] = l
if result_cppclass is None:
result_cppclass = self
if left_cppclass is None:
left_cppclass = self
op = (result_cppclass, left_cppclass)
if op not in l:
l.append(op)
def add_class(self, *args, **kwargs):
"""
Add a nested class. See L{CppClass} for information about accepted parameters.
"""
assert 'outer_class' not in kwargs
kwargs['outer_class'] = self
return self.module.add_class(*args, **kwargs)
def add_enum(self, *args, **kwargs):
"""
Add a nested enum. See L{Enum} for information about accepted parameters.
"""
assert 'outer_class' not in kwargs
kwargs['outer_class'] = self
return self.module.add_enum(*args, **kwargs)
def get_mro(self):
"""
Get the method resolution order (MRO) of this class.
:return: an iterator that gives CppClass objects, from leaf to root class
"""
to_visit = [self]
visited = set()
while to_visit:
cls = to_visit.pop(0)
visited.add(cls)
yield cls
for base in cls.bases:
if base not in visited:
to_visit.append(base)
def get_all_methods(self):
"""Returns an iterator to iterate over all methods of the class"""
for overload in self.methods.values():
for method in overload.wrappers:
yield method
for method in self.nonpublic_methods:
yield method
def get_have_pure_virtual_methods(self):
"""
Returns True if the class has pure virtual methods with no
implementation (which would mean the type is not instantiable
directly, only through a helper class).
"""
if self._have_pure_virtual_methods is not None:
return self._have_pure_virtual_methods
mro = list(self.get_mro())
mro_reversed = list(mro)
mro_reversed.reverse()
self._have_pure_virtual_methods = False
for pos, cls in enumerate(mro_reversed):
for method in list(cls.get_all_methods()) + cls._dummy_methods:
if not isinstance(method, CppMethod):
continue
if method.is_pure_virtual:
## found a pure virtual method; now go see in the
## child classes, check if any of them implements
## this pure virtual method.
implemented = False
for child_cls in mro_reversed[pos+1:]:
for child_method in list(child_cls.get_all_methods()) + child_cls._dummy_methods:
if not isinstance(child_method, CppMethod):
continue
if not child_method.is_virtual:
continue
if not child_method.matches_signature(method):
continue
if not child_method.is_pure_virtual:
implemented = True
break
if implemented:
break
if not implemented:
self._have_pure_virtual_methods = True
return self._have_pure_virtual_methods
have_pure_virtual_methods = property(get_have_pure_virtual_methods)
def is_subclass(self, other):
"""Return True if this CppClass instance represents a class that is a
subclass of another class represented by the CppClasss object \\`other\\'."""
if not isinstance(other, CppClass):
raise TypeError
return other in self.get_mro()
def add_helper_class_hook(self, hook):
"""
Add a hook function to be called just prior to a helper class
being generated. The hook function applies to this class and
all subclasses. The hook function is called like this::
hook_function(helper_class)
"""
if not isinstance(hook, collectionsCallable):
raise TypeError("hook function must be callable")
self.helper_class_hooks.append(hook)
def _get_all_helper_class_hooks(self):
"""
Returns a list of all helper class hook functions, including
the ones registered with parent classes. Parent hooks will
appear first in the list.
"""
l = []
for cls in self.get_mro():
l = cls.helper_class_hooks + l
return l
def set_instance_creation_function(self, instance_creation_function):
"""Set a custom function to be called to create instances of this
class and its subclasses.
:param instance_creation_function: instance creation function; see
default_instance_creation_function()
for signature and example.
"""
self.instance_creation_function = instance_creation_function
def set_post_instance_creation_function(self, post_instance_creation_function):
"""Set a custom function to be called to add code after an
instance is created (usually by the "instance creation
function") and registered with the Python runtime.
:param post_instance_creation_function: post instance creation function
"""
self.post_instance_creation_function = post_instance_creation_function
def get_instance_creation_function(self):
for cls in self.get_mro():
if cls.instance_creation_function is not None:
return cls.instance_creation_function
if cls.memory_policy is not None:
return cls.memory_policy.get_instance_creation_function()
return default_instance_creation_function
def get_post_instance_creation_function(self):
for cls in self.get_mro():
if cls.post_instance_creation_function is not None:
return cls.post_instance_creation_function
return None
def write_create_instance(self, code_block, lvalue, parameters, construct_type_name=None):
instance_creation_func = self.get_instance_creation_function()
if construct_type_name is None:
construct_type_name = self.get_construct_name()
instance_creation_func(self, code_block, lvalue, parameters, construct_type_name)
def write_post_instance_creation_code(self, code_block, lvalue, parameters, construct_type_name=None):
post_instance_creation_func = self.get_post_instance_creation_function()
if post_instance_creation_func is None:
return
if construct_type_name is None:
construct_type_name = self.get_construct_name()
post_instance_creation_func(self, code_block, lvalue, parameters, construct_type_name)
def get_pystruct(self):
if self._pystruct is None:
raise ValueError
return self._pystruct
pystruct = property(get_pystruct)
def get_construct_name(self):
"""Get a name usable for new %s construction, or raise
CodeGenerationError if none found"""
if self.cannot_be_constructed:
raise CodeGenerationError("%s cannot be constructed (%s)" % (self.full_name, self.cannot_be_constructed))
if self.have_pure_virtual_methods:
raise CodeGenerationError("%s cannot be constructed (class has pure virtual methods)" % self.full_name)
else:
return self.full_name
def implicitly_converts_to(self, other):
"""
Declares that values of this class can be implicitly converted
to another class; corresponds to a operator AnotherClass();
special method.
"""
assert isinstance(other, CppClass)
other.implicitly_converts_from.append(self)
def get_all_implicit_conversions(self):
"""
Gets a new list of all other classes whose value can be implicitly
converted to a value of this class.
>>> Foo = CppClass("Foo")
>>> Bar = CppClass("Bar")
>>> Zbr = CppClass("Zbr")
>>> Bar.implicitly_converts_to(Foo)
>>> Zbr.implicitly_converts_to(Bar)
>>> l = Foo.get_all_implicit_conversions()
>>> l.sort(lambda cls1, cls2: cmp(cls1.name, cls2.name))
>>> [cls.name for cls in l]
['Bar']
"""
return list(self.implicitly_converts_from)
# classes = []
# to_visit = list(self.implicitly_converts_from)
# while to_visit:
# source = to_visit.pop(0)
# if source in classes or source is self:
# continue
# classes.append(source)
# to_visit.extend(source.implicitly_converts_from)
# return classes
def _update_names(self):
prefix = settings.name_prefix.capitalize()
if self.outer_class is None:
if self.foreign_cpp_namespace:
self.full_name = self.foreign_cpp_namespace + '::' + self.name
else:
if self._module.cpp_namespace_prefix:
if self._module.cpp_namespace_prefix == '::':
self.full_name = '::' + self.name
else:
self.full_name = self._module.cpp_namespace_prefix + '::' + self.name
else:
self.full_name = self.name
else:
assert not self.foreign_cpp_namespace
self.full_name = '::'.join([self.outer_class.full_name, self.name])
def make_upper(s):
if s and s[0].islower():
return s[0].upper()+s[1:]
else:
return s
def mangle(name):
return mangle_name(name)
def flatten(name):
"make a name like::This look LikeThis"
return ''.join([make_upper(mangle(s)) for s in name.split('::')])
self.mangled_name = flatten(self.name)
self.mangled_full_name = flatten(self.full_name)
if self.template_parameters:
self.full_name += "< %s >" % (', '.join(self.template_parameters))
mangled_template_params = '__' + '_'.join([flatten(s) for s in self.template_parameters])
self.mangled_name += mangled_template_params
self.mangled_full_name += mangled_template_params
self._pystruct = "Py%s%s" % (prefix, self.mangled_full_name)
self.metaclass_name = "%sMeta" % self.mangled_full_name
self.pytypestruct = "Py%s%s_Type" % (prefix, self.mangled_full_name)
self.instance_attributes.cname = "%s__getsets" % self._pystruct
self.static_attributes.cname = "%s__getsets" % self.metaclass_name
## re-register the class type handlers, now with class full name
self.register_alias(self.full_name)
if self.get_type_narrowing_root() is self:
self.typeid_map_name = "%s__typeid_map" % self.pystruct
else:
self.typeid_map_name = None
def register_alias(self, alias):
"""Re-register the class with another base name, in addition to any
registrations that might have already been done."""
self.module.register_type(None, alias, self)
self.ThisClassParameter.CTYPES.append(alias)
try:
param_type_matcher.register(alias, self.ThisClassParameter)
except ValueError: pass
self.ThisClassRefParameter.CTYPES.append(alias+'&')
try:
param_type_matcher.register(alias+'&', self.ThisClassRefParameter)
except ValueError: pass
self.ThisClassReturn.CTYPES.append(alias)
try:
return_type_matcher.register(alias, self.ThisClassReturn)
except ValueError: pass
if self.memory_policy is not None:
self.memory_policy.register_ptr_alias_parameter_and_return(self, alias)
else:
self.ThisClassPtrParameter.CTYPES.append(alias+'*')
try:
param_type_matcher.register(alias+'*', self.ThisClassPtrParameter)
except ValueError: pass
self.ThisClassPtrReturn.CTYPES.append(alias+'*')
try:
return_type_matcher.register(alias+'*', self.ThisClassPtrReturn)
except ValueError: pass
self.ThisClassRefReturn.CTYPES.append(alias)
try:
return_type_matcher.register(alias+'&', self.ThisClassRefReturn)
except ValueError: pass
def get_module(self):
"""Get the Module object this class belongs to"""
return self._module
def set_module(self, module):
"""Set the Module object this class belongs to"""
self._module = module
self._update_names()
module = property(get_module, set_module)
def inherit_default_constructors(self):
"""inherit the default constructors from the parentclass according to C++
language rules"""
for base in self.bases:
for cons in base.constructors:
if len(cons.parameters) == 0:
self.add_constructor([], visibility=cons.visibility)
elif (len(cons.parameters) == 1
and isinstance(cons.parameters[0], self.parent.ThisClassRefParameter)):
self.add_constructor([self.ThisClassRefParameter(
self.full_name + "&",
"obj",
cons.parameters[0].direction)],
visibility=cons.visibility)
def get_helper_class(self):
"""gets the "helper class" for this class wrapper, creating it if necessary"""
for cls in self.get_mro():
if cls.helper_class_disabled:
return None
if not self.allow_subclassing:
return None
if self.helper_class is None:
if not self.is_singleton:
self.helper_class = CppHelperClass(self)
self.module.add_include('<typeinfo>')
return self.helper_class
def get_type_narrowing_root(self):
"""Find the root CppClass along the subtree of all parent classes that
have automatic_type_narrowing=True Note: multiple inheritance
not implemented"""
if not self.automatic_type_narrowing:
return None
root = self
while (root.parent is not None
and root.parent.automatic_type_narrowing):
root = root.parent
return root
def _register_typeid(self, module):
"""register this class with the typeid map root class"""
root = self.get_type_narrowing_root()
module.after_init.write_code("%s.register_wrapper(typeid(%s), &%s);"
% (root.typeid_map_name, self.full_name, self.pytypestruct))
def _generate_typeid_map(self, code_sink, module):
"""generate the typeid map and fill it with values"""
try:
module.declare_one_time_definition("TypeIDMap")
except KeyError:
pass
else:
code_sink.writeln('''
#include <map>
#include <string>
#include <typeinfo>
#if defined(__GNUC__) && __GNUC__ >= 3 && !defined(__clang__)
# include <cxxabi.h>
#endif
#define PBG_TYPEMAP_DEBUG 0
namespace pybindgen {
class TypeMap
{
std::map<std::string, PyTypeObject *> m_map;
public:
TypeMap() {}
void register_wrapper(const std::type_info &cpp_type_info, PyTypeObject *python_wrapper)
{
#if PBG_TYPEMAP_DEBUG
std::cerr << "register_wrapper(this=" << this << ", type_name=" << cpp_type_info.name()
<< ", python_wrapper=" << python_wrapper->tp_name << ")" << std::endl;
#endif
m_map[std::string(cpp_type_info.name())] = python_wrapper;
}
''')
if settings.gcc_rtti_abi_complete:
code_sink.writeln('''
PyTypeObject * lookup_wrapper(const std::type_info &cpp_type_info, PyTypeObject *fallback_wrapper)
{
#if PBG_TYPEMAP_DEBUG
std::cerr << "lookup_wrapper(this=" << this << ", type_name=" << cpp_type_info.name() << ")" << std::endl;
#endif
PyTypeObject *python_wrapper = m_map[cpp_type_info.name()];
if (python_wrapper)
return python_wrapper;
else {
#if defined(__GNUC__) && __GNUC__ >= 3 && !defined(__clang__)
// Get closest (in the single inheritance tree provided by cxxabi.h)
// registered python wrapper.
const abi::__si_class_type_info *_typeinfo =
dynamic_cast<const abi::__si_class_type_info*> (&cpp_type_info);
#if PBG_TYPEMAP_DEBUG
std::cerr << " -> looking at C++ type " << _typeinfo->name() << std::endl;
#endif
while (_typeinfo && (python_wrapper = m_map[std::string(_typeinfo->name())]) == 0) {
_typeinfo = dynamic_cast<const abi::__si_class_type_info*> (_typeinfo->__base_type);
#if PBG_TYPEMAP_DEBUG
std::cerr << " -> looking at C++ type " << _typeinfo->name() << std::endl;
#endif
}
#if PBG_TYPEMAP_DEBUG
if (python_wrapper) {
std::cerr << " -> found match " << std::endl;
} else {
std::cerr << " -> return fallback wrapper" << std::endl;
}
#endif
return python_wrapper? python_wrapper : fallback_wrapper;
#else // non gcc 3+ compilers can only match against explicitly registered classes, not hidden subclasses
return fallback_wrapper;
#endif
}
}
};
}
''')
else:
code_sink.writeln('''
PyTypeObject * lookup_wrapper(const std::type_info &cpp_type_info, PyTypeObject *fallback_wrapper)
{
#if PBG_TYPEMAP_DEBUG
std::cerr << "lookup_wrapper(this=" << this << ", type_name=" << cpp_type_info.name() << ")" << std::endl;
#endif
PyTypeObject *python_wrapper = m_map[cpp_type_info.name()];
return python_wrapper? python_wrapper : fallback_wrapper;
}
};
}
''')
if self.import_from_module:
code_sink.writeln("\nextern pybindgen::TypeMap *_%s;\n" % self.typeid_map_name)
code_sink.writeln("#define %s (*_%s)\n" % (self.typeid_map_name, self.typeid_map_name))
else:
code_sink.writeln("\nextern pybindgen::TypeMap %s;\n" % self.typeid_map_name)
def _add_method_obj(self, method):
"""
Add a method object to the class. For internal use.
:param method: a L{CppMethod} or L{Function} instance that can generate the method wrapper
"""
if isinstance(method, CppMethod):
name = method.mangled_name
elif isinstance(method, function.Function):
name = method.custom_name
assert isinstance(method.parameters[0], CppClassParameterBase)
assert method.parameters[0].cpp_class is self, \
"expected first parameter to be of class %s, but it is of class %s" % \
(self.full_name, method.parameters[0].cpp_class.full_name)
method.parameters[0].take_value_from_python_self = True
method.module = self.module
method.is_virtual = False
method.is_pure_virtual = False
method.self_parameter_pystruct = self.pystruct
method.visibility = 'public'
method.force_parse = method.PARSE_TUPLE_AND_KEYWORDS
else:
raise TypeError
method.class_ = self
if method.visibility == 'protected' and not method.is_virtual:
helper_class = self.get_helper_class()
if helper_class is not None:
parent_caller = CppVirtualMethodParentCaller(method)
parent_caller.helper_class = helper_class
parent_caller.main_wrapper = method
helper_class.add_virtual_parent_caller(parent_caller)
elif method.visibility == 'public':
if name == '__call__': # needs special handling
method.force_parse = method.PARSE_TUPLE_AND_KEYWORDS
try:
overload = self.methods[name]
except KeyError:
overload = CppOverloadedMethod(name)
overload.pystruct = self.pystruct
self.methods[name] = overload
## add it....
try:
utils.call_with_error_handling(overload.add, (method,), {}, method)
except utils.SkipWrapper:
return
# Grr! I hate C++. Overloading + inheritance = disaster!
# So I ended up coding something which C++ does not in
# fact support, but I feel bad to just throw away my good
# code due to a C++ fault, so I am leaving here the code
# disabled. Maybe some future C++ version will come along
# and fix this problem, who knows :P
if 0:
# due to a limitation of the pybindgen overloading
# strategy, we need to re-wrap for this class all
# methods with the same name and different signature
# from parent classes.
overload._compute_all_wrappers()
if isinstance(method, CppMethod):
mro = self.get_mro()
next(mro) # skip 'self'
for cls in mro:
try:
parent_overload = cls.methods[name]
except KeyError:
continue
parent_overload._compute_all_wrappers()
for parent_method in parent_overload.all_wrappers:
already_exists = False
for existing_method in overload.all_wrappers:
if existing_method.matches_signature(parent_method):
already_exists = True
break
if not already_exists:
new_method = parent_method.clone()
new_method.class_ = self
overload.add(new_method)
else:
self.nonpublic_methods.append(method)
if method.is_virtual:
self._have_pure_virtual_methods = None
helper_class = self.get_helper_class()
if helper_class is not None:
helper_class.add_virtual_method(method)
def add_method(self, *args, **kwargs):
"""
Add a method to the class. See the documentation for
L{CppMethod.__init__} for information on accepted parameters.
"""
## <compat>
if len(args) >= 1 and isinstance(args[0], CppMethod):
meth = args[0]
warnings.warn("add_method has changed API; see the API documentation",
DeprecationWarning, stacklevel=2)
if len(args) == 2:
meth.custom_name = args[1]
elif 'name' in kwargs:
assert len(args) == 1
meth.custom_name = kwargs['name']
else:
assert len(args) == 1
assert len(kwargs) == 0
elif len(args) >= 1 and isinstance(args[0], function.Function):
meth = args[0]
warnings.warn("add_method has changed API; see the API documentation",
DeprecationWarning, stacklevel=2)
if len(args) == 2:
meth.custom_name = args[1]
elif 'name' in kwargs:
assert len(args) == 1
meth.custom_name = kwargs['name']
else:
assert len(args) == 1
assert len(kwargs) == 0
## </compat>
else:
try:
meth = CppMethod(*args, **kwargs)
except utils.SkipWrapper:
if kwargs.get('is_virtual', False):
## if the method was supposed to be virtual, this
## is a very important fact that needs to be
## recorded in the class, even if the method is
## not wrapped.
method = CppDummyMethod(*args, **kwargs)
method.class_ = self
self._dummy_methods.append(method)
self._have_pure_virtual_methods = None
helper_class = self.get_helper_class()
if helper_class is not None:
helper_class.add_virtual_method(method)
if helper_class.cannot_be_constructed:
self.helper_class = None
self.helper_class_disabled = True
return None
self._add_method_obj(meth)
return meth
def add_function_as_method(self, *args, **kwargs):
"""
Add a function as method of the class. See the documentation for
L{Function.__init__} for information on accepted parameters.
TODO: explain the implicit first function parameter
"""
try:
meth = function.Function(*args, **kwargs)
except utils.SkipWrapper:
return None
self._add_method_obj(meth)
return meth
def add_custom_method_wrapper(self, *args, **kwargs):
"""
Adds a custom method wrapper. See L{CustomCppMethodWrapper} for more information.
"""
try:
meth = CustomCppMethodWrapper(*args, **kwargs)
except utils.SkipWrapper:
return None
self._add_method_obj(meth)
return meth
def set_helper_class_disabled(self, flag=True):
self.helper_class_disabled = flag
if flag:
self.helper_class = None
def set_cannot_be_constructed(self, reason):
assert isinstance(reason, string_types)
self.cannot_be_constructed = reason
def _add_constructor_obj(self, wrapper):
"""
Add a constructor to the class.
:param wrapper: a CppConstructor instance
"""
assert isinstance(wrapper, CppConstructor)
wrapper.set_class(self)
self.constructors.append(wrapper)
if not wrapper.parameters:
self.has_trivial_constructor = True # FIXME: I don't remember what is this used for anymore, maybe remove
if len(wrapper.parameters) == 1 and isinstance(wrapper.parameters[0], (CppClassRefParameter, CppClassParameter)) \
and wrapper.parameters[0].cpp_class is self and wrapper.visibility == 'public':
self.has_copy_constructor = True
def add_output_stream_operator(self):
"""
Add str() support based on C++ output stream operator.
Calling this method enables wrapping of an assumed to be defined operator function::
std::ostream & operator << (std::ostream &, MyClass const &);
The wrapper will be registered as an str() python operator,
and will call the C++ operator function to convert the value
to a string.
"""
self.has_output_stream_operator = True
self.module.add_include("<ostream>")
self.module.add_include("<sstream>")
def add_constructor(self, *args, **kwargs):
"""
Add a constructor to the class. See the documentation for
L{CppConstructor.__init__} for information on accepted parameters.
"""
## <compat>
if len(args) == 1 and isinstance(args[0], CppConstructor):
warnings.warn("add_constructor has changed API; see the API documentation",
DeprecationWarning, stacklevel=2)
constructor = args[0]
elif len(args) == 1 and isinstance(args[0], function.Function):
warnings.warn("add_constructor has changed API; see the API documentation",
DeprecationWarning, stacklevel=2)
func = args[0]
constructor = CppFunctionAsConstructor(func.function_name, func.parameters)
constructor.module = self.module
## </compat>
else:
try:
constructor = CppConstructor(*args, **kwargs)
except utils.SkipWrapper:
return None
self._add_constructor_obj(constructor)
return constructor
def add_copy_constructor(self):
"""
Utility method to add a 'copy constructor' method to this class.
"""
try:
constructor = CppConstructor([self.ThisClassRefParameter("const %s &" % self.full_name,
'ctor_arg')])
except utils.SkipWrapper:
return None
self._add_constructor_obj(constructor)
return constructor
def add_function_as_constructor(self, *args, **kwargs):
"""
Wrap a function that behaves as a constructor to the class. See the documentation for
L{CppFunctionAsConstructor.__init__} for information on accepted parameters.
"""
try:
constructor = CppFunctionAsConstructor(*args, **kwargs)
except utils.SkipWrapper:
return None
self._add_constructor_obj(constructor)
return constructor
def add_static_attribute(self, name, value_type, is_const=False):
"""
:param value_type: a ReturnValue object
:param name: attribute name (i.e. the name of the class member variable)
:param is_const: True if the attribute is const, i.e. cannot be modified
"""
## backward compatibility check
if isinstance(value_type, string_types) and isinstance(name, ReturnValue):
warnings.warn("add_static_attribute has changed API; see the API documentation (but trying to correct...)",
DeprecationWarning, stacklevel=2)
value_type, name = name, value_type
try:
value_type = utils.eval_retval(value_type, None)
except utils.SkipWrapper:
return
assert isinstance(value_type, ReturnValue)
getter = CppStaticAttributeGetter(value_type, self, name)
getter.stack_where_defined = traceback.extract_stack()
if is_const:
setter = None
else:
setter = CppStaticAttributeSetter(value_type, self, name)
setter.stack_where_defined = traceback.extract_stack()
self.static_attributes.add_attribute(name, getter, setter)
def add_custom_instance_attribute(self, name, value_type, getter, is_const=False, setter=None, custom_name=None,
getter_template_parameters=[],
setter_template_parameters=[]):
"""
:param value_type: a ReturnValue object
:param name: attribute name (i.e. the name of the class member variable)
:param is_const: True if the attribute is const, i.e. cannot be modified
:param getter: None, or name of a method of this class used to get the value
:param setter: None, or name of a method of this class used to set the value
:param getter_template_parameters: optional list of template parameters for getter function
:param setter_template_parameters: optional list of template parameters for setter function
"""
## backward compatibility check
if isinstance(value_type, string_types) and isinstance(name, ReturnValue):
warnings.warn("add_custom_instance_attribute has changed API; see the API documentation (but trying to correct...)",
DeprecationWarning, stacklevel=2)
value_type, name = name, value_type
try:
value_type = utils.eval_retval(value_type, None)
except utils.SkipWrapper:
return
assert isinstance(value_type, ReturnValue)
getter_wrapper = CppCustomInstanceAttributeGetter(value_type, self, name, getter=getter,
template_parameters = getter_template_parameters)
getter_wrapper.stack_where_defined = traceback.extract_stack()
if is_const:
setter_wrapper = None
assert setter is None
else:
setter_wrapper = CppCustomInstanceAttributeSetter(value_type, self, name, setter=setter,
template_parameters = setter_template_parameters)
setter_wrapper.stack_where_defined = traceback.extract_stack()
self.instance_attributes.add_attribute(name, getter_wrapper, setter_wrapper, custom_name)
def add_instance_attribute(self, name, value_type, is_const=False,
getter=None, setter=None, custom_name=None):
"""
:param value_type: a ReturnValue object
:param name: attribute name (i.e. the name of the class member variable)
:param is_const: True if the attribute is const, i.e. cannot be modified
:param getter: None, or name of a method of this class used to get the value
:param setter: None, or name of a method of this class used to set the value
"""
## backward compatibility check
if isinstance(value_type, string_types) and isinstance(name, ReturnValue):
warnings.warn("add_static_attribute has changed API; see the API documentation (but trying to correct...)",
DeprecationWarning, stacklevel=2)
value_type, name = name, value_type
try:
value_type = utils.eval_retval(value_type, None)
except utils.SkipWrapper:
return
assert isinstance(value_type, ReturnValue)
getter_wrapper = CppInstanceAttributeGetter(value_type, self, name, getter=getter)
getter_wrapper.stack_where_defined = traceback.extract_stack()
if is_const:
setter_wrapper = None
assert setter is None
else:
setter_wrapper = CppInstanceAttributeSetter(value_type, self, name, setter=setter)
setter_wrapper.stack_where_defined = traceback.extract_stack()
self.instance_attributes.add_attribute(name, getter_wrapper, setter_wrapper, custom_name)
def _inherit_helper_class_parent_virtuals(self):
"""
Given a class containing a helper class, add all virtual
methods from the all parent classes of this class.
"""
mro = self.get_mro()
next(mro) # skip 'self'
for cls in mro:
for method in cls.get_all_methods():
if not method.is_virtual:
continue
method = method.clone()
self.helper_class.add_virtual_method(method)
def _get_wrapper_registry(self):
# there is one wrapper registry object per root class only,
# which is used for all subclasses.
if self.parent is None:
if self._wrapper_registry is None:
self._wrapper_registry = settings.wrapper_registry(self.pystruct)
return self._wrapper_registry
else:
return self.parent._get_wrapper_registry()
wrapper_registry = property(_get_wrapper_registry)
def generate_forward_declarations(self, code_sink, module):
"""
Generates forward declarations for the instance and type
structures.
"""
if self.memory_policy is not None:
pointer_type = self.memory_policy.get_pointer_type(self.full_name)
else:
pointer_type = self.full_name + " *"
if self.allow_subclassing:
code_sink.writeln('''
typedef struct {
PyObject_HEAD
%sobj;
PyObject *inst_dict;
PyBindGenWrapperFlags flags:8;
} %s;
''' % (pointer_type, self.pystruct))
else:
code_sink.writeln('''
typedef struct {
PyObject_HEAD
%sobj;
PyBindGenWrapperFlags flags:8;
} %s;
''' % (pointer_type, self.pystruct))
code_sink.writeln()
if self.import_from_module:
code_sink.writeln('extern PyTypeObject *_%s;' % (self.pytypestruct,))
code_sink.writeln('#define %s (*_%s)' % (self.pytypestruct, self.pytypestruct))
else:
code_sink.writeln('extern PyTypeObject %s;' % (self.pytypestruct,))
if not self.static_attributes.empty():
code_sink.writeln('extern PyTypeObject Py%s_Type;' % (self.metaclass_name,))
code_sink.writeln()
if self.helper_class is not None:
self._inherit_helper_class_parent_virtuals()
for hook in self._get_all_helper_class_hooks():
hook(self.helper_class)
self.helper_class.generate_forward_declarations(code_sink)
if self.helper_class.cannot_be_constructed:
self.helper_class = None
self.helper_class_disabled = True
if self.have_pure_virtual_methods and self.helper_class is None:
self.cannot_be_constructed = "have pure virtual methods but no helper class"
if self.typeid_map_name is not None:
self._generate_typeid_map(code_sink, module)
if self.container_traits is not None:
self.container_traits.generate_forward_declarations(code_sink, module)
if self.parent is None:
self.wrapper_registry.generate_forward_declarations(code_sink, module, self.import_from_module)
def get_python_name(self):
if self.template_parameters:
if self.custom_name is None:
class_python_name = self.mangled_name
else:
class_python_name = self.custom_name
else:
if self.custom_name is None:
class_python_name = self.name
else:
class_python_name = self.custom_name
return class_python_name
def _generate_import_from_module(self, code_sink, module):
if module.parent is None:
error_retcode = "MOD_ERROR"
else:
error_retcode = "NULL"
# TODO: skip this step if the requested typestructure is never used
if ' named ' in self.import_from_module:
module_name, type_name = self.import_from_module.split(" named ")
else:
module_name, type_name = self.import_from_module, self.name
code_sink.writeln("PyTypeObject *_%s;" % self.pytypestruct)
module.after_init.write_code("/* Import the %r class from module %r */" % (self.full_name, self.import_from_module))
module.after_init.write_code("{"); module.after_init.indent()
module.after_init.write_code("PyObject *module = PyImport_ImportModule((char*) \"%s\");" % module_name)
module.after_init.write_code(
"if (module == NULL) {\n"
" return %s;\n"
"}" % (error_retcode,))
module.after_init.write_code("_%s = (PyTypeObject*) PyObject_GetAttrString(module, (char*) \"%s\");\n"
% (self.pytypestruct, self.get_python_name()))
module.after_init.write_code("if (PyErr_Occurred()) PyErr_Clear();")
if self.typeid_map_name is not None:
code_sink.writeln("pybindgen::TypeMap *_%s;" % self.typeid_map_name)
module.after_init.write_code("/* Import the %r class type map from module %r */" % (self.full_name, self.import_from_module))
module.after_init.write_code("PyObject *_cobj = PyObject_GetAttrString(module, (char*) \"_%s\");"
% (self.typeid_map_name))
module.after_init.write_code("if (_cobj == NULL) {\n"
" _%s = new pybindgen::TypeMap;\n"
" PyErr_Clear();\n"
"} else {\n"
" _%s = reinterpret_cast<pybindgen::TypeMap*> (PyCObject_AsVoidPtr (_cobj));\n"
" Py_DECREF(_cobj);\n"
"}"
% (self.typeid_map_name, self.typeid_map_name))
if self.parent is None:
self.wrapper_registry.generate_import(code_sink, module.after_init, "module")
module.after_init.unindent(); module.after_init.write_code("}")
if self.helper_class is not None:
self.helper_class.generate(code_sink)
def generate(self, code_sink, module):
"""Generates the class to a code sink"""
if self.import_from_module:
self._generate_import_from_module(code_sink, module)
return # .......................... RETURN
if self.typeid_map_name is not None:
code_sink.writeln("\npybindgen::TypeMap %s;\n" % self.typeid_map_name)
module.after_init.write_code("PyModule_AddObject(m, (char *) \"_%s\", PyCObject_FromVoidPtr(&%s, NULL));"
% (self.typeid_map_name, self.typeid_map_name))
if self.automatic_type_narrowing:
self._register_typeid(module)
if self.parent is None:
self.wrapper_registry.generate(code_sink, module)
if self.helper_class is not None:
parent_caller_methods = self.helper_class.generate(code_sink)
else:
parent_caller_methods = []
## generate getsets
instance_getsets = self.instance_attributes.generate(code_sink)
self.slots.setdefault("tp_getset", instance_getsets)
static_getsets = self.static_attributes.generate(code_sink)
## --- register the class type in the module ---
module.after_init.write_code("/* Register the '%s' class */" % self.full_name)
## generate a metaclass if needed
if static_getsets == '0':
metaclass = None
else:
if self.parent is None:
parent_typestruct = 'PyBaseObject_Type'
else:
parent_typestruct = self.parent.pytypestruct
metaclass = PyMetaclass(self.metaclass_name,
"Py_TYPE(&%s)" % parent_typestruct,
self.static_attributes)
metaclass.generate(code_sink, module)
if self.parent is not None:
assert isinstance(self.parent, CppClass)
module.after_init.write_code('%s.tp_base = &%s;' %
(self.pytypestruct, self.parent.pytypestruct))
if len(self.bases) > 1:
module.after_init.write_code('%s.tp_bases = PyTuple_New(%i);' % (self.pytypestruct, len(self.bases),))
for basenum, base in enumerate(self.bases):
module.after_init.write_code(' Py_INCREF((PyObject *) &%s);' % (base.pytypestruct,))
module.after_init.write_code(' PyTuple_SET_ITEM(%s.tp_bases, %i, (PyObject *) &%s);'
% (self.pytypestruct, basenum, base.pytypestruct))
if metaclass is not None:
module.after_init.write_code('Py_TYPE(&%s) = &%s;' %
(self.pytypestruct, metaclass.pytypestruct))
module.after_init.write_error_check('PyType_Ready(&%s)'
% (self.pytypestruct,))
class_python_name = self.get_python_name()
if self.outer_class is None:
module.after_init.write_code(
'PyModule_AddObject(m, (char *) \"%s\", (PyObject *) &%s);' % (
class_python_name, self.pytypestruct))
else:
module.after_init.write_code(
'PyDict_SetItemString((PyObject*) %s.tp_dict, (char *) \"%s\", (PyObject *) &%s);' % (
self.outer_class.pytypestruct, class_python_name, self.pytypestruct))
have_constructor = self._generate_constructor(code_sink)
self._generate_methods(code_sink, parent_caller_methods)
if self.allow_subclassing:
self._generate_gc_methods(code_sink)
self._generate_destructor(code_sink, have_constructor)
if self.has_output_stream_operator:
self._generate_str(code_sink)
#self._generate_tp_hash(code_sink)
#self._generate_tp_compare(code_sink)
#if self.slots.get("tp_hash", "NULL") == "NULL":
# self.slots["tp_hash"] = self._generate_tp_hash(code_sink)
if self.slots.get("tp_richcompare", "NULL") == "NULL" and self.binary_comparison_operators:
self.slots["tp_richcompare"] = self._generate_tp_richcompare(code_sink)
if self.binary_numeric_operators or self.inplace_numeric_operators:
self.slots["tp_as_number"] = self._generate_number_methods(code_sink)
if self.have_sequence_methods():
self.slots["tp_as_sequence"] = self._generate_sequence_methods(code_sink)
if self.container_traits is not None:
self.container_traits.generate(code_sink, module)
self._generate_type_structure(code_sink, self.docstring)
def _generate_number_methods(self, code_sink):
number_methods_var_name = "%s__py_number_methods" % (self.mangled_full_name,)
pynumbermethods = PyNumberMethods()
pynumbermethods.slots['variable'] = number_methods_var_name
# iterate over all types and request generation of the
# convertion functions for that type (so that those functions
# are not generated in the middle of one of the wrappers we
# are about to generate)
root_module = self.module.get_root()
for dummy_op_symbol, op_types in self.binary_numeric_operators.items():
for (retval, left, right) in op_types:
get_c_to_python_converter(retval, root_module, code_sink)
get_python_to_c_converter(left, root_module, code_sink)
get_python_to_c_converter(right, root_module, code_sink)
for dummy_op_symbol, op_types in self.inplace_numeric_operators.items():
for (retval, left, right) in op_types:
get_python_to_c_converter(left, root_module, code_sink)
get_python_to_c_converter(right, root_module, code_sink)
get_c_to_python_converter(retval, root_module, code_sink)
for dummy_op_symbol, op_types in self.unary_numeric_operators.items():
for (retval, left) in op_types:
get_c_to_python_converter(retval, root_module, code_sink)
get_python_to_c_converter(left, root_module, code_sink)
def try_wrap_operator(op_symbol, slot_name):
if op_symbol in self.binary_numeric_operators:
op_types = self.binary_numeric_operators[op_symbol]
elif op_symbol in self.inplace_numeric_operators:
op_types = self.inplace_numeric_operators[op_symbol]
else:
return
wrapper_name = "%s__%s" % (self.mangled_full_name, slot_name)
pynumbermethods.slots[slot_name] = wrapper_name
code_sink.writeln(("static PyObject*\n"
"%s (PyObject *py_left, PyObject *py_right)\n"
"{") % wrapper_name)
code_sink.indent()
for (retval, left, right) in op_types:
retval_converter, retval_name = get_c_to_python_converter(retval, root_module, code_sink)
left_converter, left_name = get_python_to_c_converter(left, root_module, code_sink)
right_converter, right_name = get_python_to_c_converter(right, root_module, code_sink)
code_sink.writeln("{")
code_sink.indent()
code_sink.writeln("%s left;" % left_name)
code_sink.writeln("%s right;" % right_name)
code_sink.writeln("if (%s(py_left, &left) && %s(py_right, &right)) {" % (left_converter, right_converter))
code_sink.indent()
code_sink.writeln("%s result = (left %s right);" % (retval_name, op_symbol))
code_sink.writeln("return %s(&result);" % retval_converter)
code_sink.unindent()
code_sink.writeln("}")
code_sink.writeln("PyErr_Clear();")
code_sink.unindent()
code_sink.writeln("}")
code_sink.writeln("Py_INCREF(Py_NotImplemented);")
code_sink.writeln("return Py_NotImplemented;")
code_sink.unindent()
code_sink.writeln("}")
def try_wrap_unary_operator(op_symbol, slot_name):
if op_symbol in self.unary_numeric_operators:
op_types = self.unary_numeric_operators[op_symbol]
else:
return
wrapper_name = "%s__%s" % (self.mangled_full_name, slot_name)
pynumbermethods.slots[slot_name] = wrapper_name
code_sink.writeln(("static PyObject*\n"
"%s (PyObject *py_self)\n"
"{") % wrapper_name)
code_sink.indent()
for (retval, left) in op_types:
retval_converter, retval_name = get_c_to_python_converter(retval, root_module, code_sink)
left_converter, left_name = get_python_to_c_converter(left, root_module, code_sink)
code_sink.writeln("{")
code_sink.indent()
code_sink.writeln("%s self;" % left_name)
code_sink.writeln("if (%s(py_self, &self)) {" % (left_converter))
code_sink.indent()
code_sink.writeln("%s result = %s(self);" % (retval_name, op_symbol))
code_sink.writeln("return %s(&result);" % retval_converter)
code_sink.unindent()
code_sink.writeln("}")
code_sink.writeln("PyErr_Clear();")
code_sink.unindent()
code_sink.writeln("}")
code_sink.writeln("Py_INCREF(Py_NotImplemented);")
code_sink.writeln("return Py_NotImplemented;")
code_sink.unindent()
code_sink.writeln("}")
try_wrap_operator('+', 'nb_add')
try_wrap_operator('-', 'nb_subtract')
try_wrap_operator('*', 'nb_multiply')
try_wrap_operator('/', 'nb_divide')
try_wrap_operator('+=', 'nb_inplace_add')
try_wrap_operator('-=', 'nb_inplace_subtract')
try_wrap_operator('*=', 'nb_inplace_multiply')
try_wrap_operator('/=', 'nb_inplace_divide')
try_wrap_unary_operator('-', 'nb_negative')
pynumbermethods.generate(code_sink)
return '&' + number_methods_var_name
def _generate_sequence_methods(self, code_sink):
sequence_methods_var_name = "%s__py_sequence_methods" % (self.mangled_full_name,)
pysequencemethods = PySequenceMethods()
pysequencemethods.slots['variable'] = sequence_methods_var_name
root_module = self.module.get_root()
self_converter = root_module.generate_python_to_c_type_converter(self.ThisClassReturn(self.full_name), code_sink)
def try_wrap_sequence_method(py_name, slot_name):
if py_name in self.methods:
numwraps = len(self.methods[py_name].wrappers)
some_wrapper_is_function = max([isinstance(x, function.Function) for x in self.methods[py_name].wrappers])
meth_wrapper_actual_name = self.methods[py_name].wrapper_actual_name
wrapper_name = "%s__%s" % (self.mangled_full_name, slot_name)
pysequencemethods.slots[slot_name] = wrapper_name
if py_name == "__len__" and (numwraps > 1 or some_wrapper_is_function):
template = pysequencemethods.FUNCTION_TEMPLATES[slot_name + "_ARGS"]
else:
template = pysequencemethods.FUNCTION_TEMPLATES[slot_name]
code_sink.writeln(template % {'wrapper_name' : wrapper_name,
'py_struct' : self._pystruct,
'method_name' : meth_wrapper_actual_name})
return
for py_name in self.valid_sequence_methods:
slot_name = self.valid_sequence_methods[py_name]
try_wrap_sequence_method(py_name, slot_name)
pysequencemethods.generate(code_sink)
return '&' + sequence_methods_var_name
def have_sequence_methods(self):
"""Determine if this object has sequence methods registered."""
for x in self.valid_sequence_methods:
if x in self.methods:
return True
return False
def _generate_type_structure(self, code_sink, docstring):
"""generate the type structure"""
self.slots.setdefault("tp_basicsize",
"sizeof(%s)" % (self.pystruct,))
tp_flags = set(['Py_TPFLAGS_DEFAULT'])
if self.allow_subclassing:
tp_flags.add("Py_TPFLAGS_HAVE_GC")
tp_flags.add("Py_TPFLAGS_BASETYPE")
self.slots.setdefault("tp_dictoffset",
"offsetof(%s, inst_dict)" % self.pystruct)
else:
self.slots.setdefault("tp_dictoffset", "0")
if self.binary_numeric_operators:
tp_flags.add("Py_TPFLAGS_CHECKTYPES")
self.slots.setdefault("tp_flags", '|'.join(sorted(tp_flags)))
if docstring is None:
docstring = self.generate_docstring()
self.slots.setdefault("tp_doc", (docstring is None and 'NULL'
or "\"%s\"" % (docstring,)))
dict_ = self.slots
dict_.setdefault("typestruct", self.pytypestruct)
if self.outer_class is None:
mod_path = self._module.get_module_path()
mod_path.append(self.mangled_name)
dict_.setdefault("tp_name", '.'.join(mod_path))
else:
dict_.setdefault("tp_name", '%s.%s' % (self.outer_class.slots['tp_name'], self.name))
## tp_call support
try:
call_method = self.methods['__call__']
except KeyError:
pass
else:
if call_method.wrapper_actual_name:
dict_.setdefault("tp_call", call_method.wrapper_actual_name)
self.pytype.generate(code_sink)
def generate_docstring(self):
name = self.get_python_name()
return "\\n".join(sorted([c.generate_docstring(name) for c in self.constructors],
key=len, reverse=True))
def _generate_constructor(self, code_sink):
"""generate the constructor, if any"""
have_constructor = True
if self.constructors and ((not self.cannot_be_constructed) or self.helper_class is not None
and not self.helper_class.cannot_be_constructed):
code_sink.writeln()
overload = CppOverloadedConstructor(None)
self.constructors_overload = overload
overload.pystruct = self.pystruct
for constructor in self.constructors:
try:
overload.add(constructor)
except CodegenErrorBase:
continue
if overload.wrappers:
try:
overload.generate(code_sink)
except utils.SkipWrapper:
constructor = None
have_constructor = False
else:
constructor = overload.wrapper_actual_name
code_sink.writeln()
else:
constructor = None
have_constructor = False
else:
## In C++, and unlike Python, constructors with
## parameters are not automatically inheritted by
## subclasses. We must generate a 'no constructor'
## tp_init to prevent this type from inheriting a
## tp_init that will allocate an instance of the
## parent class instead of this class.
code_sink.writeln()
wrapper = CppNoConstructor(self.cannot_be_constructed)
wrapper.generate(code_sink, self)
constructor = wrapper.wrapper_actual_name
have_constructor = False
code_sink.writeln()
self.slots.setdefault("tp_init", (constructor is None and "NULL"
or constructor))
return have_constructor
def _generate_copy_method(self, code_sink):
construct_name = self.get_construct_name()
copy_wrapper_name = '_wrap_%s__copy__' % self.pystruct
code_sink.writeln('''
static PyObject*\n%s(%s *self, PyObject *PYBINDGEN_UNUSED(_args))
{
''' % (copy_wrapper_name, self.pystruct))
code_sink.indent()
declarations = DeclarationsScope()
code_block = CodeBlock("return NULL;", declarations)
py_copy = declarations.declare_variable("%s*" % self.pystruct, "py_copy")
self.write_allocate_pystruct(code_block, py_copy)
code_block.write_code("%s->obj = new %s(*self->obj);" % (py_copy, construct_name))
if self.allow_subclassing:
code_block.write_code("%s->inst_dict = NULL;" % py_copy)
code_block.write_code("%s->flags = PYBINDGEN_WRAPPER_FLAG_NONE;" % py_copy)
self.wrapper_registry.write_register_new_wrapper(code_block, py_copy, "%s->obj" % py_copy)
code_block.write_code("return (PyObject*) %s;" % py_copy)
declarations.get_code_sink().flush_to(code_sink)
code_block.write_cleanup()
code_block.sink.flush_to(code_sink)
code_sink.unindent()
code_sink.writeln("}")
code_sink.writeln()
return copy_wrapper_name
def _generate_MI_parent_methods(self, code_sink):
methods = {}
mro = self.get_mro()
next(mro)
for base in mro:
for method_name, parent_overload in base.methods.items():
# skip methods registered via special type slots, not method table
if method_name in (['__call__'] + list(self.valid_sequence_methods)):
continue
try:
overload = methods[method_name]
except KeyError:
overload = CppOverloadedMethod(method_name)
overload.pystruct = self.pystruct
methods[method_name] = overload
for parent_wrapper in parent_overload.wrappers:
if parent_wrapper.visibility != 'public':
continue
# the method may have been re-defined as private in our class
private = False
for leaf_wrapper in self.nonpublic_methods:
if leaf_wrapper.matches_signature(parent_wrapper):
private = True
break
if private:
continue
# the method may have already been wrapped in our class
already_wrapped = False
try:
overload = self.methods[method_name]
except KeyError:
pass
else:
for leaf_wrapper in overload.wrappers:
if leaf_wrapper.matches_signature(parent_wrapper):
already_wrapped = True
break
if already_wrapped:
continue
wrapper = parent_wrapper.clone()
wrapper.original_class = base
wrapper.class_ = self
overload.add(wrapper)
method_defs = []
for method_name, overload in methods.items():
if not overload.wrappers:
continue
classes = []
for wrapper in overload.wrappers:
if wrapper.original_class not in classes:
classes.append(wrapper.original_class)
if len(classes) > 1:
continue # overloading with multiple base classes is just too confusing
try:
utils.call_with_error_handling(overload.generate, (code_sink,), {}, overload)
except utils.SkipWrapper:
continue
code_sink.writeln()
method_defs.append(overload.get_py_method_def(method_name))
return method_defs
def _generate_methods(self, code_sink, parent_caller_methods):
"""generate the method wrappers"""
method_defs = []
for meth_name, overload in self.methods.items():
code_sink.writeln()
#overload.generate(code_sink)
try:
utils.call_with_error_handling(overload.generate, (code_sink,), {}, overload)
except utils.SkipWrapper:
continue
# skip methods registered via special type slots, not method table
if meth_name not in (['__call__'] + list(self.valid_sequence_methods)):
method_defs.append(overload.get_py_method_def(meth_name))
code_sink.writeln()
method_defs.extend(parent_caller_methods)
if len(self.bases) > 1: # https://bugs.launchpad.net/pybindgen/+bug/563786
method_defs.extend(self._generate_MI_parent_methods(code_sink))
if self.has_copy_constructor:
try:
copy_wrapper_name = utils.call_with_error_handling(self._generate_copy_method, (code_sink,), {}, self)
except utils.SkipWrapper:
pass
else:
method_defs.append('{(char *) "__copy__", (PyCFunction) %s, METH_NOARGS, NULL},' % copy_wrapper_name)
## generate the method table
code_sink.writeln("static PyMethodDef %s_methods[] = {" % (self.pystruct,))
code_sink.indent()
for methdef in method_defs:
code_sink.writeln(methdef)
code_sink.writeln("{NULL, NULL, 0, NULL}")
code_sink.unindent()
code_sink.writeln("};")
self.slots.setdefault("tp_methods", "%s_methods" % (self.pystruct,))
def _get_delete_code(self):
if self.is_singleton:
delete_code = ''
else:
if self.memory_policy is not None:
delete_code = self.memory_policy.get_delete_code(self)
else:
if self.incomplete_type:
raise CodeGenerationError("Cannot finish generating class %s: "
"type is incomplete, but no free/unref_function defined"
% self.full_name)
if self.destructor_visibility == 'public':
delete_code = (" %s *tmp = self->obj;\n"
" self->obj = NULL;\n"
" if (!(self->flags&PYBINDGEN_WRAPPER_FLAG_OBJECT_NOT_OWNED)) {\n"
" delete tmp;\n"
" }" % (self.full_name,))
else:
delete_code = (" self->obj = NULL;\n")
return delete_code
def _generate_gc_methods(self, code_sink):
"""Generate tp_clear and tp_traverse"""
## --- tp_clear ---
tp_clear_function_name = "%s__tp_clear" % (self.pystruct,)
self.slots.setdefault("tp_clear", tp_clear_function_name )
delete_code = self._get_delete_code()
code_sink.writeln(r'''
static void
%s(%s *self)
{
Py_CLEAR(self->inst_dict);
%s
}
''' % (tp_clear_function_name, self.pystruct, delete_code))
## --- tp_traverse ---
tp_traverse_function_name = "%s__tp_traverse" % (self.pystruct,)
self.slots.setdefault("tp_traverse", tp_traverse_function_name )
if self.helper_class is None:
visit_self = ''
else:
if not isinstance(self.memory_policy, ReferenceCountingMethodsPolicy) or self.memory_policy.peekref_method is None:
peekref_code = ''
else:
peekref_code = " && self->obj->%s() == 1" % self.memory_policy.peekref_method
visit_self = '''
if (self->obj && typeid(*self->obj).name() == typeid(%s).name() %s)
Py_VISIT((PyObject *) self);
''' % (self.helper_class.name, peekref_code)
code_sink.writeln(r'''
static int
%s(%s *self, visitproc visit, void *arg)
{
Py_VISIT(self->inst_dict);
%s
return 0;
}
''' % (tp_traverse_function_name, self.pystruct, visit_self))
def _generate_str(self, code_sink):
"""Generate a tp_str function and register it in the type"""
tp_str_function_name = "_wrap_%s__tp_str" % (self.pystruct,)
self.slots.setdefault("tp_str", tp_str_function_name )
code_sink.writeln('''
static PyObject *
%s(%s *self)
{
std::ostringstream oss;
oss << *self->obj;
return PyUnicode_FromString(oss.str ().c_str ());
}
''' % (tp_str_function_name, self.pystruct))
def _generate_tp_hash(self, code_sink):
"""generates a tp_hash function, which returns a hash of the self->obj pointer"""
tp_hash_function_name = "_wrap_%s__tp_hash" % (self.pystruct,)
self.slots.setdefault("tp_hash", tp_hash_function_name )
code_sink.writeln('''
static long
%s(%s *self)
{
return (long) self->obj;
}
''' % (tp_hash_function_name, self.pystruct))
return tp_hash_function_name
def _generate_tp_compare(self, code_sink):
"""generates a tp_compare function, which compares the ->obj pointers"""
tp_compare_function_name = "_wrap_%s__tp_compare" % (self.pystruct,)
self.slots.setdefault("tp_compare", tp_compare_function_name )
code_sink.writeln('''
static int
%s(%s *self, %s *other)
{
if (self->obj == other->obj) return 0;
if (self->obj > other->obj) return -1;
return 1;
}
''' % (tp_compare_function_name, self.pystruct, self.pystruct))
def _generate_destructor(self, code_sink, have_constructor):
"""Generate a tp_dealloc function and register it in the type"""
## don't generate destructor if overridden by user
if "tp_dealloc" in self.slots:
return
tp_dealloc_function_name = "_wrap_%s__tp_dealloc" % (self.pystruct,)
code_sink.writeln(r'''
static void
%s(%s *self)
{''' % (tp_dealloc_function_name, self.pystruct))
code_sink.indent()
code_block = CodeBlock("PyErr_Print(); return;", DeclarationsScope())
if self.memory_policy is not None:
self.wrapper_registry.write_unregister_wrapper(code_block, 'self', self.memory_policy.get_pointer_to_void_name('self->obj'))
else:
self.wrapper_registry.write_unregister_wrapper(code_block, 'self', 'self->obj')
if self.allow_subclassing:
code_block.write_code("%s(self);" % self.slots["tp_clear"])
else:
code_block.write_code(self._get_delete_code())
code_block.write_code('Py_TYPE(self)->tp_free((PyObject*)self);')
code_block.write_cleanup()
code_block.declarations.get_code_sink().flush_to(code_sink)
code_block.sink.flush_to(code_sink)
code_sink.unindent()
code_sink.writeln('}\n')
self.slots.setdefault("tp_dealloc", tp_dealloc_function_name )
def _generate_tp_richcompare(self, code_sink):
tp_richcompare_function_name = "_wrap_%s__tp_richcompare" % (self.pystruct,)
code_sink.writeln("static PyObject*\n%s (%s *self, %s *other, int opid)"
% (tp_richcompare_function_name, self.pystruct, self.pystruct))
code_sink.writeln("{")
code_sink.indent()
code_sink.writeln("""
if (!PyObject_IsInstance((PyObject*) other, (PyObject*) &%s)) {
Py_INCREF(Py_NotImplemented);
return Py_NotImplemented;
}""" % self.pytypestruct)
code_sink.writeln("switch (opid)\n{")
def wrap_operator(name, opid_code):
code_sink.writeln("case %s:" % opid_code)
code_sink.indent()
if name in self.binary_comparison_operators:
code_sink.writeln("if (*self->obj %(OP)s *other->obj) {\n"
" Py_INCREF(Py_True);\n"
" return Py_True;\n"
"} else {\n"
" Py_INCREF(Py_False);\n"
" return Py_False;\n"
"}" % dict(OP=name))
else:
code_sink.writeln("Py_INCREF(Py_NotImplemented);\n"
"return Py_NotImplemented;")
code_sink.unindent()
wrap_operator('<', 'Py_LT')
wrap_operator('<=', 'Py_LE')
wrap_operator('==', 'Py_EQ')
wrap_operator('!=', 'Py_NE')
wrap_operator('>=', 'Py_GE')
wrap_operator('>', 'Py_GT')
code_sink.writeln("} /* closes switch (opid) */")
code_sink.writeln("Py_INCREF(Py_NotImplemented);\n"
"return Py_NotImplemented;")
code_sink.unindent()
code_sink.writeln("}\n")
return tp_richcompare_function_name
def generate_typedef(self, module, alias):
"""
Generates the appropriate Module code to register the class
with a new name in that module (typedef alias).
"""
module.after_init.write_code(
'PyModule_AddObject(m, (char *) \"%s\", (PyObject *) &%s);' % (
alias, self.pytypestruct))
def write_allocate_pystruct(self, code_block, lvalue, wrapper_type=None):
"""
Generates code to allocate a python wrapper structure, using
PyObject_New or PyObject_GC_New, plus some additional strcture
initialization that may be needed.
"""
if self.allow_subclassing:
new_func = 'PyObject_GC_New'
else:
new_func = 'PyObject_New'
if wrapper_type is None:
wrapper_type = '&'+self.pytypestruct
code_block.write_code("%s = %s(%s, %s);" %
(lvalue, new_func, self.pystruct, wrapper_type))
if self.allow_subclassing:
code_block.write_code(
"%s->inst_dict = NULL;" % (lvalue,))
if self.memory_policy is not None:
code_block.write_code(self.memory_policy.get_pystruct_init_code(self, lvalue))
# from pybindgen.cppclass_typehandlers import CppClassParameter, CppClassRefParameter, \
# CppClassReturnValue, CppClassRefReturnValue, CppClassPtrParameter, CppClassPtrReturnValue, CppClassParameterBase, \
# CppClassSharedPtrParameter, CppClassSharedPtrReturnValue
#from pybindgen.function import function
from pybindgen.cppmethod import CppMethod, CppConstructor, CppNoConstructor, CppFunctionAsConstructor, \
CppOverloadedMethod, CppOverloadedConstructor, \
CppVirtualMethodParentCaller, CppVirtualMethodProxy, CustomCppMethodWrapper, \
CppDummyMethod
def common_shared_object_return(value, py_name, cpp_class, code_block,
type_traits, caller_owns_return,
reference_existing_object, type_is_pointer, caller_manages_return=True,
free_after_copy=False):
if type_is_pointer:
value_value = '(*%s)' % value
value_ptr = value
else:
value_ptr = '(&%s)' % value
value_value = value
def write_create_new_wrapper():
"""Code path that creates a new wrapper for the returned object"""
## Find out what Python wrapper to use, in case
## automatic_type_narrowing is active and we are not forced to
## make a copy of the object
if (cpp_class.automatic_type_narrowing
and (caller_owns_return or isinstance(cpp_class.memory_policy,
ReferenceCountingPolicy))):
typeid_map_name = cpp_class.get_type_narrowing_root().typeid_map_name
wrapper_type = code_block.declare_variable(
'PyTypeObject*', 'wrapper_type', '0')
code_block.write_code(
'%s = %s.lookup_wrapper(typeid(%s), &%s);'
% (wrapper_type, typeid_map_name, value_value, cpp_class.pytypestruct))
else:
wrapper_type = '&'+cpp_class.pytypestruct
## Create the Python wrapper object
cpp_class.write_allocate_pystruct(code_block, py_name, wrapper_type)
if cpp_class.allow_subclassing:
code_block.write_code(
"%s->inst_dict = NULL;" % (py_name,))
## Assign the C++ value to the Python wrapper
if caller_owns_return:
if type_traits.target_is_const:
code_block.write_code("%s->obj = (%s *) (%s);" % (py_name, cpp_class.full_name, value_ptr))
else:
code_block.write_code("%s->obj = %s;" % (py_name, value_ptr))
code_block.write_code(
"%s->flags = PYBINDGEN_WRAPPER_FLAG_NONE;" % (py_name,))
else:
if not isinstance(cpp_class.memory_policy, ReferenceCountingPolicy):
if reference_existing_object:
if type_traits.target_is_const:
code_block.write_code("%s->obj = (%s *) (%s);" % (py_name, cpp_class.full_name, value_ptr))
else:
code_block.write_code("%s->obj = %s;" % (py_name, value_ptr))
code_block.write_code(
"%s->flags = PYBINDGEN_WRAPPER_FLAG_OBJECT_NOT_OWNED;" % (py_name,))
else:
if caller_manages_return:
# The PyObject creates its own copy
if not cpp_class.has_copy_constructor:
raise CodeGenerationError("Class {0} cannot be copied".format(cpp_class.full_name))
cpp_class.write_create_instance(code_block,
"%s->obj" % py_name,
value_value)
code_block.write_code(
"%s->flags = PYBINDGEN_WRAPPER_FLAG_NONE;" % (py_name,))
cpp_class.write_post_instance_creation_code(code_block,
"%s->obj" % py_name,
value_value)
else:
if type_traits.target_is_const:
code_block.write_code("%s->obj = (%s *) (%s);" % (py_name, cpp_class.full_name, value_ptr))
else:
code_block.write_code("%s->obj = %s;" % (py_name, value_ptr))
code_block.write_code(
"%s->flags = PYBINDGEN_WRAPPER_FLAG_OBJECT_NOT_OWNED;" % (py_name,))
else:
if caller_manages_return:
## The PyObject gets a new reference to the same obj
code_block.write_code(
"%s->flags = PYBINDGEN_WRAPPER_FLAG_NONE;" % (py_name,))
cpp_class.memory_policy.write_incref(code_block, value_ptr)
if type_traits.target_is_const:
code_block.write_code("%s->obj = (%s*) (%s);" %
(py_name, cpp_class.full_name, value_ptr))
else:
code_block.write_code("%s->obj = %s;" % (py_name, value_ptr))
else:
if type_traits.target_is_const:
code_block.write_code("%s->obj = (%s *) (%s);" % (py_name, cpp_class.full_name, value_ptr))
else:
code_block.write_code("%s->obj = %s;" % (py_name, value_ptr))
code_block.write_code(
"%s->flags = PYBINDGEN_WRAPPER_FLAG_OBJECT_NOT_OWNED;" % (py_name,))
## closes def write_create_new_wrapper():
if cpp_class.helper_class is None:
try:
if cpp_class.memory_policy is not None:
cpp_class.wrapper_registry.write_lookup_wrapper(
code_block, cpp_class.pystruct, py_name, cpp_class.memory_policy.get_pointer_to_void_name(value_ptr))
else:
cpp_class.wrapper_registry.write_lookup_wrapper(
code_block, cpp_class.pystruct, py_name, value_ptr)
except NotSupportedError:
write_create_new_wrapper()
if cpp_class.memory_policy is not None:
cpp_class.wrapper_registry.write_register_new_wrapper(
code_block, py_name, cpp_class.memory_policy.get_pointer_to_void_name("%s->obj" % py_name))
else:
cpp_class.wrapper_registry.write_register_new_wrapper(
code_block, py_name, "%s->obj" % py_name)
else:
code_block.write_code("if (%s == NULL) {" % py_name)
code_block.indent()
write_create_new_wrapper()
if cpp_class.memory_policy is not None:
cpp_class.wrapper_registry.write_register_new_wrapper(
code_block, py_name, cpp_class.memory_policy.get_pointer_to_void_name("%s->obj" % py_name))
else:
cpp_class.wrapper_registry.write_register_new_wrapper(
code_block, py_name, "%s->obj" % py_name)
code_block.unindent()
# If we are already referencing the existing python wrapper,
# we do not need a reference to the C++ object as well.
if caller_owns_return and \
isinstance(cpp_class.memory_policy, ReferenceCountingPolicy):
code_block.write_code("} else {")
code_block.indent()
cpp_class.memory_policy.write_decref(code_block, value_ptr)
code_block.unindent()
code_block.write_code("}")
else:
code_block.write_code("}")
else:
# since there is a helper class, check if this C++ object is an instance of that class
# http://stackoverflow.com/questions/579887/how-expensive-is-rtti/1468564#1468564
code_block.write_code("if (typeid(%s).name() == typeid(%s).name())\n{"
% (value_value, cpp_class.helper_class.name))
code_block.indent()
# yes, this is an instance of the helper class; we can get
# the existing python wrapper directly from the helper
# class...
if type_traits.target_is_const:
const_cast_value = "const_cast<%s *>(%s) " % (cpp_class.full_name, value_ptr)
else:
const_cast_value = value_ptr
code_block.write_code(
"%s = reinterpret_cast< %s* >(reinterpret_cast< %s* >(%s)->m_pyself);"
% (py_name, cpp_class.pystruct,
cpp_class.helper_class.name, const_cast_value))
code_block.write_code("%s->obj = %s;" % (py_name, const_cast_value))
# We are already referencing the existing python wrapper,
# so we do not need a reference to the C++ object as well.
if caller_owns_return and \
isinstance(cpp_class.memory_policy, ReferenceCountingPolicy):
cpp_class.memory_policy.write_decref(code_block, value_ptr)
code_block.write_code("Py_INCREF(%s);" % py_name)
code_block.unindent()
code_block.write_code("} else {") # if (typeid(*(%s)) == typeid(%s)) { ...
code_block.indent()
# no, this is not an instance of the helper class, we may
# need to create a new wrapper, or reference existing one
# if the wrapper registry tells us there is one already.
# first check in the wrapper registry...
try:
if cpp_class.memory_policy is not None:
cpp_class.wrapper_registry.write_lookup_wrapper(
code_block, cpp_class.pystruct, py_name, cpp_class.memory_policy.get_pointer_to_void_name(value_ptr))
else:
cpp_class.wrapper_registry.write_lookup_wrapper(
code_block, cpp_class.pystruct, py_name, value_ptr)
except NotSupportedError:
write_create_new_wrapper()
cpp_class.wrapper_registry.write_register_new_wrapper(
code_block, py_name, "%s->obj" % py_name)
else:
code_block.write_code("if (%s == NULL) {" % py_name)
code_block.indent()
# wrapper registry told us there is no wrapper for
# this instance => need to create new one
write_create_new_wrapper()
cpp_class.wrapper_registry.write_register_new_wrapper(
code_block, py_name, "%s->obj" % py_name)
code_block.unindent()
# handle ownership rules...
if caller_owns_return and \
isinstance(cpp_class.memory_policy, ReferenceCountingPolicy):
code_block.write_code("} else {")
code_block.indent()
# If we are already referencing the existing python wrapper,
# we do not need a reference to the C++ object as well.
cpp_class.memory_policy.write_decref(code_block, value_ptr)
code_block.unindent()
code_block.write_code("}")
else:
code_block.write_code("}")
code_block.unindent()
code_block.write_code("}") # closes: if (typeid(*(%s)) == typeid(%s)) { ... } else { ...
class CppClassParameterBase(Parameter):
"Base class for all C++ Class parameter handlers"
CTYPES = []
cpp_class = None #cppclass.CppClass('dummy') # CppClass instance
DIRECTIONS = [Parameter.DIRECTION_IN]
def __init__(self, ctype, name, direction=Parameter.DIRECTION_IN, is_const=False, default_value=None):
"""
:param ctype: C type, normally 'MyClass*'
:param name: parameter name
"""
if ctype == self.cpp_class.name:
ctype = self.cpp_class.full_name
super(CppClassParameterBase, self).__init__(
ctype, name, direction, is_const, default_value)
## name of the PyFoo * variable used in parameter parsing
self.py_name = None
## it True, this parameter is 'fake', and instead of being
## passed a parameter from python it is assumed to be the
## 'self' parameter of a method wrapper
self.take_value_from_python_self = False
class CppClassReturnValueBase(ReturnValue):
"Class return handlers -- base class"
CTYPES = []
cpp_class = None #cppclass.CppClass('dummy') # CppClass instance
def __init__(self, ctype, is_const=False):
super(CppClassReturnValueBase, self).__init__(ctype, is_const=is_const)
## name of the PyFoo * variable used in return value building
self.py_name = None
class CppClassParameter(CppClassParameterBase):
"""
Class parameter "by-value" handler
"""
CTYPES = []
cpp_class = None #cppclass.CppClass('dummy') # CppClass instance
DIRECTIONS = [Parameter.DIRECTION_IN]
def convert_python_to_c(self, wrapper):
"parses python args to get C++ value"
#assert isinstance(wrapper, ForwardWrapperBase)
#assert isinstance(self.cpp_class, cppclass.CppClass)
if self.take_value_from_python_self:
self.py_name = 'self'
wrapper.call_params.append(
'*((%s *) %s)->obj' % (self.cpp_class.pystruct, self.py_name))
else:
implicit_conversion_sources = self.cpp_class.get_all_implicit_conversions()
if not implicit_conversion_sources:
if self.default_value is not None:
self.cpp_class.get_construct_name() # raises an exception if the class cannot be constructed
self.py_name = wrapper.declarations.declare_variable(
self.cpp_class.pystruct+'*', self.name, 'NULL')
wrapper.parse_params.add_parameter(
'O!', ['&'+self.cpp_class.pytypestruct, '&'+self.py_name], self.name, optional=True)
wrapper.call_params.append(
'(%s ? (*((%s *) %s)->obj) : %s)' % (self.py_name, self.cpp_class.pystruct, self.py_name, self.default_value))
else:
self.py_name = wrapper.declarations.declare_variable(
self.cpp_class.pystruct+'*', self.name)
wrapper.parse_params.add_parameter(
'O!', ['&'+self.cpp_class.pytypestruct, '&'+self.py_name], self.name)
wrapper.call_params.append(
'*((%s *) %s)->obj' % (self.cpp_class.pystruct, self.py_name))
else:
if self.default_value is None:
self.py_name = wrapper.declarations.declare_variable(
'PyObject*', self.name)
tmp_value_variable = wrapper.declarations.declare_variable(
self.cpp_class.full_name, self.name)
wrapper.parse_params.add_parameter('O', ['&'+self.py_name], self.name)
else:
self.py_name = wrapper.declarations.declare_variable(
'PyObject*', self.name, 'NULL')
tmp_value_variable = wrapper.declarations.declare_variable(
self.cpp_class.full_name, self.name)
wrapper.parse_params.add_parameter('O', ['&'+self.py_name], self.name, optional=True)
if self.default_value is None:
wrapper.before_call.write_code("if (PyObject_IsInstance(%s, (PyObject*) &%s)) {\n"
" %s = *((%s *) %s)->obj;" %
(self.py_name, self.cpp_class.pytypestruct,
tmp_value_variable,
self.cpp_class.pystruct, self.py_name))
else:
wrapper.before_call.write_code(
"if (%s == NULL) {\n"
" %s = %s;" %
(self.py_name, tmp_value_variable, self.default_value))
wrapper.before_call.write_code(
"} else if (PyObject_IsInstance(%s, (PyObject*) &%s)) {\n"
" %s = *((%s *) %s)->obj;" %
(self.py_name, self.cpp_class.pytypestruct,
tmp_value_variable,
self.cpp_class.pystruct, self.py_name))
for conversion_source in implicit_conversion_sources:
wrapper.before_call.write_code("} else if (PyObject_IsInstance(%s, (PyObject*) &%s)) {\n"
" %s = *((%s *) %s)->obj;" %
(self.py_name, conversion_source.pytypestruct,
tmp_value_variable,
conversion_source.pystruct, self.py_name))
wrapper.before_call.write_code("} else {\n")
wrapper.before_call.indent()
possible_type_names = ", ".join([cls.name for cls in [self.cpp_class] + implicit_conversion_sources])
wrapper.before_call.write_code("PyErr_Format(PyExc_TypeError, \"parameter must an instance of one of the types (%s), not %%s\", Py_TYPE(%s)->tp_name);" % (possible_type_names, self.py_name))
wrapper.before_call.write_error_return()
wrapper.before_call.unindent()
wrapper.before_call.write_code("}")
wrapper.call_params.append(tmp_value_variable)
def convert_c_to_python(self, wrapper):
'''Write some code before calling the Python method.'''
assert isinstance(wrapper, ReverseWrapperBase)
self.py_name = wrapper.declarations.declare_variable(
self.cpp_class.pystruct+'*', 'py_'+self.cpp_class.name)
self.cpp_class.write_allocate_pystruct(wrapper.before_call, self.py_name)
if self.cpp_class.allow_subclassing:
wrapper.before_call.write_code(
"%s->inst_dict = NULL;" % (self.py_name,))
wrapper.before_call.write_code("%s->flags = PYBINDGEN_WRAPPER_FLAG_NONE;" % (self.py_name,))
self.cpp_class.write_create_instance(wrapper.before_call,
"%s->obj" % self.py_name,
self.value)
self.cpp_class.wrapper_registry.write_register_new_wrapper(wrapper.before_call, self.py_name,
"%s->obj" % self.py_name)
self.cpp_class.write_post_instance_creation_code(wrapper.before_call,
"%s->obj" % self.py_name,
self.value)
wrapper.build_params.add_parameter("N", [self.py_name])
class CppClassRefParameter(CppClassParameterBase):
"Class& handlers"
CTYPES = []
cpp_class = None #cppclass.CppClass('dummy') # CppClass instance
DIRECTIONS = [Parameter.DIRECTION_IN,
Parameter.DIRECTION_OUT,
Parameter.DIRECTION_INOUT]
def __init__(self, ctype, name, direction=Parameter.DIRECTION_IN, is_const=False,
default_value=None, default_value_type=None):
"""
:param ctype: C type, normally 'MyClass*'
:param name: parameter name
"""
if ctype == self.cpp_class.name:
ctype = self.cpp_class.full_name
super(CppClassRefParameter, self).__init__(
ctype, name, direction, is_const, default_value)
self.default_value_type = default_value_type
def convert_python_to_c(self, wrapper):
"parses python args to get C++ value"
#assert isinstance(wrapper, ForwardWrapperBase)
#assert isinstance(self.cpp_class, cppclass.CppClass)
if self.direction == Parameter.DIRECTION_IN:
if self.take_value_from_python_self:
self.py_name = 'self'
wrapper.call_params.append(
'*((%s *) %s)->obj' % (self.cpp_class.pystruct, self.py_name))
else:
implicit_conversion_sources = self.cpp_class.get_all_implicit_conversions()
if not (implicit_conversion_sources and self.type_traits.target_is_const):
if self.default_value is not None:
self.py_name = wrapper.declarations.declare_variable(
self.cpp_class.pystruct+'*', self.name, 'NULL')
wrapper.parse_params.add_parameter(
'O!', ['&'+self.cpp_class.pytypestruct, '&'+self.py_name], self.name, optional=True)
if self.default_value_type is not None:
default_value_name = wrapper.declarations.declare_variable(
self.default_value_type, "%s_default" % self.name,
self.default_value)
wrapper.call_params.append(
'(%s ? (*((%s *) %s)->obj) : %s)' % (self.py_name, self.cpp_class.pystruct,
self.py_name, default_value_name))
else:
self.cpp_class.get_construct_name() # raises an exception if the class cannot be constructed
wrapper.call_params.append(
'(%s ? (*((%s *) %s)->obj) : %s)' % (self.py_name, self.cpp_class.pystruct,
self.py_name, self.default_value))
else:
self.py_name = wrapper.declarations.declare_variable(
self.cpp_class.pystruct+'*', self.name)
wrapper.parse_params.add_parameter(
'O!', ['&'+self.cpp_class.pytypestruct, '&'+self.py_name], self.name)
wrapper.call_params.append(
'*((%s *) %s)->obj' % (self.cpp_class.pystruct, self.py_name))
else:
if self.default_value is not None:
warnings.warn("with implicit conversions, default value "
"in C++ class reference parameters is ignored.")
self.py_name = wrapper.declarations.declare_variable(
'PyObject*', self.name)
tmp_value_variable = wrapper.declarations.declare_variable(
self.cpp_class.full_name, self.name)
wrapper.parse_params.add_parameter('O', ['&'+self.py_name], self.name)
wrapper.before_call.write_code("if (PyObject_IsInstance(%s, (PyObject*) &%s)) {\n"
" %s = *((%s *) %s)->obj;" %
(self.py_name, self.cpp_class.pytypestruct,
tmp_value_variable,
self.cpp_class.pystruct, self.py_name))
for conversion_source in implicit_conversion_sources:
wrapper.before_call.write_code("} else if (PyObject_IsInstance(%s, (PyObject*) &%s)) {\n"
" %s = *((%s *) %s)->obj;" %
(self.py_name, conversion_source.pytypestruct,
tmp_value_variable,
conversion_source.pystruct, self.py_name))
wrapper.before_call.write_code("} else {\n")
wrapper.before_call.indent()
possible_type_names = ", ".join([cls.name for cls in [self.cpp_class] + implicit_conversion_sources])
wrapper.before_call.write_code("PyErr_Format(PyExc_TypeError, \"parameter must an instance of one of the types (%s), not %%s\", Py_TYPE(%s)->tp_name);" % (possible_type_names, self.py_name))
wrapper.before_call.write_error_return()
wrapper.before_call.unindent()
wrapper.before_call.write_code("}")
wrapper.call_params.append(tmp_value_variable)
elif self.direction == Parameter.DIRECTION_OUT:
assert not self.take_value_from_python_self
self.py_name = wrapper.declarations.declare_variable(
self.cpp_class.pystruct+'*', self.name)
self.cpp_class.write_allocate_pystruct(wrapper.before_call, self.py_name)
if self.cpp_class.allow_subclassing:
wrapper.after_call.write_code(
"%s->inst_dict = NULL;" % (self.py_name,))
wrapper.after_call.write_code("%s->flags = PYBINDGEN_WRAPPER_FLAG_NONE;" % (self.py_name,))
self.cpp_class.write_create_instance(wrapper.before_call,
"%s->obj" % self.py_name,
'')
self.cpp_class.wrapper_registry.write_register_new_wrapper(wrapper.before_call, self.py_name,
"%s->obj" % self.py_name)
self.cpp_class.write_post_instance_creation_code(wrapper.before_call,
"%s->obj" % self.py_name,
'')
wrapper.call_params.append('*%s->obj' % (self.py_name,))
wrapper.build_params.add_parameter("N", [self.py_name])
## well, personally I think inout here doesn't make much sense
## (it's just plain confusing), but might as well support it..
## C++ class reference inout parameters allow "inplace"
## modifications, i.e. the object is not explicitly returned
## but is instead modified by the callee.
elif self.direction == Parameter.DIRECTION_INOUT:
assert not self.take_value_from_python_self
self.py_name = wrapper.declarations.declare_variable(
self.cpp_class.pystruct+'*', self.name)
wrapper.parse_params.add_parameter(
'O!', ['&'+self.cpp_class.pytypestruct, '&'+self.py_name], self.name)
wrapper.call_params.append(
'*%s->obj' % (self.py_name))
def convert_c_to_python(self, wrapper):
'''Write some code before calling the Python method.'''
assert isinstance(wrapper, ReverseWrapperBase)
self.py_name = wrapper.declarations.declare_variable(
self.cpp_class.pystruct+'*', 'py_'+self.cpp_class.name)
self.cpp_class.write_allocate_pystruct(wrapper.before_call, self.py_name)
if self.cpp_class.allow_subclassing:
wrapper.before_call.write_code(
"%s->inst_dict = NULL;" % (self.py_name,))
wrapper.before_call.write_code("%s->flags = PYBINDGEN_WRAPPER_FLAG_NONE;" % (self.py_name,))
if self.direction == Parameter.DIRECTION_IN:
if not self.cpp_class.has_copy_constructor:
raise CodeGenerationError("Class {0} cannot be copied".format(self.cpp_class.full_name))
self.cpp_class.write_create_instance(wrapper.before_call,
"%s->obj" % self.py_name,
self.value)
self.cpp_class.wrapper_registry.write_register_new_wrapper(wrapper.before_call, self.py_name,
"%s->obj" % self.py_name)
self.cpp_class.write_post_instance_creation_code(wrapper.before_call,
"%s->obj" % self.py_name,
self.value)
wrapper.build_params.add_parameter("N", [self.py_name])
else:
## out/inout case:
## the callee receives a "temporary wrapper", which loses
## the ->obj pointer after the python call; this is so
## that the python code directly manipulates the object
## received as parameter, instead of a copy.
if self.type_traits.target_is_const:
value = "(%s*) (&(%s))" % (self.cpp_class.full_name, self.value)
else:
value = "&(%s)" % self.value
wrapper.before_call.write_code(
"%s->obj = %s;" % (self.py_name, value))
wrapper.build_params.add_parameter("O", [self.py_name])
wrapper.before_call.add_cleanup_code("Py_DECREF(%s);" % self.py_name)
if self.cpp_class.has_copy_constructor:
## if after the call we notice the callee kept a reference
## to the pyobject, we then swap pywrapper->obj for a copy
## of the original object. Else the ->obj pointer is
## simply erased (we never owned this object in the first
## place).
wrapper.after_call.write_code(
"if (Py_REFCNT(%s) == 1)\n"
" %s->obj = NULL;\n"
"else{\n" % (self.py_name, self.py_name))
wrapper.after_call.indent()
self.cpp_class.write_create_instance(wrapper.after_call,
"%s->obj" % self.py_name,
self.value)
self.cpp_class.wrapper_registry.write_register_new_wrapper(wrapper.after_call, self.py_name,
"%s->obj" % self.py_name)
self.cpp_class.write_post_instance_creation_code(wrapper.after_call,
"%s->obj" % self.py_name,
self.value)
wrapper.after_call.unindent()
wrapper.after_call.write_code('}')
else:
## it's not safe for the python wrapper to keep a
## pointer to the object anymore; just set it to NULL.
wrapper.after_call.write_code("%s->obj = NULL;" % (self.py_name,))
class CppClassReturnValue(CppClassReturnValueBase):
"Class return handlers"
CTYPES = []
cpp_class = None #cppclass.CppClass('dummy') # CppClass instance
REQUIRES_ASSIGNMENT_CONSTRUCTOR = True
def __init__(self, ctype, is_const=False):
"""override to fix the ctype parameter with namespace information"""
if ctype == self.cpp_class.name:
ctype = self.cpp_class.full_name
super(CppClassReturnValue, self).__init__(ctype, is_const=is_const)
def get_c_error_return(self): # only used in reverse wrappers
"""See ReturnValue.get_c_error_return"""
if self.type_traits.type_is_reference:
raise NotSupportedError
return "return %s();" % (self.cpp_class.full_name,)
def convert_c_to_python(self, wrapper):
"""see ReturnValue.convert_c_to_python"""
py_name = wrapper.declarations.declare_variable(
self.cpp_class.pystruct+'*', 'py_'+self.cpp_class.name)
self.py_name = py_name
self.cpp_class.write_allocate_pystruct(wrapper.after_call, self.py_name)
if self.cpp_class.allow_subclassing:
wrapper.after_call.write_code(
"%s->inst_dict = NULL;" % (py_name,))
wrapper.after_call.write_code("%s->flags = PYBINDGEN_WRAPPER_FLAG_NONE;" % (py_name,))
if not self.cpp_class.has_copy_constructor:
raise CodeGenerationError("Class {0} cannot be copied".format(self.cpp_class.full_name))
self.cpp_class.write_create_instance(wrapper.after_call,
"%s->obj" % py_name,
self.value)
self.cpp_class.wrapper_registry.write_register_new_wrapper(wrapper.after_call, py_name,
"%s->obj" % py_name)
self.cpp_class.write_post_instance_creation_code(wrapper.after_call,
"%s->obj" % py_name,
self.value)
#...
wrapper.build_params.add_parameter("N", [py_name], prepend=True)
def convert_python_to_c(self, wrapper):
"""see ReturnValue.convert_python_to_c"""
if self.type_traits.type_is_reference:
raise NotSupportedError
name = wrapper.declarations.declare_variable(
self.cpp_class.pystruct+'*', "tmp_%s" % self.cpp_class.name)
wrapper.parse_params.add_parameter(
'O!', ['&'+self.cpp_class.pytypestruct, '&'+name])
if self.REQUIRES_ASSIGNMENT_CONSTRUCTOR:
wrapper.after_call.write_code('%s %s = *%s->obj;' %
(self.cpp_class.full_name, self.value, name))
else:
wrapper.after_call.write_code('%s = *%s->obj;' % (self.value, name))
class CppClassRefReturnValue(CppClassReturnValueBase):
"Class return handlers"
CTYPES = []
cpp_class = None #cppclass.CppClass('dummy') # CppClass instance
REQUIRES_ASSIGNMENT_CONSTRUCTOR = True
def __init__(self, ctype, is_const=False, caller_owns_return=False, reference_existing_object=None,
return_internal_reference=None, caller_manages_return=True):
#override to fix the ctype parameter with namespace information
if ctype == self.cpp_class.name:
ctype = self.cpp_class.full_name
super(CppClassRefReturnValue, self).__init__(ctype, is_const=is_const)
self.reference_existing_object = reference_existing_object
self.return_internal_reference = return_internal_reference
if self.return_internal_reference:
assert self.reference_existing_object is None
self.reference_existing_object = True
self.caller_owns_return = caller_owns_return
self.caller_manages_return = caller_manages_return
def get_c_error_return(self): # only used in reverse wrappers
"""See ReturnValue.get_c_error_return"""
if (
self.type_traits.type_is_reference
and not self.type_traits.target_is_const
):
raise NotSupportedError("non-const reference return not supported")
return "{static %s __err; return __err;}" % (self.cpp_class.full_name,)
def convert_c_to_python(self, wrapper):
"""see ReturnValue.convert_c_to_python"""
py_name = wrapper.declarations.declare_variable(
self.cpp_class.pystruct+'*', 'py_'+self.cpp_class.name)
self.py_name = py_name
if self.reference_existing_object or self.caller_owns_return or not self.caller_manages_return:
common_shared_object_return(self.value, py_name, self.cpp_class, wrapper.after_call,
self.type_traits, self.caller_owns_return,
self.reference_existing_object,
type_is_pointer=False,
caller_manages_return=self.caller_manages_return)
else:
self.cpp_class.write_allocate_pystruct(wrapper.after_call, py_name)
wrapper.after_call.write_code("%s->flags = PYBINDGEN_WRAPPER_FLAG_NONE;" % (py_name,))
if not self.cpp_class.has_copy_constructor:
raise CodeGenerationError("Class {0} cannot be copied".format(self.cpp_class.full_name))
self.cpp_class.write_create_instance(wrapper.after_call,
"%s->obj" % py_name,
self.value)
self.cpp_class.wrapper_registry.write_register_new_wrapper(wrapper.after_call, py_name,
"%s->obj" % py_name)
self.cpp_class.write_post_instance_creation_code(wrapper.after_call,
"%s->obj" % py_name,
self.value)
#...
wrapper.build_params.add_parameter("N", [py_name], prepend=True)
def convert_python_to_c(self, wrapper):
"""see ReturnValue.convert_python_to_c"""
if (
self.type_traits.type_is_reference
and not self.type_traits.target_is_const
):
raise NotSupportedError("non-const reference return not supported")
name = wrapper.declarations.declare_variable(
self.cpp_class.pystruct+'*', "tmp_%s" % self.cpp_class.name)
wrapper.parse_params.add_parameter(
'O!', ['&'+self.cpp_class.pytypestruct, '&'+name])
if self.REQUIRES_ASSIGNMENT_CONSTRUCTOR:
wrapper.after_call.write_code('%s %s = *%s->obj;' %
(self.cpp_class.full_name, self.value, name))
else:
wrapper.after_call.write_code('%s = *%s->obj;' % (self.value, name))
class CppClassPtrParameter(CppClassParameterBase):
"Class* handlers"
CTYPES = []
cpp_class = None #cppclass.CppClass('dummy') # CppClass instance
DIRECTIONS = [Parameter.DIRECTION_IN,
Parameter.DIRECTION_OUT,
Parameter.DIRECTION_INOUT]
SUPPORTS_TRANSFORMATIONS = True
def __init__(self, ctype, name, direction=Parameter.DIRECTION_IN, transfer_ownership=None, custodian=None, is_const=False,
null_ok=False, default_value=None):
"""
Type handler for a pointer-to-class parameter (MyClass*)
:param ctype: C type, normally 'MyClass*'
:param name: parameter name
:param transfer_ownership: if True, the callee becomes
responsible for freeing the object. If False, the
caller remains responsible for the object. In
either case, the original object pointer is passed,
not a copy. In case transfer_ownership=True, it is
invalid to perform operations on the object after
the call (calling any method will cause a null
pointer dereference and crash the program).
:param custodian: if given, points to an object (custodian)
that keeps the python wrapper for the
parameter alive. Possible values are:
- None: no object is custodian;
- -1: the return value object;
- 0: the instance of the method in which
the ReturnValue is being used will become the
custodian;
- integer > 0: parameter number, starting at 1
(i.e. not counting the self/this parameter),
whose object will be used as custodian.
:param is_const: if true, the parameter has a const attached to the leftmost
:param null_ok: if true, None is accepted and mapped into a C NULL pointer
:param default_value: default parameter value (as C expression
string); probably, the only default value that makes sense
here is probably 'NULL'.
.. note::
Only arguments which are instances of C++ classes
wrapped by PyBindGen can be used as custodians.
"""
if ctype == self.cpp_class.name:
ctype = self.cpp_class.full_name
super(CppClassPtrParameter, self).__init__(
ctype, name, direction, is_const, default_value)
if transfer_ownership is None and self.type_traits.target_is_const:
transfer_ownership = False
self.custodian = custodian
self.transfer_ownership = transfer_ownership
self.null_ok = null_ok
if transfer_ownership is None:
raise TypeConfigurationError("Missing transfer_ownership option")
def convert_python_to_c(self, wrapper):
"parses python args to get C++ value"
#assert isinstance(wrapper, ForwardWrapperBase)
#assert isinstance(self.cpp_class, cppclass.CppClass)
if self.take_value_from_python_self:
self.py_name = 'self'
value_ptr = 'self->obj'
else:
self.py_name = wrapper.declarations.declare_variable(
self.cpp_class.pystruct+'*', self.name,
initializer=(self.default_value and 'NULL' or None))
value_ptr = wrapper.declarations.declare_variable("%s*" % self.cpp_class.full_name,
"%s_ptr" % self.name)
if self.null_ok:
num = wrapper.parse_params.add_parameter('O', ['&'+self.py_name], self.name, optional=bool(self.default_value))
wrapper.before_call.write_error_check(
"%s && ((PyObject *) %s != Py_None) && !PyObject_IsInstance((PyObject *) %s, (PyObject *) &%s)"
% (self.py_name, self.py_name, self.py_name, self.cpp_class.pytypestruct),
'PyErr_SetString(PyExc_TypeError, "Parameter %i must be of type %s");' % (num, self.cpp_class.name))
wrapper.before_call.write_code("if (%(PYNAME)s) {\n"
" if ((PyObject *) %(PYNAME)s == Py_None)\n"
" %(VALUE)s = NULL;\n"
" else\n"
" %(VALUE)s = %(PYNAME)s->obj;\n"
"} else {\n"
" %(VALUE)s = NULL;\n"
"}" % dict(PYNAME=self.py_name, VALUE=value_ptr))
else:
wrapper.parse_params.add_parameter(
'O!', ['&'+self.cpp_class.pytypestruct, '&'+self.py_name], self.name, optional=bool(self.default_value))
wrapper.before_call.write_code("%s = (%s ? %s->obj : NULL);" % (value_ptr, self.py_name, self.py_name))
value = self.transformation.transform(self, wrapper.declarations, wrapper.before_call, value_ptr)
wrapper.call_params.append(value)
if self.transfer_ownership:
if not isinstance(self.cpp_class.memory_policy, ReferenceCountingPolicy):
# if we transfer ownership, in the end we no longer own the object, so clear our pointer
wrapper.after_call.write_code('if (%s) {' % self.py_name)
wrapper.after_call.indent()
if self.cpp_class.memory_policy is not None:
self.cpp_class.wrapper_registry.write_unregister_wrapper(wrapper.after_call,
'%s' % self.py_name,
self.cpp_class.memory_policy.get_pointer_to_void_name('%s->obj' % self.py_name))
else:
self.cpp_class.wrapper_registry.write_unregister_wrapper(wrapper.after_call,
'%s' % self.py_name,
'%s->obj' % self.py_name)
wrapper.after_call.write_code('%s->obj = NULL;' % self.py_name)
wrapper.after_call.unindent()
wrapper.after_call.write_code('}')
else:
wrapper.before_call.write_code("if (%s) {" % self.py_name)
wrapper.before_call.indent()
self.cpp_class.memory_policy.write_incref(wrapper.before_call, "%s->obj" % self.py_name)
wrapper.before_call.unindent()
wrapper.before_call.write_code("}")
def convert_c_to_python(self, wrapper):
"""foo"""
## Value transformations
value = self.transformation.untransform(
self, wrapper.declarations, wrapper.after_call, self.value)
## declare wrapper variable
py_name = wrapper.declarations.declare_variable(
self.cpp_class.pystruct+'*', 'py_'+self.cpp_class.name)
self.py_name = py_name
def write_create_new_wrapper():
"""Code path that creates a new wrapper for the parameter"""
## Find out what Python wrapper to use, in case
## automatic_type_narrowing is active and we are not forced to
## make a copy of the object
if (self.cpp_class.automatic_type_narrowing
and (self.transfer_ownership or isinstance(self.cpp_class.memory_policy,
ReferenceCountingPolicy))):
typeid_map_name = self.cpp_class.get_type_narrowing_root().typeid_map_name
wrapper_type = wrapper.declarations.declare_variable(
'PyTypeObject*', 'wrapper_type', '0')
wrapper.before_call.write_code(
'%s = %s.lookup_wrapper(typeid(*%s), &%s);'
% (wrapper_type, typeid_map_name, value, self.cpp_class.pytypestruct))
else:
wrapper_type = '&'+self.cpp_class.pytypestruct
## Create the Python wrapper object
self.cpp_class.write_allocate_pystruct(wrapper.before_call, py_name, wrapper_type)
wrapper.before_call.write_code("%s->flags = PYBINDGEN_WRAPPER_FLAG_NONE;" % py_name)
self.py_name = py_name
## Assign the C++ value to the Python wrapper
if self.transfer_ownership:
wrapper.before_call.write_code("%s->obj = %s;" % (py_name, value))
else:
if not isinstance(self.cpp_class.memory_policy, ReferenceCountingPolicy):
## The PyObject gets a temporary pointer to the
## original value; the pointer is converted to a
## copy in case the callee retains a reference to
## the object after the call.
if self.direction == Parameter.DIRECTION_IN:
if not self.cpp_class.has_copy_constructor:
raise CodeGenerationError("Class {0} cannot be copied".format(self.cpp_class.full_name))
self.cpp_class.write_create_instance(wrapper.before_call,
"%s->obj" % self.py_name,
'*'+self.value)
self.cpp_class.write_post_instance_creation_code(wrapper.before_call,
"%s->obj" % self.py_name,
'*'+self.value)
else:
## out/inout case:
## the callee receives a "temporary wrapper", which loses
## the ->obj pointer after the python call; this is so
## that the python code directly manipulates the object
## received as parameter, instead of a copy.
if self.type_traits.target_is_const:
unconst_value = "(%s*) (%s)" % (self.cpp_class.full_name, value)
else:
unconst_value = value
wrapper.before_call.write_code(
"%s->obj = %s;" % (self.py_name, unconst_value))
wrapper.build_params.add_parameter("O", [self.py_name])
wrapper.before_call.add_cleanup_code("Py_DECREF(%s);" % self.py_name)
if self.cpp_class.has_copy_constructor:
## if after the call we notice the callee kept a reference
## to the pyobject, we then swap pywrapper->obj for a copy
## of the original object. Else the ->obj pointer is
## simply erased (we never owned this object in the first
## place).
wrapper.after_call.write_code(
"if (Py_REFCNT(%s) == 1)\n"
" %s->obj = NULL;\n"
"else {\n" % (self.py_name, self.py_name))
wrapper.after_call.indent()
self.cpp_class.write_create_instance(wrapper.after_call,
"%s->obj" % self.py_name,
'*'+value)
self.cpp_class.write_post_instance_creation_code(wrapper.after_call,
"%s->obj" % self.py_name,
'*'+value)
wrapper.after_call.unindent()
wrapper.after_call.write_code('}')
else:
## it's not safe for the python wrapper to keep a
## pointer to the object anymore; just set it to NULL.
wrapper.after_call.write_code("%s->obj = NULL;" % (self.py_name,))
else:
## The PyObject gets a new reference to the same obj
self.cpp_class.memory_policy.write_incref(wrapper.before_call, value)
if self.type_traits.target_is_const:
wrapper.before_call.write_code("%s->obj = (%s*) (%s);" %
(py_name, self.cpp_class.full_name, value))
else:
wrapper.before_call.write_code("%s->obj = %s;" % (py_name, value))
## closes def write_create_new_wrapper():
if self.cpp_class.helper_class is None:
try:
if self.cpp_class.memory_policy is not None:
self.cpp_class.wrapper_registry.write_lookup_wrapper(
wrapper.before_call, self.cpp_class.pystruct, py_name, self.cpp_class.memory_policy.get_pointer_to_void_name(value))
else:
self.cpp_class.wrapper_registry.write_lookup_wrapper(
wrapper.before_call, self.cpp_class.pystruct, py_name, value)
except NotSupportedError:
write_create_new_wrapper()
self.cpp_class.wrapper_registry.write_register_new_wrapper(wrapper.before_call, py_name,
"%s->obj" % py_name)
else:
wrapper.before_call.write_code("if (%s == NULL)\n{" % py_name)
wrapper.before_call.indent()
write_create_new_wrapper()
self.cpp_class.wrapper_registry.write_register_new_wrapper(wrapper.before_call, py_name,
"%s->obj" % py_name)
wrapper.before_call.unindent()
wrapper.before_call.write_code('}')
wrapper.build_params.add_parameter("N", [py_name])
else:
wrapper.before_call.write_code("if (typeid(*(%s)).name() == typeid(%s).name())\n{"
% (value, self.cpp_class.helper_class.name))
wrapper.before_call.indent()
if self.type_traits.target_is_const:
wrapper.before_call.write_code(
"%s = (%s*) (((%s*) ((%s*) %s))->m_pyself);"
% (py_name, self.cpp_class.pystruct,
self.cpp_class.helper_class.name, self.cpp_class.full_name, value))
wrapper.before_call.write_code("%s->obj = (%s*) (%s);" %
(py_name, self.cpp_class.full_name, value))
else:
wrapper.before_call.write_code(
"%s = (%s*) (((%s*) %s)->m_pyself);"
% (py_name, self.cpp_class.pystruct,
self.cpp_class.helper_class.name, value))
wrapper.before_call.write_code("%s->obj = %s;" % (py_name, value))
wrapper.before_call.write_code("Py_INCREF(%s);" % py_name)
wrapper.before_call.unindent()
wrapper.before_call.write_code("} else {")
wrapper.before_call.indent()
try:
if self.cpp_class.memory_policy is not None:
self.cpp_class.wrapper_registry.write_lookup_wrapper(
wrapper.before_call, self.cpp_class.pystruct, py_name, self.cpp_class.memory_policy.get_pointer_to_void_name(value))
else:
self.cpp_class.wrapper_registry.write_lookup_wrapper(
wrapper.before_call, self.cpp_class.pystruct, py_name, value)
except NotSupportedError:
write_create_new_wrapper()
self.cpp_class.wrapper_registry.write_register_new_wrapper(
wrapper.before_call, py_name, "%s->obj" % py_name)
else:
wrapper.before_call.write_code("if (%s == NULL)\n{" % py_name)
wrapper.before_call.indent()
write_create_new_wrapper()
self.cpp_class.wrapper_registry.write_register_new_wrapper(wrapper.before_call, py_name,
"%s->obj" % py_name)
wrapper.before_call.unindent()
wrapper.before_call.write_code('}') # closes if (%s == NULL)
wrapper.before_call.unindent()
wrapper.before_call.write_code("}") # closes if (typeid(*(%s)) == typeid(%s))\n{
wrapper.build_params.add_parameter("N", [py_name])
class CppClassPtrReturnValue(CppClassReturnValueBase):
"Class* return handler"
CTYPES = []
SUPPORTS_TRANSFORMATIONS = True
cpp_class = None #cppclass.CppClass('dummy') # CppClass instance
def __init__(self, ctype, caller_owns_return=None, custodian=None,
is_const=False, reference_existing_object=None,
return_internal_reference=None, caller_manages_return=True, free_after_copy=False):
"""
:param ctype: C type, normally 'MyClass*'
:param caller_owns_return: if true, ownership of the object pointer
is transferred to the caller
:param free_after_copy: if true, the python wrapper must call delete on
the returned pointer once it has taken a copy.
:param custodian: bind the life cycle of the python wrapper
for the return value object (ward) to that
of the object indicated by this parameter
(custodian). Possible values are:
- None: no object is custodian;
- 0: the instance of the method in which
the ReturnValue is being used will become the
custodian;
- integer > 0: parameter number, starting at 1
(i.e. not counting the self/this parameter),
whose object will be used as custodian.
:param reference_existing_object: if true, ownership of the
pointed-to object remains to be the caller's, but we
do not make a copy. The callee gets a reference to
the existing object, but is not responsible for
freeing it. Note that using this memory management
style is dangerous, as it exposes the Python
programmer to the possibility of keeping a reference
to an object that may have been deallocated in the
mean time. Calling methods on such an object would
lead to a memory error.
:param return_internal_reference: like
reference_existing_object, but additionally adds
custodian/ward to bind the lifetime of the 'self' object
(instance the method is bound to) to the lifetime of the
return value.
.. note::
Only arguments which are instances of C++ classes
wrapped by PyBindGen can be used as custodians.
"""
if ctype == self.cpp_class.name:
ctype = self.cpp_class.full_name
super(CppClassPtrReturnValue, self).__init__(ctype, is_const=is_const)
if caller_owns_return is None:
# For "const Foo*", we assume caller_owns_return=False by default
if self.type_traits.target_is_const:
caller_owns_return = False
self.caller_owns_return = caller_owns_return
self.caller_manages_return = caller_manages_return
self.free_after_copy = free_after_copy
self.reference_existing_object = reference_existing_object
self.return_internal_reference = return_internal_reference
if self.return_internal_reference:
assert self.reference_existing_object is None
self.reference_existing_object = True
self.custodian = custodian
if self.caller_owns_return and self.free_after_copy:
raise TypeConfigurationError("only one of caller_owns_return or free_after_copy can be given")
if self.caller_owns_return is None\
and self.free_after_copy is None \
and self.reference_existing_object is None:
raise TypeConfigurationError("Either caller_owns_return or self.reference_existing_object must be given")
def get_c_error_return(self): # only used in reverse wrappers
"""See ReturnValue.get_c_error_return"""
return "return NULL;"
def convert_c_to_python(self, wrapper):
"""See ReturnValue.convert_c_to_python"""
## Value transformations
value = self.transformation.untransform(
self, wrapper.declarations, wrapper.after_call, self.value)
# if value is NULL, return None
wrapper.after_call.write_code("if (!(%s)) {\n"
" Py_INCREF(Py_None);\n"
" return Py_None;\n"
"}" % value)
## declare wrapper variable
py_name = wrapper.declarations.declare_variable(
self.cpp_class.pystruct+'*', 'py_'+self.cpp_class.name)
self.py_name = py_name
common_shared_object_return(value, py_name, self.cpp_class, wrapper.after_call,
self.type_traits, self.caller_owns_return,
self.reference_existing_object,
type_is_pointer=True,
caller_manages_return=self.caller_manages_return,
free_after_copy=self.free_after_copy)
# return the value
wrapper.build_params.add_parameter("N", [py_name], prepend=True)
if self.free_after_copy:
wrapper.after_call.add_cleanup_code("delete retval;")
wrapper.after_call.add_cleanup_code("// free_after_copy for %s* %ss" % (self.cpp_class.name,wrapper.function_name))
def convert_python_to_c(self, wrapper):
"""See ReturnValue.convert_python_to_c"""
name = wrapper.declarations.declare_variable(
self.cpp_class.pystruct+'*', "tmp_%s" % self.cpp_class.name)
wrapper.parse_params.add_parameter(
'O!', ['&'+self.cpp_class.pytypestruct, '&'+name])
value = self.transformation.transform(
self, wrapper.declarations, wrapper.after_call, "%s->obj" % name)
## now the hairy part :)
if self.caller_owns_return:
if not isinstance(self.cpp_class.memory_policy, ReferenceCountingPolicy):
## the caller receives a copy, if possible
try:
if not self.cpp_class.has_copy_constructor:
raise CodeGenerationError("Class {0} cannot be copied".format(self.cpp_class.full_name))
self.cpp_class.write_create_instance(wrapper.after_call,
"%s" % self.value,
'*'+value)
except CodeGenerationError:
copy_possible = False
else:
copy_possible = True
if copy_possible:
self.cpp_class.write_post_instance_creation_code(wrapper.after_call,
"%s" % self.value,
'*'+value)
else:
# value = pyobj->obj; pyobj->obj = NULL;
wrapper.after_call.write_code(
"%s = %s;" % (self.value, value))
wrapper.after_call.write_code(
"%s = NULL;" % (value,))
else:
## the caller gets a new reference to the same obj
self.cpp_class.memory_policy.write_incref(wrapper.after_call, value)
if self.type_traits.target_is_const:
wrapper.after_call.write_code(
"%s = const_cast< %s* >(%s);" %
(self.value, self.cpp_class.full_name, value))
else:
wrapper.after_call.write_code(
"%s = %s;" % (self.value, value))
else:
## caller gets a shared pointer
## but this is dangerous, avoid at all cost!!!
wrapper.after_call.write_code(
"// dangerous!\n%s = %s;" % (self.value, value))
warnings.warn("Returning shared pointers is dangerous!"
" The C++ API should be redesigned "
"to avoid this situation.")
##
## Core of the custodians-and-wards implementation
##
def scan_custodians_and_wards(wrapper):
"""
Scans the return value and parameters for custodian/ward options,
converts them to add_custodian_and_ward API calls. Wrappers that
implement custodian_and_ward are: CppMethod, Function, and
CppConstructor.
"""
assert hasattr(wrapper, "add_custodian_and_ward")
for num, param in enumerate(wrapper.parameters):
custodian = getattr(param, 'custodian', None)
if custodian is not None:
wrapper.add_custodian_and_ward(custodian, num+1)
custodian = getattr(wrapper.return_value, 'custodian', None)
if custodian is not None:
wrapper.add_custodian_and_ward(custodian, -1)
if getattr(wrapper.return_value, "return_internal_reference", False):
wrapper.add_custodian_and_ward(-1, 0)
def _add_ward(code_block, custodian, ward):
wards = code_block.declare_variable(
'PyObject*', 'wards')
code_block.write_code(
"%(wards)s = PyObject_GetAttrString(%(custodian)s, (char *) \"__wards__\");"
% vars())
code_block.write_code(
"if (%(wards)s == NULL) {\n"
" PyErr_Clear();\n"
" %(wards)s = PyList_New(0);\n"
" PyObject_SetAttrString(%(custodian)s, (char *) \"__wards__\", %(wards)s);\n"
"}" % vars())
code_block.write_code(
"if (%(ward)s && !PySequence_Contains(%(wards)s, %(ward)s))\n"
" PyList_Append(%(wards)s, %(ward)s);" % dict(wards=wards, ward=ward))
code_block.add_cleanup_code("Py_DECREF(%s);" % wards)
def _get_custodian_or_ward(wrapper, num):
if num == -1:
assert wrapper.return_value.py_name is not None
return "((PyObject *) %s)" % wrapper.return_value.py_name
elif num == 0:
return "((PyObject *) self)"
else:
assert wrapper.parameters[num-1].py_name is not None
return "((PyObject *) %s)" % wrapper.parameters[num-1].py_name
def implement_parameter_custodians_precall(wrapper):
for custodian, ward, postcall in wrapper.custodians_and_wards:
if not postcall:
_add_ward(wrapper.before_call,
_get_custodian_or_ward(wrapper, custodian),
_get_custodian_or_ward(wrapper, ward))
def implement_parameter_custodians_postcall(wrapper):
for custodian, ward, postcall in wrapper.custodians_and_wards:
if postcall:
_add_ward(wrapper.after_call,
_get_custodian_or_ward(wrapper, custodian),
_get_custodian_or_ward(wrapper, ward))