4
votes

This is the first time I try to use Racket's FFI. I would like to create an application that binds to libgit2 in order to manipulate GIT repositories.

The first thing I would need to do is to initialize a repository, as shown in the libgit2 documentation:

git_repository *repo = NULL;
int error = git_repository_init(&repo, "/tmp/…", false);

Getting the function call in Racket is simple enough:

(require ffi/unsafe
         ffi/unsafe/define)
(define-ffi-definer define-libgit (ffi-lib "/opt/local/lib/libgit2.dylib"))
(define _git_repository-ptr (_cpointer/null 'git_repository))
(define-libgit git_repository_init (_fun _git_repository-ptr _string _bool -> _int))

But then trying to use the function does not work:

-> (define new_repo _git_repository-ptr)
-> (git_repository_init new_repo "/tmp/..." #f)
; git_repository->C: argument is not `git_repository' pointer
;   argument: #<ctype>
; [,bt for context]

libgit2 does not offer a function to initialize the pointer, as shown in the Racket FFI documentation example.

Is this the correct way to define a nullable pointer and initialize it to NULL?

git_repository, on the other hand, is a struct defined in the library. Am I supposed to use define-cstruct on the Racket side to get it correctly? This can be cumbersome, since the struct is defined in terms of other structs, with some levels of nesting. Are there any patterns to handle this situation?

1

1 Answers

7
votes

The problem isn't that the pointer wasn't initialised to null; in fact, the git_repository_init documentation does not say that it has to be initialised to anything at all. The problem is that it's an out parameter, and your function declaration didn't specify so.

Here's some code I tested with (on Ubuntu 14.04) that worked for me:

> (require ffi/unsafe ffi/unsafe/define) 
> (define-ffi-definer define-libgit (ffi-lib "libgit2"))
> (define _git_repository-ptr (_cpointer/null 'git_repository)) 
> (define-libgit git_repository_init (_fun (repo : (_ptr o _git_repository-ptr)) _string _bool -> (rc : _int) -> (values repo rc)))
> (git_repository_init "/tmp/repo" #f)
#<cpointer:git_repository>
0

See the documentation for _ptr on how to specify an out parameter. (You can use it to specify in/out parameters too, but those are generally rare.)

(Of course, you'll eventually want to massage your FFI so that instead of returning two values like in my example, you'll instead simply check the return code for an error, raising a proper Racket error as appropriate, and return just the repository pointer if successful.)