ffi/unsafe docs: improve `_cprocedure' docs
Among other improvements, add information about reachability of callout arguments.
This commit is contained in:
parent
33b89d6cb4
commit
3c744229e8
|
@ -413,15 +413,15 @@ instead, since it manages a wide range of complicated cases.
|
|||
|
||||
The resulting type can be used to reference foreign functions (usually
|
||||
@racket[ffi-obj]s, but any pointer object can be referenced with this type),
|
||||
generating a matching foreign callout object. Such objects are new primitive
|
||||
generating a matching foreign @deftech{callout} object. Such objects are new primitive
|
||||
procedure objects that can be used like any other Racket procedure.
|
||||
As with other pointer types, @racket[#f] is treated as a @cpp{NULL}
|
||||
function pointer and vice versa.
|
||||
|
||||
A type created with @racket[_cprocedure] can also be used for passing
|
||||
Racket procedures to foreign functions, which will generate a foreign
|
||||
function pointer that calls the given Racket procedure when it is
|
||||
used. There are no restrictions on the Racket procedure; in
|
||||
function pointer that calls to the given Racket @deftech{callback}
|
||||
procedure. There are no restrictions on the Racket procedure; in
|
||||
particular, its lexical context is properly preserved.
|
||||
|
||||
The optional @racket[abi] keyword argument determines the foreign ABI
|
||||
|
@ -431,137 +431,177 @@ values---@racket['stdcall] and @racket['sysv] (i.e., ``cdecl'')---are
|
|||
currently supported only for 32-bit Windows; using them on other
|
||||
platforms raises an exception. See also @racketmodname[ffi/winapi].
|
||||
|
||||
If @racket[atomic?] is true, then when a Racket procedure is given
|
||||
this procedure type and called from foreign code, then the Racket
|
||||
process is put into atomic mode while evaluating the Racket procedure
|
||||
body. In atomic mode, other Racket threads do not run, so the Racket
|
||||
code must not call any function that potentially blocks on
|
||||
synchronization with other threads, or else it may lead to deadlock. In
|
||||
addition, the Racket code must not perform any potentially blocking
|
||||
operation (such as I/O), it must not raise an uncaught exception, it
|
||||
must not perform any escaping continuation jumps, and its non-tail
|
||||
recursion must be minimal to avoid C-level stack overflow; otherwise,
|
||||
the process may crash or misbehave.
|
||||
For @tech{callouts} to foreign functions with the generated type:
|
||||
|
||||
If an @racket[async-apply] procedure is provided, then a Racket
|
||||
procedure with the generated procedure type can be applied in a
|
||||
foreign thread (i.e., an OS-level thread other than the one used to
|
||||
run Racket). The call in the foreign thread is transferred to the
|
||||
OS-level thread that runs Racket, but the Racket-level thread (in the
|
||||
sense of @racket[thread]) is unspecified; the job of the provided
|
||||
@racket[async-apply] procedure is to arrange for the callback procedure to be
|
||||
run in a suitable Racket thread. The given @racket[async-apply] procedure is
|
||||
applied to a thunk that encapsulates the specific callback invocation,
|
||||
and the foreign OS-level thread blocks until the thunk is called and
|
||||
completes; the thunk must be called exactly once, and the callback
|
||||
invocation must return normally. The given @racket[async-apply] procedure
|
||||
itself is called in atomic mode (see @racket[atomic?] above). If the
|
||||
callback is known to complete quickly, requires no synchronization,
|
||||
and works independent of the Racket thread in which it runs, then
|
||||
it is safe for the given
|
||||
@racket[async-apply] procedure to apply the thunk directly. Otherwise,
|
||||
the given @racket[async-apply] procedure
|
||||
must arrange for the thunk to be applied in a
|
||||
suitable Racket thread sometime after the given
|
||||
@racket[async-apply] procedure itself
|
||||
returns; if the thunk raises an exception or synchronizes within an
|
||||
unsuitable Racket-level thread, it can deadlock or otherwise damage
|
||||
the Racket process. Foreign-thread detection to trigger
|
||||
@racket[async-apply] works only when Racket is compiled with OS-level
|
||||
thread support, which is the default for many platforms. If a callback
|
||||
with an @racket[async-apply] is called from foreign code in the same
|
||||
OS-level thread that runs Racket, then the @racket[async-apply] wrapper is
|
||||
not used.
|
||||
@itemize[
|
||||
|
||||
@margin-note{The @racket[atomic?] and @racket[async-apply] arguments
|
||||
affect callbacks into Racket, while @racket[in-original-place?]
|
||||
affects calls from Racket into foreign code.}
|
||||
@item{If @racket[save-errno] is @racket['posix], then the value of
|
||||
@as-index{@tt{errno}} is saved (specific to the current thread)
|
||||
immediately after a foreign function @tech{callout}
|
||||
returns. The saved value is accessible through
|
||||
@racket[saved-errno]. If @racket[save-errno] is
|
||||
@racket['windows], then the value of
|
||||
@as-index{@tt{GetLastError}}@tt{()} is saved for later use via
|
||||
@racket[saved-errno]; the @racket['windows] option is available
|
||||
only on Windows (on other platforms @racket[saved-errno] will
|
||||
return 0). If @racket[save-errno] is @racket[#f], no error
|
||||
value is saved automatically.
|
||||
|
||||
If @racket[in-original-place?] is true, then when a foreign procedure
|
||||
with the generated type is called in any Racket @tech[#:doc '(lib
|
||||
"scribblings/reference/reference.scrbl")]{place}, the procedure is
|
||||
called from the original Racket place. Use this mode for a foreign
|
||||
function that is not thread-safe at the C level, which means that it
|
||||
is not place-safe at the Racket level. Callbacks from place-unsafe
|
||||
code back into Racket at a non-original place typically will not work,
|
||||
since the place of the Racket code may have a different allocator than
|
||||
the original place.
|
||||
The error-recording support provided by @racket[save-errno] is
|
||||
needed because the Racket runtime system may otherwise preempt
|
||||
the current Racket thread and itself call functions that set
|
||||
error values.}
|
||||
|
||||
If @racket[save-errno] is @racket['posix], then the value of
|
||||
@as-index{@tt{errno}} is saved (specific to the current thread)
|
||||
immediately after a foreign function returns. The saved value is
|
||||
accessible through @racket[saved-errno]. If @racket[save-errno] is
|
||||
@racket['windows], then the value of
|
||||
@as-index{@tt{GetLastError}}@tt{()} is saved for later use via
|
||||
@racket[saved-errno]; the @racket['windows] option is available only
|
||||
on Windows (on other platforms @racket[saved-errno] will return
|
||||
0). If @racket[save-errno] is @racket[#f], no error value is saved
|
||||
automatically. The error-recording support provided by
|
||||
@racket[save-errno] is needed because the Racket runtime system
|
||||
may otherwise preempt the current Racket thread and itself call
|
||||
functions that set error values.
|
||||
@item{If @racket[wrapper] is not @racket[#f], it takes the
|
||||
@tech{callout} that would otherwise be generated and returns a
|
||||
replacement procedure. Thus, @racket[wrapper] acts a hook to
|
||||
perform various argument manipulations before the true
|
||||
@tech{callout} is invoked, and it can return different results
|
||||
(for example, grabbing a value stored in an ``output'' pointer
|
||||
and returning multiple values).}
|
||||
|
||||
The optional @racket[wrapper], if provided, is expected to be a
|
||||
function that can change a callout procedure: when a callout is
|
||||
generated, the wrapper is applied on the newly created primitive
|
||||
procedure, and its result is used as the new function. Thus,
|
||||
@racket[wrapper] is a hook that can perform various argument
|
||||
manipulations before the foreign function is invoked, and return
|
||||
different results (for example, grabbing a value stored in an
|
||||
``output'' pointer and returning multiple values). It can also be
|
||||
used for callbacks, as an additional layer that tweaks arguments from
|
||||
the foreign code before they reach the Racket procedure, and possibly
|
||||
changes the result values too.
|
||||
@item{If @racket[in-original-place?] is true, then when a foreign
|
||||
@tech{callout} procedure with the generated type is called in
|
||||
any Racket @tech[#:doc '(lib
|
||||
"scribblings/reference/reference.scrbl")]{place}, the procedure
|
||||
is called from the original Racket place. Use this mode for a
|
||||
foreign function that is not thread-safe at the C level, which
|
||||
means that it is not place-safe at the Racket
|
||||
level. @tech{Callbacks} from place-unsafe code back into Racket
|
||||
at a non-original place typically will not work, since the
|
||||
place of the Racket code may have a different allocator than
|
||||
the original place.}
|
||||
|
||||
Sending Racket functions as callbacks to foreign code is achieved by
|
||||
translating them to a foreign ``closure,'' which foreign code can call
|
||||
as plain C functions. Additional care must be taken in case the
|
||||
foreign code might hold on to the callback function. In these cases
|
||||
you must arrange for the callback value to not be garbage-collected,
|
||||
or the held callback will become invalid. The optional @racket[keep]
|
||||
keyword argument is used to achieve this. It can have the following
|
||||
values: @itemize[
|
||||
@item{Values that are provided to a @tech{callout} (i.e., the
|
||||
underlying callout, and not the replacement produced by a
|
||||
@racket[wrapper], if any) are always considered reachable by the
|
||||
garbage collector until the called foreign function returns. If
|
||||
the foreign function invokes Racket callbacks, however, beware
|
||||
that values managed by the Racket garbage collector might be
|
||||
moved in memory by the garbage collector.}
|
||||
|
||||
@item{@racket[#t] makes the callback value stay in memory as long as
|
||||
the converted function is. In order to use this, you need to hold
|
||||
on to the original function, for example, have a binding for it.
|
||||
Note that each function can hold onto one callback value (it is
|
||||
stored in a weak hash table), so if you need to use a function in
|
||||
multiple callbacks you will need to use one of the last two
|
||||
options below. (This is the default, as it is fine in most cases.)}
|
||||
]
|
||||
|
||||
@item{@racket[#f] means that the callback value is not held. This may
|
||||
be useful for a callback that is only used for the duration of the
|
||||
foreign call --- for example, the comparison function argument to
|
||||
the standard C library @tt{qsort} function is only used while
|
||||
@tt{qsort} is working, and no additional references to the
|
||||
comparison function are kept. Use this option only in such cases,
|
||||
when no holding is necessary and you want to avoid the extra cost.}
|
||||
For @tech{callbacks} to Racket functions with the generated type:
|
||||
|
||||
@item{A box holding @racket[#f] (or a callback value) --- in this case
|
||||
the callback value will be stored in the box, overriding any value
|
||||
that was in the box (making it useful for holding a single callback
|
||||
value). When you know that it is no longer needed, you can
|
||||
``release'' the callback value by changing the box contents, or by
|
||||
allowing the box itself to be garbage-collected. This is can be
|
||||
useful if the box is held for a dynamic extent that corresponds to
|
||||
when the callback is needed; for example, you might encapsulate some
|
||||
foreign functionality in a Racket class or a unit, and keep the
|
||||
callback box as a field in new instances or instantiations of the
|
||||
unit.}
|
||||
@itemize[
|
||||
|
||||
@item{A box holding @racket[null] (or any list) -- this is similar to
|
||||
the previous case, except that new callback values are consed onto
|
||||
the contents of the box. It is therefore useful in (rare) cases
|
||||
when a Racket function is used in multiple callbacks (that is, sent
|
||||
to foreign code to hold onto multiple times).}
|
||||
@item{The @racket[keep] argument provides control over reachbility by
|
||||
the garbage collector of the underlying value that foreign code
|
||||
see as a plain C function. Additional care must be taken in
|
||||
case the foreign code might retain the callback function, in
|
||||
which case the callback value must remain reachable or else the
|
||||
held callback will become invalid. The possible values of
|
||||
@racket[keep] are as follows:
|
||||
|
||||
@item{Finally, if a one-argument function is provided as
|
||||
@racket[keep], it will be invoked with the callback value when it
|
||||
is generated. This allows you to grab the value directly and use it
|
||||
in any way.}
|
||||
@itemize[
|
||||
|
||||
]}
|
||||
@item{@racket[#t] --- the @tech{callback} stays in memory as long
|
||||
as the converted Racket function is reachable. This mode is the
|
||||
default, as it is fine in most cases. Note that each Racket
|
||||
function can hold onto only one callback value through this
|
||||
mode, so it is not suitable for a function used multiple times
|
||||
as a reatined callback.}
|
||||
|
||||
@item{@racket[#f] --- the @tech{callback} value is not held. This
|
||||
mode may be useful for a callback that is only used for the
|
||||
duration of the foreign call; for example, the comparison
|
||||
function argument to the standard C library @tt{qsort} function
|
||||
is only used while @tt{qsort} is working, and no additional
|
||||
references to the comparison function are kept. Use this option
|
||||
only in such cases, when no holding is necessary and you want to
|
||||
avoid the extra cost.}
|
||||
|
||||
@item{A box holding @racket[#f] or any other non-list value --- the
|
||||
callback value is stored in the box, overriding any non-list
|
||||
value that was in the box (making it useful for holding a single
|
||||
callback value). When you know that the callback is no longer
|
||||
needed, you can ``release'' the callback value by changing the
|
||||
box contents or by allowing the box itself to become
|
||||
unreachable. This mode can be useful if the box is held for a
|
||||
dynamic extent that corresponds to when the callback is needed;
|
||||
for example, you might encapsulate some foreign functionality in
|
||||
a Racket class or a unit, and keep the callback box as a field
|
||||
in new instances or instantiations of the unit.}
|
||||
|
||||
@item{A box holding @racket[null] (or any list) --- similar to a
|
||||
box holding a non-list value, except that new callback values are
|
||||
@racket[cons]ed onto the contents of the box. This mode is
|
||||
therefore useful in cases when a Racket function is used
|
||||
in multiple callbacks (that is, sent to foreign code to hold
|
||||
onto multiple times) and all callbacks should be retained together.}
|
||||
|
||||
@item{A one-argument function --- the function is invoked with the
|
||||
callback value when it is generated. This mode allows you to
|
||||
explicitly manage reachability of the generated callback closure.}
|
||||
|
||||
]}
|
||||
|
||||
@item{If @racket[wrapper] is not @racket[#f], it takes the procedure
|
||||
to be converted into a @tech{callback} and returns a
|
||||
replacement procedure to be invoked as the callback. Thus,
|
||||
@racket[wrapper] acts a hook to perform various argument
|
||||
manipulations before a Racket callback function is called, and
|
||||
it can return different results to the foreign caller.
|
||||
|
||||
The callback value's reachability (and its interaction with
|
||||
@racket[keep] is based on the original function for the
|
||||
callback, not the result of @racket[wrapper].}
|
||||
|
||||
@item{If @racket[atomic?] is true, then when a Racket procedure is
|
||||
given this procedure type and called as a @tech{callback} from
|
||||
foreign code, then the Racket process is put into atomic mode
|
||||
while evaluating the Racket procedure body.
|
||||
|
||||
In atomic mode, other Racket threads do not run, so the Racket
|
||||
code must not call any function that potentially blocks on
|
||||
synchronization with other threads, or else it may lead to
|
||||
deadlock. In addition, the Racket code must not perform any
|
||||
potentially blocking operation (such as I/O), it must not raise
|
||||
an uncaught exception, it must not perform any escaping
|
||||
continuation jumps, and its non-tail recursion must be minimal
|
||||
to avoid C-level stack overflow; otherwise, the process may
|
||||
crash or misbehave.}
|
||||
|
||||
@item{If an @racket[async-apply] procedure is provided, then a Racket
|
||||
@tech{callback} procedure with the generated procedure type can
|
||||
be applied in a foreign thread (i.e., an OS-level thread other
|
||||
than the one used to run Racket). The call in the foreign
|
||||
thread is transferred to the OS-level thread that runs Racket,
|
||||
but the Racket-level thread (in the sense of @racket[thread])
|
||||
is unspecified; the job of the provided @racket[async-apply]
|
||||
procedure is to arrange for the callback procedure to be run in
|
||||
a suitable Racket thread.
|
||||
|
||||
The given @racket[async-apply]
|
||||
procedure is applied to a thunk that encapsulates the specific
|
||||
callback invocation, and the foreign OS-level thread blocks
|
||||
until the thunk is called and completes; the thunk must be
|
||||
called exactly once, and the callback invocation must return
|
||||
normally. The given @racket[async-apply] procedure itself is
|
||||
called in atomic mode (see @racket[atomic?] above).
|
||||
|
||||
If the callback is known to complete quickly, requires no
|
||||
synchronization, and works independent of the Racket thread in
|
||||
which it runs, then it is safe for the given
|
||||
@racket[async-apply] procedure to apply the thunk
|
||||
directly. Otherwise, the given @racket[async-apply] procedure
|
||||
must arrange for the thunk to be applied in a suitable Racket
|
||||
thread sometime after the given @racket[async-apply] procedure
|
||||
itself returns; if the thunk raises an exception or
|
||||
synchronizes within an unsuitable Racket-level thread, it can
|
||||
deadlock or otherwise damage the Racket process.
|
||||
|
||||
|
||||
Foreign-thread detection to trigger @racket[async-apply] works
|
||||
only when Racket is compiled with OS-level thread support,
|
||||
which is the default for many platforms. If a callback with an
|
||||
@racket[async-apply] is called from foreign code in the same
|
||||
OS-level thread that runs Racket, then the @racket[async-apply]
|
||||
wrapper is not used.}
|
||||
|
||||
]
|
||||
}
|
||||
|
||||
@defform/subs[#:literals (->> :: :)
|
||||
(_fun fun-option ... maybe-args type-spec ... ->> type-spec
|
||||
|
@ -589,7 +629,7 @@ form, only the input @racket[type-expr]s and the output @racket[type-expr] are
|
|||
specified, and each types is a simple expression, which creates a
|
||||
straightforward function type.
|
||||
|
||||
For instance,
|
||||
For example,
|
||||
|
||||
@racketblock[
|
||||
(_fun _int _string ->> _int)
|
||||
|
@ -616,7 +656,7 @@ previous labels, including a label given for the output which can be
|
|||
used to access the actual foreign return value.
|
||||
|
||||
In rare cases where complete control over the input arguments is needed, the
|
||||
wrapper's argument list can be specified as @racket[args], in any form
|
||||
wrapper's argument list can be specified as @racket[maybe-args], in any form
|
||||
(including a ``rest'' argument). Identifiers in this place are related to type
|
||||
labels, so if an argument is there is no need to use an expression.
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user