Basic Concepts

C API

UAW’s programming interface is written in C. This does not mean that you have to program your extension in C – quite the opposite! C is compatible with many other languages. This design has been used successfully with C# and Python.

The API is contained in a single C header named UAW API.h. It does not require linking a specific library.

RationaleThis enables extensions to use the API without using any C or C++. For creating bindings with other languages, type information from the API header should be sufficient.

Callbacks

UAW Extensions are based on callbacks: Extensions provide a list of functions they implement. UAW calls into them when required.

Callbacks use the calling convention that is native to the operating system. This is not necessarily the C calling convention – on Windows, it is stdcall.

RationaleInteroperability – not every language supports C’s calling convention, but they all support calling into the OS.

Calling convention errors can lead to subtle errors like stack corruption that can be hard to find. Check out the UAW_CALL macro!

Callbacks must not throw exceptions or unwind the stack. If you use an exception-based language, make sure that your callbacks handle all possible exceptions internally. In C, do not use setjmp() or longjmp().

RationaleInteroperability – very few languages support stack unwinding. Extensions have no way to decide whether their caller understands exceptions.

Context-Dependent Interface

Unlike common C APIs like the Linux kernel, UAW does not provide a set of fixed functions that callbacks can use. Instead, every callback receives one interface that is context-dependent – tailored specifically to the thing that callback should do.

RationaleMakes it impossible to do frowned-upon things like opening a file during rendering: The rendering callback will not be provided with an interface for opening files; only with one for rendering-related stuff.
RationaleReduces race conditions because there are no global functions (see Threading below).

This interface is usually passed as the first parameter to every callback. It is only valid during this very callback. Extensions must not store it.

RationaleThis would nullify all of the above! Luckily, this usually crashes fast.

The interface mostly conforms to the binary interface of the Component Object Model, whose main advantage is that it can be used from many languages like C, C++, C#, Visual Basic and even JavaScript.

Unlike COM objects, it does not provide reference-counting or lifetime management because the interface is only ever valid for the duration of the callback.

In order to call function Foo(int) of interface inter with 123 for parameter, you’d do in C:

void MyCallback(struct Inter * inter) {
	inter->methods->foo(inter, 123);
}

For C++, UAW usually provides a helper that allows you to write:

void MyCallback(Inter * inter) {
	inter.foo(123);
}

For other languages, check their interoperability with COM methods.

Passing Data

You often have to pass data structures to the API. Special care must be taken if your language is not C/C++.

Garbage Collection

If your language is garbage-collected or runs compaction during execution, you must carefully disable these whenever you pass data while calling into UAW.

This is usually achieved by pinning objects in memory before passing them in.

Be sure to not only pin data in memory, but also your callback functions!

Finding this kind of error can be extremely tedious!

For details, refer to the documentation of your programming language.

Alignment

UAW assumes natural alignment on data structure members (i.e. no byte packing). The structures should be designed in a way that makes them immune to alignment settings, but you never know.

Strings

UAW Extensions rarely use strings directly. Extensions assign unique identifiers to strings – string IDs – and pass them to UAW. Furthermore, they define a callback function that translates a string ID to an actual string. UAW will call this function just before displaying the string.

RationaleThis frees extensions from complicated string lifetime management.
RationaleRequired for localization. User interface language may change after extensions have loaded, i.e. after they reported their plane names and level names to UAW. Were strings used directly, all of this work would have to be repeated.
RationaleSome frameworks handle localization via string ID anyway (most notably Win32 string resources).

UAW uses the string encoding native to the Operating system.

The native string encoding of Windows is UTF-16. This is available as wchar_t in C, and as char16_t in C++, with the L"" and u"" literals, respectively.

UAW_Extension_StringID is the type of string IDs, and UAW_Char maps to the C/C++ type associated with the native string encoding.

String IDs need not be unique across different extensions, but they must be unique within one extension.

This is a sample implementation of the string conversion function in C++. It relies on enum and switch, but really you can use any system you like:

#include <UAW API.h>

enum StringID : UAW_Extension_StringID {
	extensionName,
	extensionAuthor,
	extensionUrl,
	extensionDescription,
};

// Returns a string in UAW’s current language.
UAW_Char const * UAW_CALL stringFromStringID(
	UAW_Extension const *  extension, // explained later
	UAW_Extension_StringID id
) {
	switch(id) {
		case extensionName:
			return u"Sample Extension";
		case extensionAuthor:
			return u"Someone";
		case extensionUrl:
			return u"http://example.com";
		case extensionDescription:
			return u"Demonstrates the implementation of a simple UAW extension.";
		default:
			return u"YOU FORGOT TO DEFINE THIS STRING!";
	}
}

Threading

Except where noted otherwise, UAW’s programming interface is free-threaded. This means:

This reflects a modern programming style (known from languages such as C# and JavaScript) and allows to use multi-core CPUs efficiently.

Avoid using global variables except for read-only purposes!

If your extension uses third-party libraries, additional restrictions on threading may apply. Check the documentation!