6.12.13 Implementing New Port Types in C

This section describes how to implement a new port type in C. Although ports support many operations, as a data structure they present an opaque interface to the user. To the port implementor, you have two pieces of information to work with: the port type, and the port’s “stream”. The port type is an opaque pointer allocated when defining your port type. It is your key into the port API, and it helps you identify which ports are actually yours. The “stream” is a pointer you control, and which you set when you create a port. Get a stream from a port using the SCM_STREAM macro. Note that your port methods are only ever called with ports of your type.

A port type is created by calling scm_make_port_type. Once you have your port type, you can create ports with scm_c_make_port, or scm_c_make_port_with_encoding.

Function: scm_t_port_type* scm_make_port_type (char *name, size_t (*read) (SCM port, SCM dst, size_t start, size_t count), size_t (*write) (SCM port, SCM src, size_t start, size_t count))

Define a new port type. The name, read and write parameters are initial values for those port type fields, as described below. The other fields are initialized with default values and can be changed later.

Function: SCM scm_c_make_port_with_encoding (scm_t_port_type *type, unsigned long mode_bits, SCM encoding, SCM conversion_strategy, scm_t_bits stream)
Function: SCM scm_c_make_port (scm_t_port_type *type, unsigned long mode_bits, scm_t_bits stream)

Make a port with the given type. The stream indicates the private data associated with the port, which your port implementation may later retrieve with SCM_STREAM. The mode bits should include one or more of the flags SCM_RDNG or SCM_WRTNG, indicating that the port is an input and/or an output port, respectively. The mode bits may also include SCM_BUF0 or SCM_BUFLINE, indicating that the port should be unbuffered or line-buffered, respectively. The default is that the port will be block-buffered. See Buffering.

As you would imagine, encoding and conversion_strategy specify the port’s initial textual encoding and conversion strategy. Both are symbols. scm_c_make_port is the same as scm_c_make_port_with_encoding, except it uses the default port encoding and conversion strategy.

The port type has a number of associate procedures and properties which collectively implement the port’s behavior. Creating a new port type mostly involves writing these procedures.

name

A pointer to a NUL terminated string: the name of the port type. This property is initialized via the first argument to scm_make_port_type.

read

A port’s read implementation fills read buffers. It should copy bytes to the supplied bytevector dst, starting at offset start and continuing for count bytes, returning the number of bytes read.

write

A port’s write implementation flushes write buffers to the mutable store. It should write out bytes from the supplied bytevector src, starting at offset start and continuing for count bytes, and return the number of bytes that were written.

read_wait_fd
write_wait_fd

If a port’s read or write function returns (size_t) -1, that indicates that reading or writing would block. In that case to preserve the illusion of a blocking read or write operation, Guile’s C port run-time will poll on the file descriptor returned by either the port’s read_wait_fd or write_wait_fd function. Set using

Function: void scm_set_port_read_wait_fd (scm_t_port_type *type, int (*wait_fd) (SCM port))
Function: void scm_set_port_write_wait_fd (scm_t_port_type *type, int (*wait_fd) (SCM port))

Only a port type which implements the read_wait_fd or write_wait_fd port methods can usefully return (size_t) -1 from a read or write function. See Non-Blocking I/O, for more on non-blocking I/O in Guile.

print

Called when write is called on the port, to print a port description. For example, for a file port it may produce something like: #<input: /etc/passwd 3>. Set using

Function: void scm_set_port_print (scm_t_port_type *type, int (*print) (SCM port, SCM dest_port, scm_print_state *pstate))

The first argument port is the port being printed, the second argument dest_port is where its description should go.

close

Called when the port is closed. It should free any resources used by the port. Set using

Function: void scm_set_port_close (scm_t_port_type *type, void (*close) (SCM port))

By default, ports that are garbage collected just go away without closing. If your port type needs to release some external resource like a file descriptor, or needs to make sure that its internal buffers are flushed even if the port is collected while it was open, then mark the port type as needing a close on GC.

Function: void scm_set_port_needs_close_on_gc (scm_t_port_type *type, int needs_close_p)
seek

Set the current position of the port. Guile will flush read and/or write buffers before seeking, as appropriate.

Function: void scm_set_port_seek (scm_t_port_type *type, scm_t_off (*seek) (SCM port, scm_t_off offset, int whence))
truncate

Truncate the port data to be specified length. Guile will flush buffers before hand, as appropriate. Set using

Function: void scm_set_port_truncate (scm_t_port_type *type, void (*truncate) (SCM port, scm_t_off length))
random_access_p

Determine whether this port is a random-access port.

Seeking on a random-access port with buffered input, or switching to writing after reading, will cause the buffered input to be discarded and Guile will seek the port back the buffered number of bytes. Likewise seeking on a random-access port with buffered output, or switching to reading after writing, will flush pending bytes with a call to the write procedure. See Buffering.

Indicate to Guile that your port needs this behavior by returning a nonzero value from your random_access_p function. The default implementation of this function returns nonzero if the port type supplies a seek implementation.

Function: void scm_set_port_random_access_p (scm_t_port_type *type, int (*random_access_p) (SCM port));
get_natural_buffer_sizes

Guile will internally attach buffers to ports. An input port always has a read buffer and an output port always has a write buffer. See Buffering. A port buffer consists of a bytevector, along with some cursors into that bytevector denoting where to get and put data.

Port implementations generally don’t have to be concerned with buffering: a port type’s read or write function will receive the buffer’s bytevector as an argument, along with an offset and a length into that bytevector, and should then either fill or empty that bytevector. However in some cases, port implementations may be able to provide an appropriate default buffer size to Guile.

Function: void scm_set_port_get_natural_buffer_sizes (scm_t_port_type *type, void (*get_natural_buffer_sizes) (SCM, size_t *read_buf_size, size_t *write_buf_size))

Fill in read_buf_size and write_buf_size with an appropriate buffer size for this port, if one is known.

File ports implement a get_natural_buffer_sizes to let the operating system inform Guile about the appropriate buffer sizes for the particular file opened by the port.

Note that calls to all of these methods can proceed in parallel and concurrently and from any thread up until the point that the port is closed. The call to close will happen when no other method is running, and no method will be called after the close method is called. If your port implementation needs mutual exclusion to prevent concurrency, it is responsible for locking appropriately.