diff --git a/doc/tutorial.rst b/doc/tutorial.rst index 58aee96..84db010 100644 --- a/doc/tutorial.rst +++ b/doc/tutorial.rst @@ -640,6 +640,17 @@ case too:: Which instructs PyBindGen that DoSomething is not to be trusted and that it should acquire ownership of the returned pointer if it needs to keep track of it. +A Copied Object or String +------------------------- + +Some C/C++ functions, and in particular those generated as bindings to other +languages allocate storage in the C/C++ heap purely for transferring data +to the python C bindings (consider python calling golang via C wrappers for +example). In these cases, there is no language level mechanism to track and free +this storage. The 'free_on_copy' parameter can be used to request that the +python generated bindings free this storage after Py_BuildValue has been +used to take a copy of it. This is most often needed for C strings (char *) +but is also available for C++ classes using 'delete' rather than 'free'. A STL container --------------- diff --git a/pybindgen/cppclass.py b/pybindgen/cppclass.py index c298429..230b629 100644 --- a/pybindgen/cppclass.py +++ b/pybindgen/cppclass.py @@ -3676,11 +3676,14 @@ class CppClassPtrReturnValue(CppClassReturnValueBase): 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): + 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 free on + the returned pointer once it has taken a (shallow) + copy of it. :param custodian: bind the life cycle of the python wrapper for the return value object (ward) to that @@ -3727,6 +3730,7 @@ class CppClassPtrReturnValue(CppClassReturnValueBase): 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: @@ -3769,7 +3773,8 @@ class CppClassPtrReturnValue(CppClassReturnValueBase): # 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;") def convert_python_to_c(self, wrapper): """See ReturnValue.convert_python_to_c""" diff --git a/pybindgen/typehandlers/base.py b/pybindgen/typehandlers/base.py index d07dc4d..678d998 100644 --- a/pybindgen/typehandlers/base.py +++ b/pybindgen/typehandlers/base.py @@ -1155,6 +1155,7 @@ class _ReturnValue(TypeHandler): args[0] = type_traits args = tuple(args) try: + print(type_handler_class) return type_handler_class(*args, **kwargs) except TypeError: ex = sys.exc_info()[1] @@ -1212,9 +1213,10 @@ ReturnValue.CTYPES = NotImplemented class PointerReturnValue(ReturnValue): """Base class for all pointer-to-something handlers""" CTYPES = [] - def __init__(self, ctype, is_const=False, caller_owns_return=None): + def __init__(self, ctype, is_const=False, caller_owns_return=None, free_after_copy=False): super(PointerReturnValue, self).__init__(ctype, is_const) self.call_owns_return = caller_owns_return + self.free_after_copy = free_after_copy PointerReturnValue.CTYPES = NotImplemented diff --git a/pybindgen/typehandlers/stringtype.py b/pybindgen/typehandlers/stringtype.py index 4c96a3e..04a4e17 100644 --- a/pybindgen/typehandlers/stringtype.py +++ b/pybindgen/typehandlers/stringtype.py @@ -218,6 +218,8 @@ class CStringReturn(PointerReturnValue): def convert_c_to_python(self, wrapper): wrapper.build_params.add_parameter("s", [self.value]) + if self.free_after_copy: + wrapper.after_call.add_cleanup_code("free(retval);") class StdStringReturn(ReturnValue): diff --git a/waf-tools/python_patched.py b/waf-tools/python_patched.py index ab0e97a..7072a83 100644 --- a/waf-tools/python_patched.py +++ b/waf-tools/python_patched.py @@ -22,6 +22,7 @@ import os, sys from waflib import Utils, Options, Errors, Logs from waflib.TaskGen import extension, before_method, after_method, feature from waflib.Configure import conf +import waflib FRAG = ''' #include @@ -318,7 +319,7 @@ def check_python_headers(conf): includes = [] if conf.env.PYTHON_CONFIG: - for incstr in conf.cmd_and_log([ conf.env.PYTHON_CONFIG, '--includes']).strip().split(): + for incstr in conf.cmd_and_log([ conf.env.PYTHON_CONFIG[0], '--includes']).strip().split(): # strip the -I or /I if (incstr.startswith('-I') or incstr.startswith('/I')): incstr = incstr[2:] @@ -334,6 +335,7 @@ def check_python_headers(conf): env['INCLUDES_PYEXT'] = [dct['INCLUDEPY']] env['INCLUDES_PYEMBED'] = [dct['INCLUDEPY']] + # Code using the Python API needs to be compiled with -fno-strict-aliasing if env['CC_NAME'] == 'gcc': env.append_value('CFLAGS_PYEMBED', ['-fno-strict-aliasing']) diff --git a/wscript b/wscript index 88f3f85..28ad90e 100644 --- a/wscript +++ b/wscript @@ -13,6 +13,8 @@ from waflib import Configure from waflib import Logs +from waflib.Tools import c_config + import os import subprocess import shutil @@ -58,7 +60,7 @@ def zipper(dir, zip_file, archive_main_folder=None): def options(opt): opt.load('python_patched', tooldir="waf-tools") - opt.load('compiler_cc') + opt.load('compiler_c') opt.load('compiler_cxx') opt.load('cflags', tooldir="waf-tools") opt.recurse('examples') @@ -120,12 +122,19 @@ def _check_compilation_flag(conf, flag, mode='cxx', linkflags=None): env.append_value("LINKFLAGS", linkflags) try: - retval = conf.run_c_code(code='#include \nint main() { return 0; }\n', - env=env, compile_filename=fname, + print("HERE") + retval = conf.run_build(code='#include \nint main() { return 0; }\n', + env=env, compile_filename=fname, + build_fun=c_config.build_fun, features=[mode, mode+'program'], execute=False) + print("WRF... ",retval) except conf.errors.ConfigurationError: ok = False + except: + print(":FAILED") + raise else: + print(":WORKED") ok = (retval == 0) conf.end_msg(ok) return ok @@ -151,7 +160,7 @@ def configure(conf): conf.check_python_version((2,3)) try: - conf.load('compiler_cc') + conf.load('compiler_c') conf.load('compiler_cxx') except conf.errors.ConfigurationError: Logs.warn("C/C++ compiler not detected. Unit tests and examples will not be compiled.")