Jsctypes/api: Difference between revisions

From MozillaWiki
Jump to navigation Jump to search
(Created page with 'API for types, structs, and pointers. 1. opening a library and declaring a function. Cu.import("ctypes"); // imports the global ctypes object // searches the path and opens "l…')
 
No edit summary
Line 1: Line 1:
API for types, structs, and pointers.
== jsctypes: API for types, structs, and pointers ==


1. opening a library and declaring a function.
==== 1. opening a library and declaring a function ====
 
<pre>Cu.import("ctypes"); // imports the global ctypes object
Cu.import("ctypes"); // imports the global ctypes object


// searches the path and opens "libctypes.so" on linux,
// searches the path and opens "libctypes.so" on linux,
Line 13: Line 12:
let myfunc = mylib.declare("myfunc", DEFAULT_ABI, Int32(), Int32());
let myfunc = mylib.declare("myfunc", DEFAULT_ABI, Int32(), Int32());


let ret = myfunc(2); // calls myfunc
let ret = myfunc(2); // calls myfunc</pre>
 
Note that for simple types (integers and strings), we will autoconvert the argument at call time - there's no need to pass in an Int32 object. The consumer should never need to instantiate such an object explicitly, unless they're using it to back a pointer - in which case we require explicit, strong typing. See later for examples.
Note that for simple types (integers and strings), we will autoconvert the argument at call time - there's no need to pass in an Int32 object. The consumer should never need to instantiate such an object explicitly, unless they're using it to back a pointer - in which case we require explicit, strong typing. See later for examples.


2. declaring and passing a simple type (by object)
==== 2. declaring and passing a simple type (by object) ====
 
<pre>let myfunc = mylib.declare("myfunc", DEFAULT, Int32, Int32);
let myfunc = mylib.declare("myfunc", DEFAULT, Int32, Int32);
let i = new Int32(); // instantiates an Int32 object with default value 0
let i = new Int32(); // instantiates an Int32 object with default value 0
let ret = myfunc(i);
let ret = myfunc(i);</pre>
 
An Int32 object, like all other type objects in ctypes, can be used for type specification when passed as an object, as above. declare() can look at the prototype JSObject* of its argument, and use this as a canonical JSObject representing the type, a pointer to which can be used for simple type equality comparisons. (This will work for user-defined types such as structs also - see later - though for pointer types we need to dig down to the underlying type.)
An Int32 object, like all other type objects in ctypes, can be used for type specification when passed as an object, as above. declare() can look at the prototype JSObject* of its argument, and use this as a canonical JSObject representing the type, a pointer to which can be used for simple type equality comparisons. (This will work for user-defined types such as structs also - see later - though for pointer types we need to dig down to the underlying type.)


Int32() can have two modes depending on whether JS_IsConstructing(cx) is JS_TRUE ("new Int32()") or JS_FALSE ("Int32()"). Used as a function, we could perform a type conversion with range checking, for instance:
Int32() can have two modes depending on whether JS_IsConstructing(cx) is JS_TRUE ("new Int32()") or JS_FALSE ("Int32()"). Used as a function, we could perform a type conversion with range checking, for instance:
 
<pre>let n = Int32(4); // JSVAL_IS_INT(n) == JS_TRUE
let n = Int32(4); // JSVAL_IS_INT(n) == JS_TRUE
n = Int32(4e16); // RangeError - out of bounds
n = Int32(4e16); // RangeError - out of bounds
n = Int32.max; // 2^31 - 1
n = Int32.max; // 2^31 - 1
// etc
// etc</pre>
 
For the new constructor, the resulting object stores three pieces of information internally in reserved slots. |new Int32()| creates a JSObject which allocates sizeof(int32_t) and stores that pointer in a private slot. It also stores its type, as a JSObject* pointing to the canonical Int32 prototype, and can store a parent JSObject* in case it refers to an Int32 that happens to be part of another object. Thus the slot layout of i above would be
For the new constructor, the resulting object stores three pieces of information internally in reserved slots. |new Int32()| creates a JSObject which allocates sizeof(int32_t) and stores that pointer in a private slot. It also stores its type, as a JSObject* pointing to the canonical Int32 prototype, and can store a parent JSObject* in case it refers to an Int32 that happens to be part of another object. Thus the slot layout of i above would be


i object:
i object:<br>&nbsp; slot 1 (parent): JSObject* -&gt; NULL (no parent object)<br>&nbsp; slot 2 (type) : JSObject* -&gt; Int32 prototype<br>&nbsp; slot 3 (value) : void* -&gt; binary blob from malloc(sizeof(int32_t))
  slot 1 (parent): JSObject* -> NULL (no parent object)
  slot 2 (type) : JSObject* -> Int32 prototype
  slot 3 (value) : void* -> binary blob from malloc(sizeof(int32_t))


