In the email below, I address the follow-up questions you passed to the answers I passed in the previous email, mention reasons for why we recognize those rules I described, and I also pass another three practices regarding FFI development, and two questions that neither the manual nor the mailing list afaik have addressed, that are vital to how FFI:s are best designed.
2008/9/5 Joel J. Adamson <
adamsonj@email.unc.edu>
<adamsonj@email.unc.edu>
Thanks so much for the tips; I have some follow-up questions:
>>>>> "MM" == Mikael More <mikael.more@gmail.com> writes:
MM> - Throwing exceptions with no handler/catcher may sigsegv your
MM> app.
Good to know, I was unaware of this.
We saw this in threads, i.e. we got sigsegv when the thunk of a threads throws an exception and there was no exception handler.
MM> - You should presuppose that ___argXX variables inside
MM> Scheme-to-C calls are trashed after C-to-Scheme calls. I.e.: If
MM> you have declared a c-define or c-lambda that takes parameters,
MM> these will appear as ___arg0, ___arg1 etc. in the C code of the
MM> c-define / c-lambda. These will have correct contents all until
MM> you invoke Scheme functions from the C code. not afterwards.
Okay, take the following functions, that may be the source of the
problem:
/* C function for transferring vectors */
int f64vector_length (___SCMOBJ);
double f64vector_ref (___SCMOBJ, int);
double * scm_vector_to_C (___SCMOBJ f64vec)
{
/* initialize the length of the vector */
int len = f64vector_length (f64vec);
/* initializes the new vector */
double * newCvec = malloc (len*sizeof (double));
/* declare a counter */
int i;
/* iterate over the length of the vector copying each object into
the new vector */
for (i = 0; i<len; i++)
{
/* could be re-written using pointer arithmetic */
newCvec[i] = f64vector_ref (f64vec, i);
}
return newCvec;
}
Can you please provide the complete c-lambda for this one?
and then the corresponding "c-define"s:
;; Scheme function for getting data out of f64vectors
(c-define (proc1 f64vec i)
(scheme-object int)
double
"f64vector_ref" ""
(f64vector-ref f64vec i))
(c-define (proc2 f64vec)
(scheme-object)
int
"f64vector_length" ""
(f64vector-length f64vec))
MM> So back them up to the stack if you do.
Forgive my ignorance of C, but how do I do that? (where the heck is
this stack that everyone speaks of?)
In C, "the stack" == "local variables". Back up to stack == make local variables and store there.
Now, I also noticed this passage in the C-interface chapter of the
manual:
,----
| Within the C code the variables `___arg1', `___arg2', etc. can be
| referenced to access the converted arguments. Similarly, the result to
| be returned from the call should be assigned to the variable `___result'
| except when the result is of type `struct', `union', `type', `pointer',
| `nonnull-pointer', `function' or `nonnull-function' in which case a
| pointer must be assigned to the variable `___result_voidstar' which is
| of type `void*'. For results of type `pointer', `nonnull-pointer',
| `function' and `nonnull-function', the value assigned to the variable
| `___result_voidstar' must be the pointer or function cast to `void*'.
`----
However, I have functions like this:
(define make-gsl-matrix
(c-lambda (int int scheme-object)
gsl-matrix*
"gsl_matrix * ___matrix = gsl_matrix_alloc (___arg1, ___arg2);
___matrix->data = scm_vector_to_C (___arg3);
___matrix->size1 = ___arg1;
___matrix->size2 = ___arg2;
___result_voidstar = ___matrix;"))
The rule about backing up arguments to the stack to save argument value contents from being lost before calling Scheme functions from C, when in a call to C from Scheme, would in the case of the c-lambda above translate into:
(define make-gsl-matrix
(c-lambda (int int scheme-object)
gsl-matrix*
"int a1 = ___arg1; int a2 = ___arg2; int a3 = ___arg3;
gsl_matrix * ___matrix = gsl_matrix_alloc (___arg1, ___arg2);
___matrix->data = scm_vector_to_C (a3);
___matrix->size1 = a1;
___matrix->size2 = a2;
___result_voidstar = ___matrix;"))
( I do not know if the rule applies to c-lambdas with particular argument counts. The case we tracked down was a c-lambda taking over ten arguments. Also I have no idea if the rule applies to very basic procedures such as car, cdr, +, etc. )
It seems that this is seriously wrong, according to the paragraph above:
the last line should be
___result_voidstar = (void*)___matrix;
and the return type should be void* and not gsl-matrix*, defined as
follows:
(c-define-type gsl-matrix*
(pointer "gsl_matrix"
gsl-matrix*))
But then the question becomes how do I access the result I need? I
would much rather have a function that returns a matrix as a result
(functional style) rather than having it imperatively modify an existing
variable, a la C.
Returning a gsl-matrix* from a c-lambda is perfectly fine.
Thanks for your help!
## You may want to hook a release function to it also, that is a function
that will perform the garbage collection for variables of this type,
when the garbage collector invokes it to.
Example:
(c-declare #<<c-declare-end
___SCMOBJ release_cell_star(void* p) {
if (p == 0)
fprintf(stderr,"release_cell_star: Called with null parameter! Internal inconsistency !! Ignoring.\n");
else {
delete (Cell*) p;
}
return ___FIX(___NO_ERR);
}
c-declare-end
)
(c-define-type Cell* (pointer "Cell" Cell* "release_cell_star"))
The example above is for freeing a C++ object. In C, you would typically have free(p); instead of delete (Cell*) p; .
## Be careful never to reintroduce a pointer to a structure from the C world into the Scheme world twice, so that the garbage collector would trig twice. Examples:
OK:
(c-declare #<<c-declare-end
___SCMOBJ release_mystruct_star(void* p) {
if (p == 0)
fprintf(stderr,"release_mystruct_star: Called with null parameter! Internal inconsistency !! Ignoring.\n");
else {
delete (Cell*) p;
}
return ___FIX(___NO_ERR);
}
c-declare-end
)
(c-define-type MyStruct* (pointer "MyStruct" MyStruct* "release_mystruct_star"))
OK:
(define create-mystruct (c-lambda () MyStruct* #<<c-lambda-end
___result_voidstar = (void*) [ some code that allocates a new MyStruct ];
c-lambda-end
))
NOT OK. The reason it's not OK is because the MyStruct* is reintroduced from the C world to the Scheme world when create-mystruct returns.
(define create-mystruct (c-lambda () MyStruct* #<<c-lambda-end
___result_voidstar = (void*) construct_my_returnvalue([ some code that allocates a new MyStruct ]);
c-lambda-end
))
(c-define (construct-my-returnvalue struct)
(MyStruct*)
MyStruct*
"construct_my_returnvalue" "static"
[ code ] )
OK:
(define create-mystruct (c-lambda () scheme-object #<<c-lambda-end
___result = construct_my_returnvalue([ some code that allocates a new MyStruct ]);
c-lambda-end
))
(c-define (construct-my-returnvalue struct)
(MyStruct*)
scheme-object
"construct_my_returnvalue" "static"
[ code ] )
Function that reintroduces a MyStruct* from the C world to the Scheme world.
NOT OK:
(define do-something-with-mystruct (c-lambda (MyStruct*) MyStruct* #<<c-lambda-end
[code that does something with ___arg1]
___result_voidstar = ___arg1;
c-lambda-end
))
Function that reintroduces a MyStruct* from the C world to the Scheme world. OK:
(define (do-something-with-mystruct MyStruct*)
(do-something-with-mystruct MyStruct* MyStruct*))
(define do-something-with-mystruct-private (c-lambda (MyStruct* scheme-object) scheme-object #<<c-lambda-end
[code that typecasts ___arg1 to a MyStruct and does something with ___arg1]
___result = ___arg2;
c-lambda-end
))
Function that reintroduces a MyStruct* from the C world to the Scheme world.
NOT OK, What makes it not OK is two things: first, it reintroduces MyStruct* from the C world to the Scheme world when invoking construct_my_returnvalue, then it reintroduces it yet another time when do-something-with-mystruct returns.
(define do-something-with-mystruct (c-lambda (MyStruct*) MyStruct* #<<c-lambda-end
[code that does something with ___arg1]
___result_voidstar = construct_my_returnvalue(___arg1);
c-lambda-end
))
(c-define (construct-my-returnvalue struct)
(MyStruct*)
MyStruct*
"construct_my_returnvalue" "static"
[ code ] )
Function that reintroduces a MyStruct* from the C world to the Scheme world. OK: