diff --git a/examples/c-hello/hello.h b/examples/c-hello/hello.h index 661ce50..b4ba84c 100644 --- a/examples/c-hello/hello.h +++ b/examples/c-hello/hello.h @@ -11,7 +11,6 @@ void hello_print_message (const char *message); double hello_sum (double x, double y); - typedef struct _HelloFoo HelloFoo; diff --git a/examples/c-hello/hellomodulegen.py b/examples/c-hello/hellomodulegen.py index bdf48f9..a59c4b7 100755 --- a/examples/c-hello/hellomodulegen.py +++ b/examples/c-hello/hellomodulegen.py @@ -11,6 +11,37 @@ from pybindgen.gccxmlparser import ModuleParser from pybindgen.function import CustomFunctionWrapper from pybindgen.cppmethod import CustomCppMethodWrapper +constructor_rx = re.compile("hello_foo_new(_.*)?") +method_rx = re.compile("hello_foo(_.*)?") + +def pre_scan_hook(module_parser, + pygccxml_definition, + global_annotations, + parameter_annotations): + if pygccxml_definition.name == "_HelloFoo": + global_annotations['free_function'] = 'hello_foo_unref' + global_annotations['incref_function'] = 'hello_foo_ref' + global_annotations['decref_function'] = 'hello_foo_unref' + + ## constructor? + m = constructor_rx.match(pygccxml_definition.name) + if m: + global_annotations['is_constructor_of'] = 'HelloFoo' + return + + ## method? + m = method_rx.match(pygccxml_definition.name) + if m: + method_name = m.group(1)[1:] + if method_name in ['ref', 'unref']: + return + print >> sys.stderr, ">>>>>>>>", m.group(1)[1:] + global_annotations['as_method'] = m.group(1)[1:] + global_annotations['of_class'] = 'HelloFoo' + parameter_annotations['foo'] = {'transfer_ownership': 'false'} + + + #print >> sys.stderr, ">>>>>>>>>>>>", pygccxml_definition def my_module_gen(out_file): @@ -18,6 +49,7 @@ def my_module_gen(out_file): pybindgen.write_preamble(out) out.writeln("#include \"hello.h\"") module_parser = ModuleParser('hello') + module_parser.add_pre_scan_hook(pre_scan_hook) module = module_parser.parse(sys.argv[1:]) module.generate(out) diff --git a/pybindgen/cppclass.py b/pybindgen/cppclass.py index 87937ea..40f7256 100644 --- a/pybindgen/cppclass.py +++ b/pybindgen/cppclass.py @@ -39,6 +39,9 @@ def default_instance_creation_function(cpp_class, code_block, lvalue, """ 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)) @@ -311,7 +314,9 @@ class CppClass(object): automatic_type_narrowing=None, allow_subclassing=None, is_singleton=False, outer_class=None, peekref_method=None, - template_parameters=(), custom_template_class_name=None): + template_parameters=(), custom_template_class_name=None, + incomplete_type=False, free_function=None, + incref_function=None, decref_function=None,): """Constructor name -- class name parent -- optional parent class wrapper @@ -335,8 +340,12 @@ class CppClass(object): C++ class destructor to free the value. peekref_method -- if the class supports reference counting, the name of the method that returns the current reference count. + free_function -- name of C function used to deallocate class instances + incref_function -- same as incref_method, but as a function instead of method + decref_method -- same as decref_method, but as a function instead of method """ 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 @@ -374,6 +383,7 @@ class CppClass(object): self.parent = parent assert parent is None or isinstance(parent, CppClass) + assert (incref_method is None and decref_method is None) \ or (incref_method is not None and decref_method is not None) if incref_method is None and parent is not None: @@ -385,6 +395,20 @@ class CppClass(object): self.decref_method = decref_method self.peekref_method = peekref_method + self.free_function = free_function + + assert (incref_function is None and decref_function is None) \ + or (incref_function is not None and decref_function is not None) + if incref_function is None and parent is not None: + self.incref_function = parent.incref_function + self.decref_function = parent.decref_function + else: + self.incref_function = incref_function + self.decref_function = decref_function + + self.has_reference_counting = (self.incref_function is not None + or self.incref_method is not None) + if automatic_type_narrowing is None: if parent is None: self.automatic_type_narrowing = settings.automatic_type_narrowing @@ -462,6 +486,38 @@ class CppClass(object): except ValueError: pass + 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 `has_reference_counting'. + """ + if self.incref_method is not None: + assert self.incref_function is None + code_block.write_code('%s->%s();' % (obj_expr, self.incref_method)) + elif self.incref_function is not None: + assert self.incref_method is None + code_block.write_code('%s(%s);' % (self.incref_function, obj_expr)) + else: + raise AssertionError + + 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 `has_reference_counting'. + """ + if self.decref_method is not None: + assert self.decref_function is None + code_block.write_code('%s->%s();' % (obj_expr, self.decref_method)) + elif self.decref_function is not None: + assert self.decref_method is None + code_block.write_code('%s(%s);' % (self.decref_function, obj_expr)) + else: + raise AssertionError + def add_helper_class_hook(self, hook): """ Add a hook function to be called just prior to a helper class @@ -617,7 +673,6 @@ class CppClass(object): 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.""" @@ -683,6 +738,8 @@ class CppClass(object): """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): @@ -1068,22 +1125,37 @@ typedef struct { code_sink.writeln("};") self.slots.setdefault("tp_methods", "%s_methods" % (self.pystruct,)) + def _get_delete_code(self, clear_pointer): + if self.is_singleton: + delete_code = '' + else: + if self.decref_method is not None: + delete_code = ("if (self->obj)\n self->obj->%s();" + % (self.decref_method,)) + elif self.decref_function is not None: + delete_code = ("if (self->obj)\n %s(self->obj);" + % (self.decref_function,)) + elif self.free_function is not None: + delete_code = ("if (self->obj)\n %s(self->obj);" + % (self.free_function,)) + else: + if self.incomplete_type: + raise CodeGenerationError("Cannot finish generating class %s: " + "type is incomplete, but no free/unref_function defined") + delete_code = "delete self->obj;" + + if clear_pointer: + delete_code += "\n self->obj = NULL;" + + 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 ) - - if self.is_singleton: - delete_code = '' - else: - if self.decref_method is None: - delete_code = "delete self->obj;" - else: - delete_code = ("if (self->obj)\n self->obj->%s();" - % (self.decref_method,)) - + delete_code = self._get_delete_code(clear_pointer=False) code_sink.writeln(r''' static void %s(%s *self) @@ -1135,16 +1207,7 @@ static int delete_code = "" else: clear_code = "" - if self.is_singleton: - delete_code = '' - else: - if self.decref_method is None: - delete_code = "delete tmp; self->obj = NULL;" - else: - delete_code = ("if (tmp)\n" - " tmp->%s();\n" - "self->obj = NULL;" - % (self.decref_method,)) + delete_code = self._get_delete_code(clear_pointer=True) if have_constructor: code_sink.writeln(r''' @@ -1505,11 +1568,10 @@ class CppClassPtrParameter(CppClassParameterBase): wrapper.call_params.append(value) if self.transfer_ownership: - if self.cpp_class.incref_method is None: + if not self.cpp_class.has_reference_counting: wrapper.after_call.write_code('%s->obj = NULL;' % (self.py_name,)) else: - wrapper.before_call.write_code('%s->obj->%s();' % ( - self.py_name, self.cpp_class.incref_method,)) + self.cpp_class.write_incref(wrapper.before_call, "%s->obj" % self.py_name) def convert_c_to_python(self, wrapper): @@ -1531,7 +1593,7 @@ class CppClassPtrParameter(CppClassParameterBase): ## 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 self.cpp_class.incref_method is not None)): + and (self.transfer_ownership or self.cpp_class.has_reference_counting)): typeid_map_name = self.cpp_class.get_type_narrowing_root().typeid_map_name wrapper_type = wrapper.declarations.declare_variable( @@ -1561,7 +1623,7 @@ class CppClassPtrParameter(CppClassParameterBase): wrapper.before_call.write_code("%s->obj = %s;" % (py_name, value)) wrapper.build_params.add_parameter("N", [py_name]) else: - if self.cpp_class.incref_method is None: + if not self.cpp_class.has_reference_counting: ## 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 @@ -1604,8 +1666,7 @@ class CppClassPtrParameter(CppClassParameterBase): wrapper.after_call.write_code('}') else: ## The PyObject gets a new reference to the same obj - wrapper.before_call.write_code( - "%s->%s();" % (value, self.cpp_class.incref_method)) + self.cpp_class.write_incref(wrapper.before_call, value) if self.is_const: wrapper.before_call.write_code("%s->obj = const_cast< %s*>(%s);" % (py_name, self.cpp_class.full_name, value)) @@ -1726,7 +1787,7 @@ class CppClassPtrReturnValue(CppClassReturnValueBase): ## 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.caller_owns_return or self.cpp_class.incref_method is not None)): + and (self.caller_owns_return or self.cpp_class.has_reference_counting)): typeid_map_name = self.cpp_class.get_type_narrowing_root().typeid_map_name wrapper_type = wrapper.declarations.declare_variable( @@ -1757,15 +1818,14 @@ class CppClassPtrReturnValue(CppClassReturnValueBase): if self.caller_owns_return: wrapper.after_call.write_code("%s->obj = %s;" % (py_name, value)) else: - if self.cpp_class.incref_method is None: + if not self.cpp_class.has_reference_counting: ## The PyObject creates its own copy self.cpp_class.write_create_instance(wrapper.after_call, "%s->obj" % py_name, '*'+value) else: ## The PyObject gets a new reference to the same obj - wrapper.after_call.write_code( - "%s->%s();" % (value, self.cpp_class.incref_method)) + self.cpp_class.write_incref(wrapper.after_call, value) if self.is_const: wrapper.after_call.write_code("%s->obj = const_cast< %s* >(%s);" % (py_name, self.cpp_class.full_name, value)) @@ -1821,15 +1881,14 @@ class CppClassPtrReturnValue(CppClassReturnValueBase): ## now the hairy part :) if self.caller_owns_return: - if self.cpp_class.incref_method is None: + if not self.cpp_class.has_reference_counting: ## the caller receives a copy self.cpp_class.write_create_instance(wrapper.after_call, "%s" % self.value, '*'+value) else: ## the caller gets a new reference to the same obj - wrapper.after_call.write_code( - "%s->%s();" % (value, self.cpp_class.incref_method)) + self.cpp_class.write_incref(wrapper.after_call, value) if self.is_const: wrapper.after_call.write_code( "%s = const_cass< %s* >(%s);" % diff --git a/pybindgen/gccxmlparser.py b/pybindgen/gccxmlparser.py index fcda6cb..d2de9ad 100644 --- a/pybindgen/gccxmlparser.py +++ b/pybindgen/gccxmlparser.py @@ -17,6 +17,7 @@ from pygccxml.declarations import type_traits from pygccxml.declarations import cpptypes from pygccxml.declarations import calldef from pygccxml.declarations import templates +from pygccxml.declarations.class_declaration import class_declaration_t, class_t import settings #from pygccxml.declarations.calldef import \ @@ -479,6 +480,8 @@ class ModuleParser(object): def scan_methods(self): assert self._types_scanned for class_wrapper in type_registry.ordered_classes: + if isinstance(class_wrapper.gccxml_definition, class_declaration_t): + continue # skip classes not fully defined self._scan_class_methods(class_wrapper.gccxml_definition, class_wrapper) def scan_functions(self): @@ -489,6 +492,38 @@ class ModuleParser(object): annotations_scanner.warn_unused_annotations() return self.module + def _apply_class_annotations(self, cls, annotations, kwargs): + for name, value in annotations.iteritems(): + if name == 'allow_subclassing': + kwargs.setdefault('allow_subclassing', annotations_scanner.parse_boolean(value)) + elif name == 'is_singleton': + kwargs.setdefault('is_singleton', annotations_scanner.parse_boolean(value)) + elif name == 'incref_method': + kwargs.setdefault('incref_method', value) + elif name == 'decref_method': + kwargs.setdefault('decref_method', value) + elif name == 'peekref_method': + kwargs.setdefault('peekref_method', value) + elif name == 'automatic_type_narrowing': + kwargs.setdefault('automatic_type_narrowing', annotations_scanner.parse_boolean(value)) + elif name == 'free_function': + kwargs.setdefault('free_function', value) + elif name == 'incref_function': + kwargs.setdefault('incref_function', value) + elif name == 'decref_function': + kwargs.setdefault('decref_function', value) + else: + warnings.warn_explicit("Class annotation %r ignored" % name, + Warning, cls.location.file_name, cls.location.line) + + if isinstance(cls, class_t): + if self._class_has_virtual_methods(cls): + kwargs.setdefault('allow_subclassing', True) + + if not self._class_has_public_destructor(cls): + kwargs.setdefault('is_singleton', True) + + def _scan_namespace_types(self, module, module_namespace, outer_class=None): ## scan enumerations if outer_class is None: @@ -513,6 +548,37 @@ class ModuleParser(object): module.add_enum(Enum(enum.name, [name for name, dummy_val in enum.values], outer_class=outer_class)) + registered_classes = {} # class_t -> CppClass + + ## Look for forward declarations of class/structs like + ## "typedef struct _Foo Foo"; these are represented in + ## pygccxml by a typedef whose .type.declaration is a + ## class_declaration_t instead of class_t. + for alias in module_namespace.typedefs(function=self.location_filter, + recursive=False, allow_empty=True): + cls = alias.type.declaration + if not isinstance(cls, class_declaration_t): + continue # fully defined classes handled further below + + global_annotations, param_annotations = \ + annotations_scanner.get_annotations(cls.location.file_name, + cls.location.line) + for hook in self._pre_scan_hooks: + hook(self, cls, global_annotations, param_annotations) + if 'ignore' in global_annotations: + continue + + kwargs = dict() + self._apply_class_annotations(cls, global_annotations, kwargs) + kwargs.setdefault("incomplete_type", True) + kwargs.setdefault("automatic_type_narrowing", False) + kwargs.setdefault("allow_subclassing", False) + class_wrapper = CppClass(alias.name, **kwargs) + class_wrapper.gccxml_definition = cls + module.add_class(class_wrapper) + registered_classes[cls] = class_wrapper + type_registry.register_class(class_wrapper, alias=cls.name) + ## scan classes if outer_class is None: unregistered_classes = [cls for cls in @@ -529,7 +595,6 @@ class ModuleParser(object): continue unregistered_classes.append(cls) - registered_classes = {} # class_t -> CppClass while unregistered_classes: cls = unregistered_classes.pop(0) typedef = None @@ -590,30 +655,7 @@ class ModuleParser(object): unregistered_classes.append(cls) continue - ##-- - - for name, value in global_annotations.iteritems(): - if name == 'allow_subclassing': - kwargs.setdefault('allow_subclassing', annotations_scanner.parse_boolean(value)) - elif name == 'is_singleton': - kwargs.setdefault('is_singleton', annotations_scanner.parse_boolean(value)) - elif name == 'incref_method': - kwargs.setdefault('incref_method', value) - elif name == 'decref_method': - kwargs.setdefault('decref_method', value) - elif name == 'peekref_method': - kwargs.setdefault('peekref_method', value) - elif name == 'automatic_type_narrowing': - kwargs.setdefault('automatic_type_narrowing', annotations_scanner.parse_boolean(value)) - else: - warnings.warn_explicit("Class annotation %r ignored" % name, - Warning, cls.location.file_name, cls.location.line) - - if self._class_has_virtual_methods(cls): - kwargs.setdefault('allow_subclassing', True) - - if not self._class_has_public_destructor(cls): - kwargs.setdefault('is_singleton', True) + self._apply_class_annotations(cls, global_annotations, kwargs) custom_template_class_name = None template_parameters = ()