add support for freeing C/C++ objects used purely within wrappers

This commit is contained in:
Cosmos Nicolaou committed 2020-02-21 14:53:48 -08:00
1 parent adeb7ce557
commit 1ce9a95173
6 files changed
+39 -8

No files matched your search

+11
View File
@@ -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
---------------
+7 -2
View File
@@ -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"""
+3 -1
View File
@@ -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
+2
View File
@@ -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):
+3 -1
View File
@@ -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 <Python.h>
@@ -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'])
+13 -4
View File
@@ -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 <stdio.h>\nint main() { return 0; }\n',
env=env, compile_filename=fname,
print("HERE")
retval = conf.run_build(code='#include <stdio.h>\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.")