summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArmin Rigo <arigo@tunes.org>2018-02-16 09:27:27 +0100
committerArmin Rigo <arigo@tunes.org>2018-02-16 09:27:27 +0100
commit10b51aec2ac55e31a254cec0959bd2ca2659b61a (patch)
tree5f5820dfc9183d1e3f31912319432ae922e2f77e
parentbb2645dbe2c021db1cbfb72fb32e79c0350aa42b (diff)
downloadcffi-10b51aec2ac55e31a254cec0959bd2ca2659b61a.tar.gz
Implement ffi.dlclose() for the in-line case
-rw-r--r--c/_cffi_backend.c34
-rw-r--r--cffi/api.py10
-rw-r--r--testing/cffi0/test_function.py20
3 files changed, 63 insertions, 1 deletions
diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c
index 19070b1..b812600 100644
--- a/c/_cffi_backend.c
+++ b/c/_cffi_backend.c
@@ -3982,7 +3982,8 @@ typedef struct {
static void dl_dealloc(DynLibObject *dlobj)
{
- dlclose(dlobj->dl_handle);
+ if (dlobj->dl_handle != NULL)
+ dlclose(dlobj->dl_handle);
free(dlobj->dl_name);
PyObject_Del(dlobj);
}
@@ -3992,6 +3993,17 @@ static PyObject *dl_repr(DynLibObject *dlobj)
return PyText_FromFormat("<clibrary %s>", dlobj->dl_name);
}
+static int dl_check_closed(DynLibObject *dlobj)
+{
+ if (dlobj->dl_handle == NULL)
+ {
+ PyErr_Format(PyExc_ValueError, "library '%s' has already been closed",
+ dlobj->dl_name);
+ return -1;
+ }
+ return 0;
+}
+
static PyObject *dl_load_function(DynLibObject *dlobj, PyObject *args)
{
CTypeDescrObject *ct;
@@ -4002,6 +4014,9 @@ static PyObject *dl_load_function(DynLibObject *dlobj, PyObject *args)
&CTypeDescr_Type, &ct, &funcname))
return NULL;
+ if (dl_check_closed(dlobj) < 0)
+ return NULL;
+
if (!(ct->ct_flags & (CT_FUNCTIONPTR | CT_POINTER | CT_ARRAY))) {
PyErr_Format(PyExc_TypeError,
"function or pointer or array cdata expected, got '%s'",
@@ -4034,6 +4049,9 @@ static PyObject *dl_read_variable(DynLibObject *dlobj, PyObject *args)
&CTypeDescr_Type, &ct, &varname))
return NULL;
+ if (dl_check_closed(dlobj) < 0)
+ return NULL;
+
dlerror(); /* clear error condition */
data = dlsym(dlobj->dl_handle, varname);
if (data == NULL) {
@@ -4059,6 +4077,9 @@ static PyObject *dl_write_variable(DynLibObject *dlobj, PyObject *args)
&CTypeDescr_Type, &ct, &varname, &value))
return NULL;
+ if (dl_check_closed(dlobj) < 0)
+ return NULL;
+
dlerror(); /* clear error condition */
data = dlsym(dlobj->dl_handle, varname);
if (data == NULL) {
@@ -4074,10 +4095,21 @@ static PyObject *dl_write_variable(DynLibObject *dlobj, PyObject *args)
return Py_None;
}
+static PyObject *dl_close_lib(DynLibObject *dlobj, PyObject *no_args)
+{
+ if (dl_check_closed(dlobj) < 0)
+ return NULL;
+ dlclose(dlobj->dl_handle);
+ dlobj->dl_handle = NULL;
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
static PyMethodDef dl_methods[] = {
{"load_function", (PyCFunction)dl_load_function, METH_VARARGS},
{"read_variable", (PyCFunction)dl_read_variable, METH_VARARGS},
{"write_variable", (PyCFunction)dl_write_variable, METH_VARARGS},
+ {"close_lib", (PyCFunction)dl_close_lib, METH_NOARGS},
{NULL, NULL} /* sentinel */
};
diff --git a/cffi/api.py b/cffi/api.py
index 446e554..7b63ca7 100644
--- a/cffi/api.py
+++ b/cffi/api.py
@@ -143,6 +143,13 @@ class FFI(object):
self._libraries.append(lib)
return lib
+ def dlclose(self, lib):
+ """Close a library obtained with ffi.dlopen(). After this call,
+ access to functions or variables from the library will fail
+ (possibly with a segmentation fault).
+ """
+ type(lib).__cffi_close__(lib)
+
def _typeof_locked(self, cdecl):
# call me with the lock!
key = cdecl
@@ -898,6 +905,9 @@ def _make_ffi_library(ffi, libname, flags):
return addressof_var(name)
raise AttributeError("cffi library has no function or "
"global variable named '%s'" % (name,))
+ def __cffi_close__(self):
+ backendlib.close_lib()
+ self.__dict__.clear()
#
if libname is not None:
try:
diff --git a/testing/cffi0/test_function.py b/testing/cffi0/test_function.py
index 61be656..077f806 100644
--- a/testing/cffi0/test_function.py
+++ b/testing/cffi0/test_function.py
@@ -499,3 +499,23 @@ class TestFunction(object):
""")
m = ffi.dlopen(lib_m)
assert dir(m) == ['MYE1', 'MYE2', 'MYFOO', 'myconst', 'myfunc', 'myvar']
+
+ def test_dlclose(self):
+ if self.Backend is CTypesBackend:
+ py.test.skip("not with the ctypes backend")
+ ffi = FFI(backend=self.Backend())
+ ffi.cdef("int foobar(void); int foobaz;")
+ lib = ffi.dlopen(lib_m)
+ ffi.dlclose(lib)
+ e = py.test.raises(ValueError, ffi.dlclose, lib)
+ assert str(e.value).startswith("library '")
+ assert str(e.value).endswith("' has already been closed")
+ e = py.test.raises(ValueError, getattr, lib, 'foobar')
+ assert str(e.value).startswith("library '")
+ assert str(e.value).endswith("' has already been closed")
+ e = py.test.raises(ValueError, getattr, lib, 'foobaz')
+ assert str(e.value).startswith("library '")
+ assert str(e.value).endswith("' has already been closed")
+ e = py.test.raises(ValueError, setattr, lib, 'foobaz', 42)
+ assert str(e.value).startswith("library '")
+ assert str(e.value).endswith("' has already been closed")