Commits

Armin Rigo committed 246ee2a

FILE limited support

  • Participants
  • Parent commits 69cddb8

Comments (0)

Files changed (5)

File c/_cffi_backend.c

     return ct_int;
 }
 
-static PyObject *
-_prepare_pointer_call_argument(CTypeDescrObject *ctptr, PyObject *init)
+static int
+_prepare_pointer_call_argument(CTypeDescrObject *ctptr, PyObject *init,
+                               char **output_data)
 {
     /* 'ctptr' is here a pointer type 'ITEM *'.  Accept as argument an
        initializer for an array 'ITEM[]'.  This includes the case of
            We assume that the C code won't modify the 'char *' data. */
         if ((ctitem->ct_flags & CT_PRIMITIVE_CHAR) &&
                 (ctitem->ct_size == sizeof(char))) {
-            Py_INCREF(init);
-            return init;
+            output_data[0] = PyBytes_AS_STRING(init);
+            return 1;
         }
         else
-            return Py_None;
+            return 0;
     }
     else if (PyList_Check(init) || PyTuple_Check(init)) {
         length = PySequence_Fast_GET_SIZE(init);
         /* from a unicode, we add the null terminator */
         length = _my_PyUnicode_SizeAsWideChar(init) + 1;
     }
+    else if (PyFile_Check(init)) {
+        if (strcmp(ctptr->ct_itemdescr->ct_name, "struct _IO_FILE") != 0) {
+            PyErr_Format(PyExc_TypeError,
+                         "FILE object passed to a '%s' argument",
+                         ctptr->ct_name);
+            return -1;
+        }
+        output_data[0] = (char *)PyFile_AsFile(init);
+        return 1;
+    }
     else {
         /* refuse to receive just an integer (and interpret it
            as the array size) */
-        return Py_None;
+        return 0;
     }
 
     if (ctitem->ct_size <= 0)
-        return Py_None;
+        return 0;
     datasize = length * ctitem->ct_size;
     if ((datasize / ctitem->ct_size) != length) {
         PyErr_SetString(PyExc_OverflowError,
                         "array size would overflow a Py_ssize_t");
-        return NULL;
+        return -1;
     }
 
     result = PyBytes_FromStringAndSize(NULL, datasize);
     if (result == NULL)
-        return NULL;
+        return -1;
 
     data = PyBytes_AS_STRING(result);
     memset(data, 0, datasize);
     if (convert_array_from_object(data, ctptr, init) < 0) {
         Py_DECREF(result);
-        return NULL;
+        return -1;
     }
-    return result;
+    output_data[0] = data;
+    output_data[1] = (char *)result;
+    return 1;
 }
 
 static PyObject*
             argtype = (CTypeDescrObject *)PyTuple_GET_ITEM(fvarargs, i);
 
         if (argtype->ct_flags & CT_POINTER) {
-            PyObject *string;
+            ((char **)data)[1] = NULL;
+
             if (!CData_Check(obj)) {
-                string = _prepare_pointer_call_argument(argtype, obj);
-                if (string != Py_None) {
-                    if (string == NULL)
+                int res = _prepare_pointer_call_argument(argtype, obj,
+                                                         (char **)data);
+                if (res != 0) {
+                    if (res < 0)
                         goto error;
-                    ((char **)data)[0] = PyBytes_AS_STRING(string);
-                    ((char **)data)[1] = (char *)string;
                     assert(i < nargs_declared); /* otherwise, obj is a CData */
                     free_me_until = i + 1;
                     continue;
                 }
             }
-            ((char **)data)[1] = NULL;
         }
         if (convert_from_object(data, argtype, obj) < 0)
             goto error;
         argtype = (CTypeDescrObject *)PyTuple_GET_ITEM(signature, 2 + i);
         if (argtype->ct_flags & CT_POINTER) {
             char *data = buffer + cif_descr->exchange_offset_arg[1 + i];
-            PyObject *string_or_null = (PyObject *)(((char **)data)[1]);
-            Py_XDECREF(string_or_null);
+            PyObject *tmpobj_or_null = (PyObject *)(((char **)data)[1]);
+            Py_XDECREF(tmpobj_or_null);
         }
     }
     if (buffer)
     buffer(p)[:] = bytearray(b"foo\x00")
     assert len(p) == 4
     assert list(p) == [b"f", b"o", b"o", b"\x00"]
