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.