C source files connecting to the Ur/Web FFI should include urweb.h, and C++ source files should include urweb_cpp.h.
A server-side FFI type or value Module.ident must have a corresponding type or value definition uw_Module_ident in C code. With the current Ur/Web version, it's not generally possible to work with Ur records or complex datatypes in C code, but most other kinds of types are fair game.
The C FFI version of a Ur function with type T1 -> ... -> TN -> R or T1 -> ... -> TN -> transaction R has a C prototype like R uw_Module_ident(uw_context, T1, ..., TN). Only functions with types of the second form may have side effects. uw_context is the type of state that persists across handling a client request. Many functions that operate on contexts are prototyped in include/urweb/urweb_cpp.h. Most should only be used internally by the compiler. A few are useful in general FFI implementation:
void uw_error(uw_context, failure_kind, const char *fmt, ...);Abort the current request processing, giving a printf-style format string and arguments for generating an error message. The failure_kind argument can be FATAL, to abort the whole execution; BOUNDED_RETRY, to try processing the request again from the beginning, but failing if this happens too many times; or UNLIMITED_RETRY, to repeat processing, with no cap on how many times this can recur.
All pointers to the context-local heap (see description below of uw_malloc()) become invalid at the start and end of any execution of a main entry point function of an application. For example, if the request handler is restarted because of a uw_error() call with BOUNDED_RETRY or for any other reason, it is unsafe to access any local heap pointers that may have been stashed somewhere beforehand.
void uw_set_error_message(uw_context, const char *fmt, ...);This simpler form of uw_error() saves an error message without immediately aborting execution.
void uw_push_cleanup(uw_context, void (*func)(void *), void *arg); void uw_pop_cleanup(uw_context);Manipulate a stack of actions that should be taken if any kind of error condition arises. Calling the ``pop'' function both removes an action from the stack and executes it. It is a bug to let a page request handler finish successfully with unpopped cleanup actions.
Pending cleanup actions aren't intended to have any complex relationship amongst themselves, so, upon request handler abort, pending actions are executed in first-in-first-out order.
void *uw_malloc(uw_context, size_t);A version of malloc() that allocates memory inside a context's heap, which is managed with region allocation. Thus, there is no uw_free(), but you need to be careful not to keep ad-hoc C pointers to this area of memory. In general, uw_malloc()ed memory should only be used in ways compatible with the computation model of pure Ur. This means it is fine to allocate and return a value that could just as well have been built with core Ur code. In contrast, it is almost never safe to store uw_malloc()ed pointers in global variables, including when the storage happens implicitly by registering a callback that would take the pointer as an argument.
For performance and correctness reasons, it is usually preferable to use uw_malloc() instead of malloc(). The former manipulates a local heap that can be kept allocated across page requests, while the latter uses global data structures that may face contention during concurrent execution. However, we emphasize again that uw_malloc() should never be used to implement some logic that couldn't be implemented trivially by a constant-valued expression in Ur.
typedef void (*uw_callback)(void *); typedef void (*uw_callback_with_retry)(void *, int will_retry); int uw_register_transactional(uw_context, void *data, uw_callback commit, uw_callback rollback, uw_callback_with_retry free);All side effects in Ur/Web programs need to be compatible with transactions, such that any set of actions can be undone at any time. Thus, you should not perform actions with non-local side effects directly; instead, register handlers to be called when the current transaction is committed or rolled back. The arguments here give an arbitary piece of data to be passed to callbacks, a function to call on commit, a function to call on rollback, and a function to call afterward in either case to clean up any allocated resources. A rollback handler may be called after the associated commit handler has already been called, if some later part of the commit process fails. A free handler is told whether the runtime system expects to retry the current page request after rollback finishes. The return value of uw_register_transactional() is 0 on success and nonzero on failure (where failure currently only happens when exceeding configured limits on number of transactionals).
Any of the callbacks may be NULL. To accommodate some stubbornly non-transactional real-world actions like sending an e-mail message, Ur/Web treats NULL rollback callbacks specially. When a transaction commits, all commit actions that have non-NULL rollback actions are tried before any commit actions that have NULL rollback actions. Furthermore, an SQL COMMIT is also attempted in between the two phases, so the nicely transactional actions have a chance to influence whether data are committed to the database, while NULL-rollback actions only get run in the first place after committing data. The reason for all this is that it is expected that concurrency interactions will cause database commits to fail in benign ways that call for transaction restart. A truly non-undoable action should only be run after we are sure the database transaction will commit.
When a request handler ends with multiple pending transactional actions, their handlers are run in a first-in-last-out stack-like order, wherever the order would otherwise be ambiguous.
It is not safe for any of these handlers to access a context-local heap through a pointer returned previously by uw_malloc(), nor should any new calls to that function be made. Think of the context-local heap as meant for use by the Ur/Web code itself, while transactional handlers execute after the Ur/Web code has finished.
A handler may signal an error by calling uw_set_error_message(), but it is not safe to call uw_error() from a handler. Signaling an error in a commit handler will cause the runtime system to switch to aborting the transaction, immediately after the current commit handler returns.
void *uw_get_global(uw_context, char *name); void uw_set_global(uw_context, char *name, void *data, uw_callback free);Different FFI-based extensions may want to associate their own pieces of data with contexts. The global interface provides a way of doing that, where each extension must come up with its own unique key. The free argument to uw_set_global() explains how to deallocate the saved data. It is never safe to store uw_malloc()ed pointers in global variable slots.