Compiler-assisted serialization
 
Portable snapshots of ordinary values, strings, arrays and public UDT fields

Syntax

#include once "fbserialize.bi"
payload = Serialize( source )
success = Deserialize( payload, destination )

Description

Serialize converts a supported value to an owned binary string. Deserialize reconstructs it with independent ownership and leaves the destination unchanged on failure. Both names are macros supplied by fbserialize.bi and require a compiler and runtime built with serialization support.

The compiler determines the type's public data structure and emits target-specific traversal instructions together with a portable schema. The runtime executes those instructions without a reflection registry or manual field registration. Ordinary assignment, UDT memory layouts and the existing Put and Get file operations retain their existing behavior.

Supported data and ownership

Supported data consists of Boolean, all signed and unsigned integer types, enums, IEEE Single and Double, dynamic String, fixed String * N, nested ordinary UDTs, fixed arrays, dynamic arrays and arrays of UDTs. Packed UDTs are supported. Only public data fields are traversed.

Owned string and dynamic array contents are copied recursively. Embedded null bytes and fixed-string spaces are preserved. The source, payload and reconstructed destination have separate lifetimes. See the deep ownership example on Serialize and the whole-array failure example on Deserialize.

Pointers, procedure pointers, ByRef fields, bitfields, unions, inheritance, non-public fields, ZString, WString, UString and UDTs with user-defined constructors, destructors or operators are rejected at compilation. Convert external objects or pointer graphs into supported wire records with an explicit application codec. Compiler-generated string/array lifetime operations are supported. A Const source is allowed; a destination containing Const fields is not.

Sources and destinations must contain valid FreeBASIC values, not descriptors overwritten with raw bytes. Callers must synchronize accesses when other threads can modify the same values. Each codec call owns its traversal and allocation state.

Scalars, fixed strings and fixed arrays

A scalar uses the same API as a record. Fixed strings retain every stored byte, including padding and embedded nulls. Fixed arrays use empty parentheses and require matching element types, dimensions and bounds in the destination. This example checks each reconstructed byte and array element.

Example

'' examples/manual/array/serialization-fixed.bas
''
'' FreeBASIC Manual: Compiler-assisted serialization
'' Purpose: Round-trip scalar values, fixed strings and fixed arrays.
'' Responsibilities: Preserve stored bytes, array bounds and element order.
'' This example intentionally contains no dynamic records or transport code.
'' --------

#include Once "fbserialize.bi"

Dim As Long original_number = -42, restored_number
Dim As String payload = Serialize(original_number)
If Err <> 0 Then End 1
If Deserialize(payload, restored_number) = 0 Then End 1
If restored_number <> original_number Then End 1
Print "Number: "; restored_number

'' A fixed string includes its stored padding, as well as embedded nulls.
Dim As String * 6 original_text, restored_text
original_text = "A" & Chr(0) & "B"
payload = Serialize(original_text)
If Err <> 0 Then End 1
If Deserialize(payload, restored_text) = 0 Then End 1
For position As Integer = 1 To Len(original_text)
    If Asc(restored_text, position) <> Asc(original_text, position) Then End 1
Next position
Print "Fixed string bytes: "; Len(restored_text)
Print "Embedded null byte: "; Asc(restored_text, 2)

'' Fixed arrays require the same element type, dimensions and bounds.
Dim original_values(-1 To 0, 2 To 4) As Long
Dim restored_values(-1 To 0, 2 To 4) As Long
Dim As Integer row, column
For row = -1 To 0
    For column = 2 To 4
        original_values(row, column) = row * 10 + column
    Next column
Next row

payload = Serialize(original_values())
If Err <> 0 Then End 1
If Deserialize(payload, restored_values()) = 0 Then End 1
For row = -1 To 0
    For column = 2 To 4
        If restored_values(row, column) <> original_values(row, column) Then End 1
    Next column
Next row
Print "Array corners: "; restored_values(-1, 2); " / "; restored_values(0, 4)

'' end of serialization-fixed.bas


Schema compatibility and wire format

Every version 1 payload begins with four ASCII bytes FBS1, a little-endian unsigned 32-bit schema byte count, the exact schema bytes without a terminator, then the logical contents in declaration or array storage order. No padding, pointers, native offsets or descriptor bytes are sent.

The schema includes member names and order, scalar types, enum member names and values, fixed string lengths, array kinds, fixed bounds and declared ranks. UDT names, packing and native Integer width do not affect compatibility. Structurally identical records may have different UDT names. Schema comparison uses all bytes rather than only a hash.

  • Fixed-width integers use their declared 1, 2, 4 or 8 wire bytes. Signed values use two's complement.
  • Integer, UInteger and enums use 64 wire bits. A narrower reader rejects values outside its native range.
  • Boolean uses one byte, either 0 or 1.
  • Single and Double use their 4-byte and 8-byte IEEE representations, preserving signed zero and NaN bits.
  • Dynamic strings use an unsigned 32-bit byte length followed by that many bytes. Fixed strings use exactly their declared byte count.
  • UDTs and inline fixed arrays contain consecutive logical fields or elements.
  • Descriptor-backed arrays, including root arrays, store a one-byte allocated rank, or zero for an unallocated array. Allocated arrays then store signed 64-bit lower and upper bounds for each dimension, followed by elements with the last dimension varying fastest.

All multibyte wire values use little-endian order. This representation permits compatible 32-bit and 64-bit applications to exchange data. Strings contain bytes rather than a declared text encoding; applications must agree on an encoding such as UTF-8 when text crosses systems. Unsupported versions, mismatched schemas, invalid bounds, truncated input and trailing bytes are rejected.

Limits and errors

