Structs
When a struct type is not opaque, its fields are known and can be read and written directly, without using a library method.
To define a struct type, extend ammer.def.Struct<..., ...>
with a Haxe class. The first type parameter for ammer.def.Struct
should be a string identifying the native C type name. The second type parameter should be the ammer
library this type belongs to.
Struct definitions can contain variable fields, as well as instance methods.
Example: library datatype definition
class FoobarStruct extends ammer.def.Struct<"struct foobar_s", Foobar> { // ... }
In this example, FoobarStruct
is a struct in the Foobar
library. The C name for this struct is struct foobar_s
. Values of FoobarStruct
in Haxe represent instances of struct foobar_s*
(a pointer to struct foobar_s
).
Instances are pointers
Note that on the Haxe side, any struct value will be represented as a pointer to a struct. This is because most ammer
targets do not support arbitrarily large stack-allocated data. See passing structs directly for declaring APIs which do not use a pointer indirection.
Variables
Structs definitions can contain variables, declared as public var
or var
fields.
Example: struct variables
class FoobarStruct extends ammer.def.Struct<"struct foobar_s", Foobar> { public var bar:Int; }
In this example FoobarStruct
has a bar
variable that can be read or written:
var x:FoobarStruct = #dummy expr/*...*/; x.bar = 3; var y = x.bar;
Variables map to pointer accesses in C code, so a bar
variable is read as (someStruct)->bar
and written as (someStruct)->bar = value
. Note that any read or write variable access may have a runtime cost of a function call.
Allocation and deallocation
To make it possible to allocate and deallocate a struct, it must be marked with the @:ammer.alloc
metadata. When annotated, several functions are made available:
alloc
— a static function which allocates an instance of the given struct type. Initial values for its fields can optionally be passed using an object syntax.free
— an instance method which deallocates the underlying allocation.nullPtr
— a static function which returns a null pointer of the given struct type.
The name of the generated functions can be changed to avoid conflicts with other functions. @:ammer.alloc
is simply a convenience shortcut to the combination @:ammer.gen.alloc("alloc")
, @:ammer.gen.free("free")
, @:ammer.gen.nullPtr("nullPtr")
, where the string arguments specify the name of each generated method.
Example: allocating and deallocating a struct
Given a struct definition annotated with @:ammer.alloc
:
@:ammer.alloc class FoobarStruct extends ammer.def.Struct<"struct foobar_s", Foobar> { public var some_field:Int; }
It can be allocated by calling alloc
:
// with fields zeroed out: var x = FoobarStruct.alloc(); // or with some initial values: var x = FoobarStruct.alloc({ some_field: 42, });
It can then be deallocated:
x.free();
And a null pointer can be obtained:
var x = FoobarStruct.nullPtr();
Passing structs directly
Native methods which take a struct directly, as opposed to a pointer to a struct, can be declared by using the special ammer.ffi.Deref<...>
type. This dereferences the struct pointer just before the native method call.
Example: using ammer.ffi.Deref
class Foobar { public static function take_struct_ptr(x:FoobarStruct):Void; public static function take_struct_val(x:ammer.ffi.Deref<FoobarStruct>):Void; }
This example demonstrates passing a struct using a pointer and passing it directly. The corresponding C signatures could look like this:
void take_struct_ptr(struct foobar_s* x) { /*...*/ } void take_struct_val(struct foobar_s x) { /*...*/ }
Note that on the Haxe call side, the two methods are called the same way: by passing an instance of the FoobarStruct
type. The dereferencing, if any, happens transparently.
var x:FoobarStruct = #dummy expr/*...*/; Foobar.take_struct_ptr(x); Foobar.take_struct_val(x);
A similar situation arises when a native library method returns a struct value. To obtain a pointer to the struct, a heap allocation must take place to store that struct. In ammer
, return types can be wrapped with the special ammer.ffi.Alloc<...>
type to achieve this.
Example: using ammer.ffi.Alloc
class Foobar { public static function give_struct_ptr():FoobarStruct; public static function give_struct_val():ammer.ffi.Alloc<FoobarStruct>; }
This example demonstrates a native method returning a pointer to a struct and one returning a struct directly. The corresponding C signatures could look like this:
struct foobar_s* give_struct_ptr() { /*...*/ } struct foobar_s give_struct_val() { /*...*/ }
Note that on the Haxe call side, the two methods have the same return type: an instance of FoobarStruct
. The allocation, if any, happens transparently.
var x:FoobarStruct = Foobar.give_struct_ptr(); var y:FoobarStruct = Foobar.give_struct_val();
Linking
Structs should be linked with the parent library using the @:ammer.sub(...)
metadata to avoid compilation errors. See linking subdefinitions.