Skip to content

C Interoperability

Vihan edited this page Jun 9, 2019 · 6 revisions

VSL is fully interoperable with C functions. VSL doesn't use function-prototypes instead, you can declare a function 'external', as in having its body externally defined. Ensure that parameters and return type are correct (even if some parameters are not used) as you may encounter runtime errors otherwise.

func puts(cstring: CString) external(puts);

Pointers

Note: Pointers are very unsafe. Any error thrown by pointer faults (e.g. SIGSEGV) cannot be gracefully handled by VSL. For these errors your best bet is to use LLDB to identify which VSL frame throws the error but do note with inlining and function splitting this might be difficult unless optimization is disabled. If the error disappears when optimization disabled, it is likely a concurrency issue.

Pointers are a special class which are not objects and seamlessly compile to C-pointers. This said, they do use generic-syntax, specifically to specify a pointer of a type T, you use Pointer<T>. An example of a pointer to an integer:

let intPtr: Pointer<Int>

The Pointer class can be subclassed however it does not support dynamic dispatch by design. To obtain a pointer to an existing value, Pointer offers a constructor, Pointer<T>(to: T):

let intPtr = Pointer<Int>(to: 1)

What init(to:) will do is heap-allocate the memory for a single Int and then will copy 1 to the newly allocated portion of memory. If the parameter is a non-heap allocated struct then the whole struct would be copied. This behavior ensures init(to:) will never error. You may also use init(toUnsafe:) this will error when obtaining the pointer to a stack-allocated or register value however it therefore also lacks the overhead.

Using init(to:) with an object will generate a pointer to the object which is already a pointer:

let animalPtr = Pointer<Animal>(to: animal)
animal === Animal::animalPtr.dereference()

Additionally there is an init(of:) which will interpret the pointer value of a given value. An example:

let animalPtr = Pointer<Animal>(of: animal)
assert(animal === Animal::animalPtr) // true

Do note that if you do something such as Pointer<Int>(of: 0) this will essentially be the null pointer, doing Pointer<Int>(of: 0).dereference() would generally result in a segmentation fault.


Pointers are unsafe and you must explicitly manage the memory of the contained objects. Additionally, if you used an externally managed reference, be aware that VSL may clean the referenced section of memory. To avoid, this, you may want to unqualified the variable from automatic memory management:

@unmanaged let myAnimal = Animal()
let int = Pointer<Animal>(to: 1)

In the above case the @unmanaged annotation is redundant however it may be necessary in more complex cases. Pointers do not qualify for automatic memory management.

Managing Memory Allocations

You can manage memory allocations using Pointer<T>(alloc: Int) and Pointer<T>#free(). Generally these functions will use the system malloc/free.

Tip: If you wish to interact with VSL types from a C-library see the vsl/native.h subsection

To allocate a sequence of memory use:

Pointer<T>(alloc count: UInt)

The alloc count parameter determines the amount of consecutive spots in memory of size T should be allocated. Roughly equivalent to the C call:

(T*) malloc(sizeof(T) * count)

To free the returned reference, use:

myPointer.free()

Warning: If myPointer does not refer to a section of memory allocated using VSL, expect runtime errors. Managing memory is an unsafe operation and VSL assumes you know what you are doing when using pointer types.

Byte Sequences

In C you often denote byte sequences using char* (for strings). VSL has a byte-sequence literal. All characters inside the byte-sequence literal are literally inserted inside the binary and you can obtain a pointer to this sequence. The length is not stored and you must manually specify a null-terminator if appropriate. The syntax for a byte-sequence is:

puts(`Hello, World!\x0A\x00`)

\x00 is an example of the two hex-digit escape codes you can use. Hex escape values must be between 0 and FF inclusive otherwise the sequence will be literally inserted. It may initially be thought that Pointer<UInt8> is the correct data type for the byte sequence however the Pointer type is semantically unrelated to the byte-sequence. A byte-sequence literal is of type ByteSequence. This class has no properties or methods. To interact with the byte-sequence, cast it to a Pointer using bit-casts:

let byteSequence: ByteSequence = `Hello, World!\x0A\x00`
let pointer = Pointer<UInt8>::byteSequence
print("First character byte sequence is #{pointer.dereference()}")

There is no difference if an external C-function is declared with Pointer<UInt8> or ByteSequence however these will not be implicitly converted. It is recommended that external functions accepting strings have ByteSequence used as the param type.