+
+def test_FILE():
+    if sys.platform == "win32":
+        py.test.skip("testing FILE not implemented")
+    #
+    BFILE = new_struct_type("_IO_FILE")
+    BFILEP = new_pointer_type(BFILE)
+    BChar = new_primitive_type("char")
+    BCharP = new_pointer_type(BChar)
+    BInt = new_primitive_type("int")
+    BFunc = new_function_type((BCharP, BFILEP), BInt, False)
+    BFunc2 = new_function_type((BFILEP, BCharP), BInt, True)
+    ll = find_and_load_library('c')
+    fputs = ll.load_function(BFunc, "fputs")
+    fscanf = ll.load_function(BFunc2, "fscanf")
+    #
+    import posix
+    fdr, fdw = posix.pipe()
+    fr1 = posix.fdopen(fdr, 'r')
+    fw1 = posix.fdopen(fdw, 'w')
+    #
+    res = fputs(b"hello world\n", fw1)
+    assert res >= 0
+    fw1.close()
+    #
+    p = newp(new_array_type(BCharP, 100), None)
+    res = fscanf(fr1, b"%s\n", p)
+    assert res == 1
+    assert string(p) == b"hello"
+    fr1.close()
+
+def test_FILE_only_for_FILE_arg():
+    if sys.platform == "win32":
+        py.test.skip("testing FILE not implemented")
+    #
+    B_NOT_FILE = new_struct_type("NOT_FILE")
+    B_NOT_FILEP = new_pointer_type(B_NOT_FILE)
+    BChar = new_primitive_type("char")
+    BCharP = new_pointer_type(BChar)
+    BInt = new_primitive_type("int")
+    BFunc = new_function_type((BCharP, B_NOT_FILEP), BInt, False)
+    ll = find_and_load_library('c')
+    fputs = ll.load_function(BFunc, "fputs")
+    #
+    import posix
+    fdr, fdw = posix.pipe()
+    fr1 = posix.fdopen(fdr, 'r')
+    fw1 = posix.fdopen(fdw, 'w')
+    #
+    e = py.test.raises(TypeError, fputs, b"hello world\n", fw1)
+    assert str(e.value) == ("FILE object passed to a 'struct NOT_FILE *' "
+                            "argument")
             if name.startswith('RTLD_'):
                 setattr(self, name, getattr(backend, name))
         #
-        lines = []
+        lines = ['typedef struct _IO_FILE FILE;']
         by_size = {}
         for cname in ['long long', 'long', 'int', 'short', 'char']:
             by_size[self.sizeof(cname)] = cname

File doc/source/index.rst

   some headers declare a different type (e.g. an enum) and also call it
   ``bool``.
 
+* *New in version 0.4:* FILE.  Limited support: just enough to declare C
+  functions taking a ``FILE *`` argument and calling them with a Python
+  file object.
+
 .. "versionadded:: 0.4": bool
+.. "versionadded:: 0.4": FILE
 
 As we will see on `the verification step`_ below, the declarations can
 also contain "``...``" at various places; these are placeholders that will

File testing/test_function.py

 from cffi import FFI
 import math, os, sys
 from cffi.backend_ctypes import CTypesBackend
+from testing.udir import udir
 
 try:
     from StringIO import StringIO
         m = ffi.dlopen("m")
         x = m.sin(1.23)
         assert x == math.sin(1.23)
+
+    def test_fputs_custom_FILE(self):
+        if self.Backend is CTypesBackend:
+            py.test.skip("FILE not supported with the ctypes backend")
+        filename = str(udir.join('fputs_custom_FILE'))
+        ffi = FFI(backend=self.Backend())
+        ffi.cdef("int fputs(const char *, FILE *);")
+        C = ffi.dlopen(None)
+        with open(filename, 'wb') as f:
+            f.write(b'[')
+            C.fputs(b"hello from custom file", f)
+            f.write(b'][')
+            C.fputs(b"some more output", f)
+            f.write(b']')
+        with open(filename, 'rb') as f:
+            res = f.read()
+        assert res == b'[hello from custom file][some more output]'