From: Dmitry Bogatov <kact...@gnu.org> Document '<foreign-type>' record type and 'define-foreign-type' procedure. --- doc/ref/api-foreign.texi | 150 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 147 insertions(+), 3 deletions(-)
diff --git a/doc/ref/api-foreign.texi b/doc/ref/api-foreign.texi index c2c49ec..605dbed 100644 --- a/doc/ref/api-foreign.texi +++ b/doc/ref/api-foreign.texi @@ -26,6 +26,7 @@ procedures. * Modules and Extensions:: Loading C extensions into modules. * Foreign Pointers:: Accessing global variables. * Dynamic FFI:: Calling arbitrary C functions. +* Declarative dynamic FFI:: Declarative macros for calling C functions @end menu @@ -439,10 +440,10 @@ section takes up the problem of accessing C values from Scheme, and the next discusses C functions. @menu -* Foreign Types:: Expressing C types in Scheme. -* Foreign Variables:: Pointers to C symbols. +* Foreign Types:: Expressing C types in Scheme. +* Foreign Variables:: Pointers to C symbols. * Void Pointers and Byte Access:: Pointers into the ether. -* Foreign Structs:: Packing and unpacking structs. +* Foreign Structs:: Packing and unpacking structs. @end menu @node Foreign Types @@ -980,6 +981,149 @@ on a few exotic architectures. Thus, user code may need to check many architectures, including (as of libffi 3.0.9) x86, ia64, SPARC, PowerPC, ARM, and MIPS, to name a few. +@node Declarative dynamic FFI +@subsection Declarative dynamic FFI + +Dynamic foreign function interface, described in previous section is +powerful and sufficient to bind power of almost any C library to Guile +with only Scheme code. But it is very low-level and does not provide any +abstractions for common function conventions. This is where module +@code{(system foreign declarative)} comes into the game. + +@deftp {Record type} <foreign-type> name validate encode decode type +@code{<foreign-type>} record represents correspondence between C data +type and some Scheme object. It contains @code{name} for purposes of +error reporting, procedure @code{validate}, which will can be called +with single argument and return @code{#t} if it's argument is suitable +for passing to @code{encode} procedure, and return @code{#f} or throw if +not. + +Procedure @code{encode} converts Scheme object of arbitrary complexity +to C data type, coherent to @code{type}. @xref{Dynamic FFI} Procedure +@code{decode} converts back from C data type to some Scheme object. +At least one of them should be present for record be useful. + +Fields of @code{<foreign-type>} record must (but not enforced) +to obey following rules: + +@enumerate + +@item +Every procedure @code{validate}, @code{encode} and @code{decode} are +pure -- their result depends only on argument, and they do nothing, +except of returning a value. +@example +;; for every `procedure' from 'validate', 'encode', 'decode' +;; for every Scheme value `x' +;; following expressions are either both throw or return same value. +;; they can freely be replaced one with another. +(list (procedure x) + (procedure x)) +(let ((value (validate x)) + (list value value))) +@end example + +@item +If field @code{decode} is present, procedure @code{decode} accepts any +argument with type matching @code{type} field and returns normally some +value. If both @code{decode} and @code{encode} fields are present, +value, returned by @code{decode} procedure, can be encoded back by +@code{encode} procedure. +@example +;; For every Scheme value 'x', matching field 'type' following expression +;; never throw and always evaluates to #t +(equal? (encode (decode x)) x) +@end example + +@item +For every value, for which @code{validate} procedure returned @code{#t}, +@code{encode} procedure returns normally. +@example +;; For every Scheme value 'x' following expression never throws +(and (false-if-exception (validate x)) + (encode x)) +@end example +@end enumerate +@end deftp + +@deffn {Scheme Macro} define-foreign-type name @ + [#:validate-proc=(const #t)] @ + [#:encode-proc=error] @ + [#:decode-proc=error] @ + [#:type='*] +Defines @code{name} to a @code{<foreign-type>} record with @code{name}, +fields @code{validate-proc}, @code{encode-proc}, @code{decode-proc}, +@code{type} configured with keywords arguments. + +Here is how one may define foreign type record, that match C integer with +Scheme boolean +@example +(use-modules (system foreign)) ;; `int' is defined there +(use-modules (system foreign declarative)) + +(define (boolean->integer b) + (if b 1 0)) +(define (integer->boolean i) + (not (zero? i)) + +(define-foreign-type boolean: + #:validate-proc boolean? + #:encode-proc boolean->integer + #:decode-proc integer->boolean + #:type int) +@end example +By convention, @code{<foreign-type>} record's names ends with colon. +@end deffn + +Module @code{(system foreign declarative)} provides such records for +every primitive C type exported by @code{(system foreign)}. + +@deffn {Scheme Macro} define-foreign-function function-name ((type name) ...) :: return @ + [#:dynamic-library=(dynamic-link)] [#:symbol] +Define Scheme procedure @code{function-name}, that wraps C function with name @code{symbol} +from dynamic library @code{dynamic-library}. Both @code{return} and @code{type} arguments +must be @code{<foreign-type>} records, to specify how to pass arguments to underlying +C function. + +Arguments, passed to @code{function-name} are validated and encoded +according to specified @code{type}s, passed to C function. Return value +of C function is decoded according to @code{return} foreign-type record. + +@code{symbol} is name of C function, unless specified explicitly is deduced +from @code{function-name} by following rules: + +@enumerate + +@item +Remove prefix "c-" prefix, if present. +@item +Replace every non-alphanumeric symbol with underscores. + +@end enumerate + +Here are some examples of @code{define-foreign-function} usage: +@example +;; In such simple case neither 'dynamic-library' keyword needed, since +;; 'sin' function is already in library, pulled by libguile, neither +;; 'symbol' keyword, since underlying function name "sin" is deduced +(define-foreign-function c-sin ((double: arg)) :: double:) +(equal? (c-sin 10) (sin 10)) ; #t + +;; In case of time(3) function, we must specify correct signature, +;; but we can %null-pointer and hide implementation details. +(define ask-time + (let () + (define-foreign-function c-time ((*: t)) :: long:) + (lambda () + (c-time %null-pointer)))) + +(ask-time) ; 1468434734, at time of writing + +;; We can as easily work with constant strings +(define-foreign-time c-rename ((string: oldpath) (string: newpath)) :: int:) +@end example +@end deffn + @c Local Variables: @c TeX-master: "guile.texi" @c End: -- I may be not subscribed. Please, keep me in carbon copy.