Version 1 enforces these per-call limits before or during traversal:

  • Complete payload: 16 MiB.
  • Aggregate decode allocations, including metadata and scratch storage: 64 MiB.
  • Canonical schema: 64 KiB.
  • Traversal instructions: 4,096.
  • Nested instruction depth: 32 levels.
  • Array dimensions: 8.
  • Array elements and total encode/decode value visits: 1,048,576.

The compiler rejects types exceeding its schema, instruction or depth limits. The runtime checks counts, extent products and native integer/bound conversions before allocating or reconstructing descriptors. Each successful call sets Err to zero. Check Err immediately after Serialize; check the return value from Deserialize before using the reconstructed value. Both operations report FB_SERIALIZE_INVALID (1) or FB_SERIALIZE_OUT_OF_MEMORY (4) on failure.

Binary files and transport framing

The codec handles memory buffers. The application handles files, sockets, message boundaries and transport errors. A binary string can contain null bytes; pass its full byte length to APIs instead of treating it as a terminated string. Supply exactly one complete payload to Deserialize. Streams carrying multiple payloads need application framing, such as a length prefix with a checked maximum.

This example writes one payload to a new or empty file, then reads the full file with a size limit and an exact byte-count check. Run it with a path argument, for example serialization saved-state.bin. It leaves that file available for inspection and refuses to overwrite a nonempty file.

Example

'' examples/manual/fileio/serialization.bas
''
'' FreeBASIC Manual: Compiler-assisted serialization
'' Purpose: Carry a serialized record through an ordinary binary file.
'' Responsibilities: File errors, bounded reads and exact byte counts.
'' Ownership: Each opened file handle is closed by this program.
'' The caller owns the output file, which is left in place for inspection.
'' This example intentionally contains no stream or socket framing.
'' --------

#include Once "fbserialize.bi"

Type SavedState
    title As String
    score As Long
End Type

Dim As String filename = Command(1)
If filename = "" Then
    Print "Usage: serialization <new-or-empty-file>"
    End 1
End If

Dim As SavedState original, restored
original.title = "Saved" & Chr(0) & "game"
original.score = 42
Dim As String payload = Serialize(original)
If Err <> 0 Then End 1

Dim As Integer file_number = FreeFile()
If Open(filename For Binary Access Read Write As #file_number) <> 0 Then End 1

'' Use a new or empty file so older trailing bytes cannot survive a write.
If LOF(file_number) <> 0 Then
    Close #file_number
    Print "Choose a new or empty file."
    End 1
End If
If Put(#file_number, , payload) <> 0 Then
    Close #file_number
    End 1
End If
If Close(file_number) <> 0 Then End 1

file_number = FreeFile()
If Open(filename For Binary Access Read As #file_number) <> 0 Then End 1

'' Bound the allocation before reading. Version 1 accepts at most 16 MiB.
Dim As LongInt byte_count = LOF(file_number)
If byte_count <= 0 OrElse byte_count > 16 * 1024 * 1024 Then
    Close #file_number
    End 1
End If
Dim As String received = Space(CInt(byte_count))
Dim As UInteger bytes_read
Dim As Integer read_error = Get(#file_number, , received, , bytes_read)
Dim As Integer close_error = Close(file_number)
If read_error <> 0 OrElse close_error <> 0 Then End 1
If bytes_read <> byte_count Then End 1

If Deserialize(received, restored) = 0 Then
    Print "Deserialize failed: "; Err
    End 1
End If
Print "Title bytes: "; Len(restored.title)
Print "Score: "; restored.score

'' end of serialization.bas


Application record versions

The codec format version is separate from an application's record version. Changing a member name, type or order changes the schema, so decoding directly into a revised type fails. Keep a stable envelope with an application version and binary string payload. Decode the envelope, select the matching old or new wire record, then migrate explicitly.

This example writes a version 1 record and supplies the new version 2 field's default when reading it. Keep old wire definitions as long as old data must remain readable. Unknown application versions are rejected.

Example

'' examples/manual/proguide/serialization-version.bas
''
'' FreeBASIC Manual: Compiler-assisted serialization
'' Purpose: Read an older application record through a stable envelope.
'' Responsibilities: Version dispatch and explicit field migration.
'' This example intentionally contains no automatic schema conversion.
'' --------

#include Once "fbserialize.bi"

'' Keep this envelope definition stable across application versions.
Type Envelope
    version As ULong
    payload As String
End Type

Type PlayerV1
    player_name As String
    score As Long
End Type

Type PlayerV2
    player_name As String
    score As Long
    level As Long
End Type

Dim As PlayerV1 old_player
old_player.player_name = "Player One"
old_player.score = 42

Dim As Envelope outgoing
outgoing.version = 1
outgoing.payload = Serialize(old_player)
If Err <> 0 Then End 1
Dim As String packet = Serialize(outgoing)
If Err <> 0 Then End 1

Dim As Envelope incoming
If Deserialize(packet, incoming) = 0 Then End 1

'' Decode into the selected wire type first. The application supplies
'' defaults for added fields instead of asking the codec to guess them.
Dim As PlayerV2 current_player
Select Case incoming.version
Case 1
    Dim As PlayerV1 previous_player
    If Deserialize(incoming.payload, previous_player) = 0 Then End 1
    current_player.player_name = previous_player.player_name
    current_player.score = previous_player.score
    current_player.level = 1
Case 2
    If Deserialize(incoming.payload, current_player) = 0 Then End 1
Case Else
    Print "Unsupported application version: "; incoming.version
    End 1
End Select

Print current_player.player_name
Print "Score: "; current_player.score
Print "Level: "; current_player.level

'' end of serialization-version.bas


Dialect Differences

See also