Skip to content
Shixin Zeng edited this page Aug 11, 2015 · 8 revisions

Table of Contents

Struct! in R3

Struct! defines how data is mapped to C from rebol. The design is aimed to address all limitations listed [here](http://www.rebol.net/wiki/DLL_Interface) and subject to change.

Creation

Data types supported by struct! include fixed-size integers (uint8, int8, uint16, int16, uint32, int32, uint64, int64), floating point numbers, pointer, and nested struct!. Only fixed-size integers are supported because of variation in the sizes of integers in compilers.

The format is:

 
 struct-block: "[" opt attr-block any field "]"
 attr-block: "[" any attr "]"
 attr: attr-name ":" attr-value
 attr-name: ["raw-memory" | "raw-size"]
 attr-value: integer! ;for now
 field: [ field-uninitilized | field-initilized]
 field-uninitilized: word! "[" type "]"
 field-initialized: set-word! "[" type "]" initialization
 type: [atomic-type | ["struct!" struct-block]]  opt dimention 
 dimension: "[" integer "]"
 atomic-type: [uint8 | int8 | uint16 | int16 | uint32 | int32 | uint64 | int64 | float | double | pointer | rebval]
 

Here are some examples

 
 a: make struct! [a: [int32] 1]
 b: make struct! [b: [pointer] 0]
 c: make struct! [a: [int8] 1 sa [struct! [a: [int8] 0]]]
 

Member Initialization

As shown in the examples above, fields in the struct can be followed by the initialization. When the field name is of type set-word!, the initialization is expected, which could be any rebol statement. When the field name is of type word!, the field will be initialized to default values (0 for atomic types, or a copy of struct! that is used as type).

 
 a: make struct! [a [int32]] ;a/a is 0
 b: make struct! compose/deep [sa [(a)]] ; sa will be same as "a"
 

Nested struct!

In Rebol 2, all nested structs are converted to pointers, which doesn't match C semantics. So I changed this to match what C does.

 
 a: make struct! [a [int32]] ;
 b: make struct! compose/deep [sa [(a)] b [int8]] ; b will be same as "make struct! [int32 a int8 b]"
 c: make struct! [struct! [a [int32] b [int8]] ; c will have the same C representation as b.
 

Fixed-Size Array Members

Arrays are also allowed in struct!, which is a new feature, compared with R2.

 
 a: make struct! [a [int32 [10]]] ; a/a will be an array of 10 32-bit integers, initialized to all zeros.
 b: make struct! [a: [int8 [2]] [1 2]] ; b/a will be initilized to [1 2]
 

Arrays can also be initialized from an integer!, which is interpreted as a pointer to a C array.

 
 a: make struct! [a: [int8 [10]] 123456] ;123456 is a memory location that holds 10 8-bit integers, the address has to be valid, or the interpreter could crash
 

This can be used to retrieve the memory content, and wrap the following C struct, which is quite common in C:

 
 struct a {
  uint32_t len;
  int32_t *data; //the length is specified in "len" 
 };
 

In rebol, this can be done in two steps:

1. get the length and pointer

 
 a: make struct! [
  len [uint32]
  data [pointer];
 ]
 

2. get the data block

 
 b: make struct! compose/deep [data: [uint32 [(a/len)]] a/data]
 ; b/data becomes a block with "a/len" 32-bit integers
 

Pointers

As pointers are ubiquitous in C, we better support it natively. The size of pointers will be 4 or 8 bytes, depending on the platform, which maps exactly to C. Examples:

 
 a: make struct! [a: [pointer] 0] ; sizeof a will be 4 bytes on 32-bit systems, and 8 on 64-bit systems;
 

Memory Alignment

All fields in the struct! will densely stored, which means no padding in between. The Rebol programmer has to put in proper padding when neccessary. For example, for the C struct

 
 struct a {
  int8_t i;
  int32_t j; /* will be aligned at boundary of 4, unless specified otherwise */
 };
 

The correpsonding rebol struct needs to be:

 
 make struct! [
    i [int8]
    padding [int8 [3]] ; unused
    j [int32]
 ]
 

Memory Access

In some cases, direct memory access is required, e.g. as an argument to the callback function, and the callback might be required to update the argument. This is handled by using struct attributes "raw-memory", whose value is the memory location, and access to the struct will have same effects as accessing the memory directly (changing struct members will change memory content). Examples:

 
    a: memory-location ;has to be an valid memory location, or interpreter could crash.
    i: make struct! compose/deep [
        [raw-memory: (a)]
        i [int32]
    ]

When "raw-memory" attribute is specified, no initialization is allowed. The intention is that when the struct is first created, it should reflect the current memory content, without changing it. Modification to the memory should be done later on by member access or "change" action.

Another attribute is "raw-size" with specify the size of the memory for this struct, this works as a safeguard. When it's specified, the size of the struct will be checked against this size, if they don't match, an error will be thrown.

 
 i: make struct! compose/deep [
  [
   raw-memory: (a)
   raw-size: 4 ; OK
   ;raw-size: 1 ; NOT OK, because it doesn't match the size of struct
  ]
  i [int32]
 ]
 

Member Access

Member access in struct is like in an object!.

 
 >> a: make struct! [a: [uint8] 1]
 >> a/a
 == 1
 >> a: make struct! [a: [uint8 [2]] [1 2]]
 >> a/a
 == [1 2]
 

They can also be modified by:

 
 >> a: make struct! [a: [uint8 [2]] [1 2]]
 >> a/a: [3 4]
 >> a/a
 == [3 4]
 >> a/a/1: 10
 >> a/a
 == [10 4]
 

Actions on struct!

  • make/to struct! spec
 
 >> a: to struct! [a: [uint8 [2]] [1 2]]   
 >> mold a
 == "make struct! [a: [uint8 [2]] [1 2]]"
 
  • reflect sturct! word
 
 >> a: make struct! [a: [uint8 [2]] [1 2]]
 >> spec-of a
 == [a: [uint8 [2]] [1 2]]
 >> values-of a; the binary representation of the struct content
 == #{0102}
 
  • length? stuct!, the length of the binary content
 
 >> length? a ; same as length? values-of a
 == 2
 
  • change struct!, change the binary content of the struct
 
 >> change a #{0304}
 >> mold a
 == "make struct! [a: [uint8 [2]] [3 4]]"
 

Type rebval in struct!

This type is quite useful to pass rebol values to a callback functions. It's quite common in C to have this kind of functions:

 
 void foo(void (*bar)(void *data), void *user_data);
 

Inside foo, it does:

 
 bar(user_data);
 

Their rebol wrappers are:

 foo: make routine! compose [[
 	bar 		[pointer]
 	user-data 	[pointer]
 	return: 	[void]
 ] (lib) "foo"]
 
 bar: func [
 	data [integer!]
 ][
 ]
 
 bar-cb: make callback! reduce [[
 	data [pointer]
 	return: [void]
 ] :bar]
 

So far, everything looks fine. What's not so fine is when foo is called and some user-data needs to be passed to bar. Because user-data is a pointer, it can only be an integer, or result from "addr-of struct!". Passing in an integer is trivial, and the usage in "bar" is simple. Most situations requires passing in complex data structure, which is the result form "add-of". However, as struct! is designed to solely work with C datatypes, it only supports basic atomic types or another "struct!", which is not quite convienent to work with, especially when it involves dynamic memory management.

Say you want "bar" to put some data into an array, and the length of data is unknown before hand. Manual calls to "malloc" or "free" might be required, which makes "bar" not really like a REBOL function anymore:

 
 foobar: make struct! [
 	len [uint32] ;
 	addr [pointer] ;since the length is unknown, it gotta be a pointer
 ]
 foo addr-of foobar
 

and:

 
 bar: func [
 	data [integer!]
 	/local foobar
 ][
 	foobar: make struct! compose/deep [
 		[raw-memory: (data)]
 		len [uint32]
 		addr [pointer]
 	]
 	;foobar/addr: malloc ...
 	;free foobar/addr
 ]
 

This is where "REBVAL" comes to rescue.

The usage is quite simple, just use it inside struct! as any other atomic datatype:

 foobar: make struct! [
 	v [rebval]
 ]
 ;foobar/v can be any rebol value, not just those with atomic C types
 
 foobar/v: copy []
 ;it can also be any of these:
 ;foobar/v: "hello"
 ;foobar/v: make object! [i: 0]
 

and it can be passed to foo:

 
 foo addr-of foobar
 

inside bar:

 
 bar: func [
 	data [integer!]
 	/local foobar
 ][
     	foobar: make struct! compose/deep [
 		[raw-memory: (data)]
 	    	v [rebval]
     	]
 	;now foobar/v will be whatever is set before "foo" is being called.
 	;and you can handle it as any other rebol values:
 	; object? foobar/v
 	append foobar/v "hello"
 ]
 

and after the "foo" call, foobar/v becomes ["hello"], no manual memory management at all.