racket/pkgs/racket-doc/scribblings/foreign/serialize-cstruct.scrbl

186 lines
8.8 KiB
Racket

#lang scribble/doc
@(require "utils.rkt"
(for-label racket/serialize
ffi/serialize-cstruct
(except-in ffi/unsafe ->))
scribble/racket
scribble/example)
@(define serialize-eval (make-base-eval))
@examples[#:eval serialize-eval #:hidden (require ffi/serialize-cstruct
ffi/unsafe
racket/serialize)]
@title[#:tag "serialize-struct"]{Serializable C Struct Types}
@defmodule[ffi/serialize-cstruct]
@defform/subs[(define-serializable-cstruct _id ([field-id type-expr] ...)
property ...)
[(property (code:line #:alignment alignment-expr)
(code:line #:malloc-mode malloc-mode-expr)
(code:line #:serialize-inplace)
(code:line #:deserialize-inplace)
(code:line #:version vers)
(code:line #:other-versions ([other-vers deserialize-chain-expr
convert-proc-expr
unconvert-proc-expr
cycle-convert-proc-expr]
...))
(code:line #:property prop-expr val-expr))]]{
Like @racket[define-cstruct], but defines a serializable type, with
several changed additional bindings:
@itemize[
@item{@racketidfont{make-@racketvarfont{id}} --- always uses
@racket['atomic] allocation, even if @racket[#:malloc-mode] is
specified (for historical reasons).}
@item{@racketidfont{make-@racketvarfont{id}/mode} --- like behaves
like @racketidfont{make-@racketvarfont{id}} but uses the mode
or allocator specified via @racket[malloc-mode-expr] (for
historical reasons).}
@item{@racketidfont{deserialize:cstruct:@racketvarfont{id}} (for a
@racket[vers] of @racket[0]) or
@racketidfont{deserialize:cstruct:@racketvarfont{id}-v@racketvarfont{vers}}
(for a @racket[vers] of @racket[1] or more) --- deserialization information that is
automatically exported from a @racket[deserialize-info] submodule.}
@item{@racketidfont{deserialize-chain:cstruct:@racketvarfont{id}}
(for a @racket[vers] of @racket[0]) or
@racketidfont{deserialize-chain:cstruct:@racketvarfont{id}-v@racketvarfont{vers}}
(for a @racket[vers] of @racket[1] or more) --- deserialization
information for use via @racket[#:other-versions] in other
@racket[define-serializable-cstruct] forms.}
@item{@racketidfont{deserialize:cstruct:@racketvarfont{id}} (for an
@racket[other-vers] of @racket[0]) or
@racketidfont{deserialize:cstruct:@racketvarfont{id}-v@racketvarfont{other-vers}}
(for an @racket[other-vers] of @racket[1] or more) ---
deserialization information that is automatically exported from
a @racket[deserialize-info] submodule.}
]
Instances of the new type fulfill the @racket[serializable?] predicate and can
be used with @racket[serialize] and @racket[deserialize]. Serialization may
fail if one of the fields contains an arbitrary pointer, an embedded
non-serializable C struct, or a pointer to a non-serializable C struct.
Array-types are supported as long as they don't contain one of these types.
The default @racket[vers] is @racket[0], and @racket[vers] must be a
literal, exact, non-negative integer. An @racket[#:other-versions]
clause provides deserializers for previous versions of the structure
with the name @racketvarfont{id}, so that previously serialized data can be
deserialized after a change to the declaration of @racketvarfont{id}. For
each @racket[other-vers], @racket[deserialize-chain-expr] should be
the value of a
@racketidfont{deserialize:cstruct:@racketvarfont{other-id}} binding
for some other @racket{other-id} declared with
@racket[define-serializable-cstruct] that has the same shape that the
previous version of @racketvarfont{id}; the function produced by
@racket[convert-proc-expr] should convert an instance of
@racket[_other-id] to an instance of @racketvarfont{id}. The functions
produced by @racket[unconvert-proc-expr] and
@racket[cycle-convert-proc-expr] are used if a record is involved in a
cycle; the function from @racket[unconvert-proc-expr] takes an
@racketvarfont{id} instance produced by @racket[convert-proc-expr]'s function
back to a @racket[_other-id], while @racket[cycle-convert-proc-expr]
returns two values: a shell instance of @racketidfont{id} and function to
accept a filled @racket[_other-id] whose content should be moved to
the shell instance of @racketidfont{id}.
The @racket[malloc-mode-expr] arguments control the memory allocation
for this type during deserialization and
@racketidfont{make-@racketvarfont{id}/mode}. It can be one of the mode
arguments to @racket[malloc], or a procedure
@;
@racketblock[(-> exact-positive-integer? cpointer?)]
@;
that allocates memory of the given size. The default is
@racket[malloc] with @racket['atomic].
When @racket[#:serialize-inplace] is specified, the serialized
representation shares memory with the C struct object. While being more
efficient, especially for large objects, changes to the object after
serialization may lead to changes in the serialized representation.
A @racket[#:deserialize-inplace] option reuses the memory of the serialized
representation, if possible. This option is more efficient for large objects,
but it may fall back to allocation via @racket[malloc-mode-expr] for cyclic
structures. As the allocation mode of the serialized representation
will be @racket['atomic] by default or may be arbitrary if
@racket[#:serialize-inplace] is specified, inplace deserialisation
should be used with caution whenever the object contains pointers.
When the C struct contains pointers, it is advisable to use a custom
allocator. It should be based on a non-moving-memory allocation like
@racket['raw], potentially with manual freeing to avoid memory leaks
after garbage collection.
@history[#:changed "1.1" @elem{Added @racket[#:version] and @racket[#:other-versions].}]
@examples[
#:eval serialize-eval
(define-serializable-cstruct _fish ([color _int]))
(define f0/s (serialize (make-fish 1)))
(fish-color (deserialize f0/s))
(define-serializable-cstruct _aq ([a _fish-pointer]
[d _aq-pointer/null])
#:malloc-mode 'nonatomic)
(define aq1 (make-aq/mode (make-fish 6) #f))
(code:line (set-aq-d! aq1 aq1) (code:comment "create a cycle"))
(define aq0/s (serialize aq1))
(aq-a (aq-d (aq-d (deserialize aq0/s))))
(code:comment @#,elem{Same shape as original @racket[aq]:})
(define-serializable-cstruct _old-aq ([a _fish-pointer]
[d _pointer])
#:malloc-mode 'nonatomic)
(code:comment @#,elem{Replace the original @racket[aq]:})
(define-serializable-cstruct _aq ([a _fish-pointer]
[b _fish-pointer]
[d _aq-pointer/null])
#:malloc-mode 'nonatomic
#:version 1
#:other-versions ([0 deserialize-chain:cstruct:old-aq
(lambda (oa)
(make-aq/mode (old-aq-a oa)
(old-aq-a oa)
(cast (old-aq-d oa) _pointer _aq-pointer)))
(lambda (a)
(make-old-aq/mode (aq-a a)
(aq-d a)))
(lambda ()
(define tmp-fish (make-fish 0))
(define a (make-aq/mode tmp-fish tmp-fish #f))
(values a
(lambda (oa)
(set-aq-a! a (old-aq-a oa))
(set-aq-b! a (old-aq-a oa))
(set-aq-d! a (cast (old-aq-d oa) _pointer _aq-pointer)))))]))
(code:comment "Deserialize old instance to new cstruct:")
(fish-color (aq-a (aq-d (aq-d (deserialize aq0/s)))))
(define aq1/s (serialize (make-aq (make-fish 1) (make-fish 2) #f)))
(code:comment @#,elem{New version of @racket[fish]:})
(define-serializable-cstruct _old-fish ([color _int]))
(define-serializable-cstruct _fish ([weight _float]
[color _int])
#:version 1
#:other-versions ([0 deserialize-chain:cstruct:old-fish
(lambda (of)
(make-fish 10.0 (old-fish-color of)))
(lambda (a) (error "cycles not possible!"))
(lambda () (error "cycles not possible!"))]))
(code:comment @#,elem{Deserialized content upgraded to new @racket[fish]:})
(fish-color (aq-b (deserialize aq1/s)))
(fish-weight (aq-b (deserialize aq1/s)))
]}
@close-eval[serialize-eval]