FFI and arrays of strings

140 views
Skip to first unread message

Matt Spaulding

unread,
Mar 27, 2021, 1:38:09 PM3/27/21
to chez-scheme
Hello,

I've really gotten stuck trying to understand how to deal with arrays of pointers with Chez's FFI.  As an example I've been trying to use the "glob" function in Linux which returns a struct that contains a char** which is an array of strings containing the glob results.  So far I am able to access only the first string in the array and none other.  Is there some way to do this?

Here's my FFI code

(define-ftype glob_t                                                                                                                              (struct
    [gl_pathc size_t]
    [gl_pathv (* (* char))]
    [gl_offs size_t]))

(define glob-c
  (foreign-procedure "glob" (string int void* (* glob_t)) int))

(define (glob pattern)
  (let ((g (make-ftype-pointer glob_t (foreign-alloc (ftype-sizeof glob_t)))))
  (glob-c pattern 0 0 g)
  g))

Now I run the following code

(define g (glob "/etc/*.conf"))
(ftype-pointer->sexpr g)

Which gives the results

(struct
  [gl_pathc 28]
  [gl_pathv (* "/etc/adduser.conf")]
  [gl_offs 0])

So only the first result is shown.  I have tried using "ftype-ref" and "ftype-&ref" to use an index to get the other entries, but the result is always an invalid pointer.

For example

(ftype-pointer->sexpr (ftype-&ref glob_t (gl_pathv) g))

Is fine and returns

(* "/etc/adduser.conf")

But if I add an index

(ftype-pointer->sexpr (ftype-&ref glob_t (gl_pathv) g 1))

Then I get this

(* invalid)


Any help to point me in the right direction would be much appreciated.

Best Regards,
Matt


  

Jamie Taylor

unread,
Mar 27, 2021, 4:25:40 PM3/27/21
to Matt Spaulding, chez-scheme
Here's why the way you tried to use the index parameter to ftype-&ref doesn't work:

The index is automatically scaled by the size of the ftype identified by ftype-name, which allows the fptr to be treated as an array of ftype-name objects and index as an index into that array

In other words, the index is used to compute the base pointer for the ftype, and then the path through the ftype is followed, whereas you want it to follow the path through the ftype and then apply an offset.  You should be able to accomplish this by defining an ftype for (* (* char)) and dereferencing in two steps (i.e., one ftype-&ref to get the gl_pathv field out as an ftype pointer and a second that applies the index to this newly defined ftype to get the right string).

However, I think what you want to do can be better accomplished by using a zero-sized array in the ftype definition.  Something like
  [gl_pathv (* (array 0 (* char)))]
I haven't tried this out to see if it will work (or even compile), so if this (somewhat vague) advice doesn't get you on the right track then let me know and I'll see about getting a real working example.

Hope this helps,
Jamie

--
You received this message because you are subscribed to the Google Groups "chez-scheme" group.
To unsubscribe from this group and stop receiving emails from it, send an email to chez-scheme...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/chez-scheme/7266fef3-5109-421c-9865-c2ec0a09bc11n%40googlegroups.com.

Matt Spaulding

unread,
Mar 27, 2021, 5:22:45 PM3/27/21
to chez-scheme
Thanks, that's a start at least.  I see what you're saying, but I can't seem to get it to work for me.

I update my types as follows

(define-ftype char* (* char))
(define-ftype char** (* char*))

(define-ftype glob_t
  (struct
  [gl_pathc size_t]
  [gl_pathv char**]
  [gl_offs size_t]))

Then after getting the glob_t pointer ran code like this

(define gp (ftype-&ref glob_t (gl_pathv) g))

Then to reference the string itself

(ftype-pointer->sexpr (ftype-&ref char** () gp 0))

This works only for an index of 0, but then for subsequent index values it returns either "null" or "invalid".  At least this is what I gathered from your explanation I should be doing.  If I'm not understanding then a code example would be really helpful.

I will say that I found a workaround to this problem where I write some C code like the following to retrieve an entry at an index in the array.

#include <stdio.h>
#include <glob.h>

extern char *get_path_idx(glob_t *obj, int idx);
char *get_path_idx(glob_t *obj, int idx)
{
  if (idx > obj->gl_pathc) return NULL;
  return obj->gl_pathv[idx];
}

And in scheme added this code

(define (glob-paths g)
  (let ((count (ftype-ref glob_t (gl_pathc) g)))
    (define (add-glob-path glob acc num total)
      (if (< num total)
        (add-path glob (cons (get-path-idx glob num) acc) (+ num 1) total)
        acc))
    (add-glob-path g '() 0 count)))

So this actually returns all the entries which at least proves that the entries are allocated correctly, just that scheme is somehow not representing it properly.  I'd still really like to understand how I'm going wrong with the pure scheme implementation.

Best Regards,
Matt

Matt Spaulding

unread,
Mar 27, 2021, 5:59:47 PM3/27/21
to chez-scheme
Well, I think I may have solved it.  I've written a new version of the glob-paths function that uses pure scheme and returns the results I wanted.

(define (glob-paths-scm g)
  (let ((count (ftype-ref glob_t (gl_pathc) g))
        (entries (ftype-ref glob_t (gl_pathv) g)))
    (define (add-glob-path glob acc num total)
      (if (< num total)
        (add-glob-path glob
                       (cons
                         (ftype-pointer->sexpr (ftype-&ref char* () entries num)) acc)
                       (+ num 1) total)
        acc))
    (add-glob-path g '() 0 count)))

Thanks for the tips, that helped a lot.

Best Regards,
Matt


Jamie Taylor

unread,
Mar 27, 2021, 6:47:51 PM3/27/21
to Matt Spaulding, chez-scheme
Glad you got it working.  In your previous iteration, the gp binding should have used just ftype-ref, not ftype-&ref (because you don't want the address of the gl_pathv field, you want the address that is the value stored in it).  My original reply probably lead you astray there.

FWIW, I also got the array-based solution to work, with the reference site looking like:
(ftype-pointer->sexpr glob_t (gl_pathv * 2) g)
where the "*" is for following the gl_pathv pointer, and 2 is the second entry in the list

Using this array version, a more compact and clearer (but less efficient) implementation of your glob-paths function could look like
(map (lambda (n)
       (ftype-pointer->sexpr glob_t (gl_pathv * n) g))
  (iota (ftype-ref glob_t (gl_pathc) g)))

Jamie

Reply all
Reply to author
Forward
0 new messages