Do we need to provide an explicit set() method, to allow for efficient modification? For instance,
Do we need to provide an explicit set() method, to allow for efficient modification? For instance,
 
<pre>i.set(5); // cheaper than i = new Int32(5);</pre>
i.set(5); // cheaper than i = new Int32(5);
==== 3. declaring and passing a pointer ====
 
<pre>// C prototype: int32_t myfunc(int32_t* p)
3. declaring and passing a pointer
 
// C prototype: int32_t myfunc(int32_t* p)
let myfunc = mylib.declare("myfunc", DEFAULT_ABI, Int32, Pointer(Int32));
let myfunc = mylib.declare("myfunc", DEFAULT_ABI, Int32, Pointer(Int32));
let p = new Pointer(new Int32()); // instantiates an int and a pointer
let p = new Pointer(new Int32()); // instantiates an int and a pointer
Line 63: Line 51:
// other examples
// other examples
let q = new Pointer(); // instantiate a null pointer to a void type
let q = new Pointer(); // instantiate a null pointer to a void type
q = new Pointer(5); // TypeError - require a ctypes type
q = new Pointer(5); // TypeError - require a ctypes type</pre>
 
Internally, a pointer requires a backing object (unless it's a null pointer). In the examples, the Pointer JSObject holds a reference to the Int32 JSObject for rooting purposes, and is laid out similarly to an Int32 object:
Internally, a pointer requires a backing object (unless it's a null pointer). In the examples, the Pointer JSObject holds a reference to the Int32 JSObject for rooting purposes, and is laid out similarly to an Int32 object:


p object:
p object:<br>&nbsp; slot 1 (parent): JSObject* -&gt; Int32 backing object<br>&nbsp; slot 2 (type) : JSObject* -&gt; Pointer prototype<br>&nbsp; slot 3 (value) : void* -&gt; pointer to binary int32_t blob inside backing object
  slot 1 (parent): JSObject* -> Int32 backing object
  slot 2 (type) : JSObject* -> Pointer prototype
  slot 3 (value) : void* -> pointer to binary int32_t blob inside backing object
 
3. declaring a pointer to opaque struct


const FILE = ctypes.Struct(); // creates a Struct() type with no allocated binary storage, and no fields to access
==== 4. declaring a pointer to opaque struct ====
<pre>const FILE = ctypes.Struct(); // creates a Struct() type with no allocated binary storage, and no fields to access
let fopen = mylib.declare("fopen", DEFAULT_ABI, Pointer(FILE), String);
let fopen = mylib.declare("fopen", DEFAULT_ABI, Pointer(FILE), String);
let file = fopen("foo"); // creates a new Pointer() object
let file = fopen("foo"); // creates a new Pointer() object
file.contents(); // will throw - type is unknown
file.contents(); // will throw - type is unknown
file.address(); // ok
file.address(); // ok</pre>
 
==== 5. declaring a struct ====
3. declaring a struct
<pre>// C prototype: struct s_t { int32_t a; int64_t b };
 
// C prototype: struct s_t { int32_t a; int64_t b };
const s_t = Struct([{ a: Int32 }, { b: Int64 }]);
const s_t = Struct([{ a: Int32 }, { b: Int64 }]);
let myfunc = mylib.declare("myfunc", DEFAULT_ABI, Int32, s_t);
let myfunc = mylib.declare("myfunc", DEFAULT_ABI, Int32, s_t);


let s = new s_t(10, 20);
let s = new s_t(10, 20);</pre>
 
This creates an s_t object which allocates binary space for both fields, creates getters and setters to access the binary fields via their offset, assigns the values 10 and 20 to the fields, and whose prototype is s_t:
This creates an s_t object which allocates binary space for both fields, creates getters and setters to access the binary fields via their offset, assigns the values 10 and 20 to the fields, and whose prototype is s_t:


s object:
s object:<br>&nbsp; slot 1 (parent): JSObject* -&gt; NULL<br>&nbsp; slot 2 (type) : JSObject* -&gt; s_t prototype<br>&nbsp; slot 3 (value) : void* -&gt; pointer to binary blob from malloc()<br>&nbsp; slot 4 (fields): array of data for each field:<br>&nbsp;&nbsp;&nbsp; { JSObject* parent; JSObject* type; ptrdiff_t offset; }
  slot 1 (parent): JSObject* -> NULL
  slot 2 (type) : JSObject* -> s_t prototype
  slot 3 (value) : void* -> pointer to binary blob from malloc()
  slot 4 (fields): array of data for each field:
                  { JSObject* parent; JSObject* type; ptrdiff_t offset; }


The array of field information allows each field to be dependent on another JSObject (only for the case where the field is a pointer), have an associated type, and have an offset into the binary blob for ease of access.
The array of field information allows each field to be dependent on another JSObject (only for the case where the field is a pointer), have an associated type, and have an offset into the binary blob for ease of access.
 
<pre>let c = s.b; // invokes the getter for |b| to create an Int64 object like so:</pre>
let c = s.b; // invokes the getter for |b| to create an Int64 object like so:
c object:<br>&nbsp; slot 1 (parent): JSObject* -&gt; s backing object<br>&nbsp; slot 2 (type) : JSObject* -&gt; Int64 prototype<br>&nbsp; slot 3 (value) : void* -&gt; pointer to binary int64_t blob inside backing object
 
<pre>let i = myfunc(s); // checks the type of s by JSObject* prototype equality</pre>
c object:
==== 6. pointers to struct fields ====
  slot 1 (parent): JSObject* -> s backing object
<pre>let p = new Pointer(s.b);</pre>
  slot 2 (type) : JSObject* -> Int64 prototype
  slot 3 (value) : void* -> pointer to binary int64_t blob inside backing object
 
let i = myfunc(s); // checks the type of s by JSObject* prototype equality
 
4. pointers to struct fields
 
let p = new Pointer(s.b);
 
Once the Int64 representing s.b is constructed, the Pointer object references it directly:
Once the Int64 representing s.b is constructed, the Pointer object references it directly:


p object:
p object:<br>&nbsp; slot 1 (parent): JSObject* -&gt; Int64 backing object (which, in turn, is backed by s)<br>&nbsp; slot 2 (type) : JSObject* -&gt; Pointer prototype<br>&nbsp; slot 3 (value) : void* -&gt; pointer to binary int64_t blob inside backing object
  slot 1 (parent): JSObject* -> Int64 backing object (which, in turn, is backed by s)
  slot 2 (type) : JSObject* -> Pointer prototype
  slot 3 (value) : void* -> pointer to binary int64_t blob inside backing object


5. nested structs
==== 7. nested structs ====
 
<pre>const u_t = Struct([{ x: Int64 }, { y: s_t }]);
const u_t = Struct([{ x: Int64 }, { y: s_t }]);
let u = new u_t(5e4, s); // copies data from s into u.y - no references
let u = new u_t(5e4, s); // copies data from s into u.y - no references


Line 127: Line 89:


const v_t = Struct([{ x: Pointer(s_t) }, { y: Pointer(s_t) }]);
const v_t = Struct([{ x: Pointer(s_t) }, { y: Pointer(s_t) }]);
let v = new v_t(new Pointer(s), new Pointer(s));
let v = new v_t(new Pointer(s), new Pointer(s));</pre>
 
In this case, the fields array will each have their respective Pointer as the parent object, and both will point to the s binary blob.<br>
In this case, the fields array will each have their respective Pointer as the parent object, and both will point to the s binary blob.

Revision as of 23:31, 22 September 2009

jsctypes: API for types, structs, and pointers

1. opening a library and declaring a function

Cu.import("ctypes"); // imports the global ctypes object

// searches the path and opens "libctypes.so" on linux,
// "libctypes.dylib" on mac, and "ctypes.dll" on windows
let mylib = ctypes.open("mylib", ctypes.SEARCH);

// declares the C prototype int32_t myfunc(int32_t)
// Int32 implies ctypes.Int32, shortened for brevity
let myfunc = mylib.declare("myfunc", DEFAULT_ABI, Int32(), Int32());

let ret = myfunc(2); // calls myfunc

Note that for simple types (integers and strings), we will autoconvert the argument at call time - there's no need to pass in an Int32 object. The consumer should never need to instantiate such an object explicitly, unless they're using it to back a pointer - in which case we require explicit, strong typing. See later for examples.

2. declaring and passing a simple type (by object)

let myfunc = mylib.declare("myfunc", DEFAULT, Int32, Int32);
let i = new Int32(); // instantiates an Int32 object with default value 0
let ret = myfunc(i);

An Int32 object, like all other type objects in ctypes, can be used for type specification when passed as an object, as above. declare() can look at the prototype JSObject* of its argument, and use this as a canonical JSObject representing the type, a pointer to which can be used for simple type equality comparisons. (This will work for user-defined types such as structs also - see later - though for pointer types we need to dig down to the underlying type.)

Int32() can have two modes depending on whether JS_IsConstructing(cx) is JS_TRUE ("new Int32()") or JS_FALSE ("Int32()"). Used as a function, we could perform a type conversion with range checking, for instance:

let n = Int32(4); // JSVAL_IS_INT(n) == JS_TRUE
n = Int32(4e16); // RangeError - out of bounds
n = Int32.max; // 2^31 - 1
// etc

For the new constructor, the resulting object stores three pieces of information internally in reserved slots. |new Int32()| creates a JSObject which allocates sizeof(int32_t) and stores that pointer in a private slot. It also stores its type, as a JSObject* pointing to the canonical Int32 prototype, and can store a parent JSObject* in case it refers to an Int32 that happens to be part of another object. Thus the slot layout of i above would be

i object:
  slot 1 (parent): JSObject* -> NULL (no parent object)
  slot 2 (type) : JSObject* -> Int32 prototype
  slot 3 (value) : void* -> binary blob from malloc(sizeof(int32_t))

Do we need to provide an explicit set() method, to allow for efficient modification? For instance,

i.set(5); // cheaper than i = new Int32(5);

3. declaring and passing a pointer

// C prototype: int32_t myfunc(int32_t* p)
let myfunc = mylib.declare("myfunc", DEFAULT_ABI, Int32, Pointer(Int32));
let p = new Pointer(new Int32()); // instantiates an int and a pointer
let ret = myfunc(p); // the int is an outparam
let i = p.contents(); // i = *p (by reference)
let a = p.address(); // 0x...

// same thing, but with a named integer
let i = new Int32();
let p = new Pointer(i);
let ret = myfunc(p); // modifies i

// same thing, but with a pointer temporary
let i = new Int32();
let ret = myfunc(new Pointer(i)); // modifies i

// other examples
let q = new Pointer(); // instantiate a null pointer to a void type
q = new Pointer(5); // TypeError - require a ctypes type

Internally, a pointer requires a backing object (unless it's a null pointer). In the examples, the Pointer JSObject holds a reference to the Int32 JSObject for rooting purposes, and is laid out similarly to an Int32 object:

p object:
  slot 1 (parent): JSObject* -> Int32 backing object
  slot 2 (type) : JSObject* -> Pointer prototype
  slot 3 (value) : void* -> pointer to binary int32_t blob inside backing object

4. declaring a pointer to opaque struct

const FILE = ctypes.Struct(); // creates a Struct() type with no allocated binary storage, and no fields to access
let fopen = mylib.declare("fopen", DEFAULT_ABI, Pointer(FILE), String);
let file = fopen("foo"); // creates a new Pointer() object
file.contents(); // will throw - type is unknown
file.address(); // ok

5. declaring a struct

// C prototype: struct s_t { int32_t a; int64_t b };
const s_t = Struct([{ a: Int32 }, { b: Int64 }]);
let myfunc = mylib.declare("myfunc", DEFAULT_ABI, Int32, s_t);

let s = new s_t(10, 20);

This creates an s_t object which allocates binary space for both fields, creates getters and setters to access the binary fields via their offset, assigns the values 10 and 20 to the fields, and whose prototype is s_t:

s object:
  slot 1 (parent): JSObject* -> NULL
  slot 2 (type) : JSObject* -> s_t prototype
  slot 3 (value) : void* -> pointer to binary blob from malloc()
  slot 4 (fields): array of data for each field:
    { JSObject* parent; JSObject* type; ptrdiff_t offset; }

The array of field information allows each field to be dependent on another JSObject (only for the case where the field is a pointer), have an associated type, and have an offset into the binary blob for ease of access.

let c = s.b; // invokes the getter for |b| to create an Int64 object like so:

c object:
  slot 1 (parent): JSObject* -> s backing object
  slot 2 (type) : JSObject* -> Int64 prototype
  slot 3 (value) : void* -> pointer to binary int64_t blob inside backing object

let i = myfunc(s); // checks the type of s by JSObject* prototype equality

6. pointers to struct fields

let p = new Pointer(s.b);

Once the Int64 representing s.b is constructed, the Pointer object references it directly:

p object:
  slot 1 (parent): JSObject* -> Int64 backing object (which, in turn, is backed by s)
  slot 2 (type) : JSObject* -> Pointer prototype
  slot 3 (value) : void* -> pointer to binary int64_t blob inside backing object

7. nested structs

const u_t = Struct([{ x: Int64 }, { y: s_t }]);
let u = new u_t(5e4, s); // copies data from s into u.y - no references

let u_field = u.y; // creates an s_t object that points directly to the offset of u.y within u.

const v_t = Struct([{ x: Pointer(s_t) }, { y: Pointer(s_t) }]);
let v = new v_t(new Pointer(s), new Pointer(s));

In this case, the fields array will each have their respective Pointer as the parent object, and both will point to the s binary blob.