Draft
This page is not complete.
Data types for use with js-ctypes are represented by CType
objects. These are JavaScript constructors; as such, they're callable functions that you can use to create new CData
objects of that type. There are several ways you can go about creating new CData
objects.
There are three forms of the syntax for creating CData
objects without immediately assigning them a value:
var myCDataObj = new type;
var myCDataObj = new type();
var myCDataObj = type();
These all do the same thing: they return a new CData
object of the specified type, whose data buffer has been populated entirely with zeroes.
type.size
is undefined, creating a new object this way will throw a TypeError
exception.Similarly, you can initialize CData
objects with specific values at the type of creation by specifying them as a parameter when calling the CType
's constructor, like this:
var myCDataObj = new type(value);
var myCDataObj = type(value);
If the size of the specified type isn't undefined, the specified value is converted to the given type. If the conversion isn't possible, TypeError
is thrown. The resulting data is then copied into a new CData
object. If the original value is already a CData
object, the original object is simply duplicated directly into the new one.
If type
is an array type of unspecified length, the following steps are taken:
value
is a size value, a new array of that length is created, with its cells ready to accept values of the same type as those in the specified array. This is the same as new ArrayType(type.elementType, value)
.type
represents a JavaScript string (that is, an array of jschar characters followed by a null terminator), a copy of that string is created and returned.type
is an array of 8-bit characters and value
is a UTF-16 string, the new CData
object is the result of converting the UTF-16 string to UTF-8, with a null terminator.value
is a JavaScript array object and it has a non-negative length, a new array is created and the contents of the array specified by value
are converted to CData
objects and copied into the new array, which is then returned.TypeError
is thrown.If type
is ctypes.void_t
, a TypeError
is thrown.
let arrayType = ctypes.ArrayType(ctypes.int32_t); let myArray = new arrayType(5);
At this point, myArray.length
is 5; there are 5 entries in the array. myArray.constructor.size
is 20; the total size of the array's data buffer is 20 bytes (5 entries, 4 bytes apiece).
var CStr1 = ctypes.jschar.array()('rawr'); var CStr2 = ctypes.jschar.array()('boo'); var myCArray_ofStrings = ctypes.jschar.ptr.array(2)([CStr1, CStr2]); // specifying length of 2 is optional, can omit it, so can just do `ctypes.jschar.ptr.array()([CStr1, CStr2])` myCArray_ofStrings.addressOfElement(0).contents.readString(); // outputs: "rawr" myCArray_ofStrings.addressOfElement(1).contents.readString(); // outputs: "boo"
var jsArr = [4, 10]; var myCArr = ctypes.int.array(jsArr.length)(jsArr); // specifying length is optional, can omit. this will also work: `ctypes.int.array()(jsArr)` myCArr.addressOfElement(0).contents; // outputs: 4 myCArr.addressOfElement(1).contents; // outputs: 10
You can type cast data from one type to another by using the ctypes.cast()
function:
var newObj = ctypes.cast(origObj, newType);
This will return a new object whose data block is shared with the original object, but whose type is newType
. If the size of the new type is undefined or larger than the size of the original object's data block, TypeError
is thrown.
This works very much like a standard C type cast or C++ reinterpret_cast
.
This example shows how to cast an array of a certain type to another type.
// lets create an array of long's var my = ctypes.long.array()([1, 2, 3, 4]); my.toString(); // this outputs to browser console: `"ctypes.long.array(4)([ctypes.Int64("1"), ctypes.Int64("2"), ctypes.Int64("3"), ctypes.Int64("4")])"` my.addressOfElement(1).contents; // this outputs `Int64 { }` my.addressOfElement(1).contents.toString(); // outputs: `"2"` // now this is how to get the array of long's cast to array of int's var myCasted = ctypes.cast(my.address(), ctypes.int.array(my.length).ptr).contents; myCasted.toString(); // this outputs to browser console: `"ctypes.int.array(4)([1, 2, 3, 4])"` myCasted.addressOfElement(1).contents; // this outputs `2` myCasted.addressOfElement(1).contents.toString(); // outputs: `"2"`
Source of this, and to see wrong ways of casting, and explanation on why this is the right way to cast an array (explained by matching constructor's) see here: GitHubGIST :: _ff-addon-tutorial-jsctypes_castingArrays
A CData object represents a C value in memory. You can always get a pointer to the C value using the CData
object's address()
method.
It's important to keep in mind that two (or more) CData
objects can share the same memory block for their contents. This will happen, for example, when type casting. This is called aliasing. The shared memory can be whole or in part.
For example:
const Point = new ctypes.StructType("Point", [{x: ctypes.int32_t}, {y: ctypes.int32_t}]); const Rect = new ctypes.StructType("Rect", [{topLeft: Point}, {bottomRight: Point}]); var r = Rect(); var p = r.topLeft; r.topLeft.x = 100;
At this point, p
is a reference to the topLeft
field in the Rect
named r
. Setting the value of p.x
will affect the value of r.topLeft.x
, as expected.
Equality doesn't work the same way in JavaScript as it does in C, which means certain operations might not work the way you expect. In particular, comparing two different objects that are represented under-the-hood as JavaScript objects using the ==
or ===
operators will always return false
. This affects comparisons of pointers, integers that are the same size as pointers, and 64-bit integers.
You can work around this by serializing these values using the toString()
method and comparing using the resulting string.
See Determining if two pointers are equal for an example of how this works when comparing pointers.
If, for example, you need to see if the value of an integer is 5, you can do so like this:
var t = ctypes.int32_t(5); if (t.toString() == "ctypes.int32_t(5)") { // it's 5 }
C functions expect strings to be arrays of characters, with the end of the string indicated by a null character. JavaScript, on the other hand, uses the String
object to represent strings.
The CData
object provides the readString()
method, which reads bytes from the specified string and returns a new JavaScript String
object representing that string.
For example:
var jsString = timeStr.readString();
Happily, converting JavaScript strings to C formatted strings is easy; just create a character array containing the JavaScript string:
var myUTF8String = ctypes.char.array()("Original string.");
This creates a UTF-8 format null-terminated string in the character array named myUTF8String
.
If you need a UTF-16 string, you can do this:
var myUTF16String = ctypes.jschar.array()("Original string.");
You don't even need to convert strings when using them as input parameters to C functions. They get converted automatically for you. Just pass in the JavaScript String
object.
However, when C functions return, they still return a char.ptr
or jschar.ptr
(that is, a pointer to an 8-bit or 16-bit array of characters). You'll have to convert those yourself, as covered above.
If for some reason non-null terminated strings are needed, this can also be accomplished.
As review, making a null-terminated string happens like this:
var CStr_nullTerminated = ctypes.jschar.array()('rawr'); console.log(CStr_nullTerminated); // outputs to browserconsole: `CData { length: 5 }` console.log(CStr_nullTerminated.toString()); // outputs to browser console: `"ctypes.char16_t.array(5)(["r", "a", "w", "r", "\x00"])"`
The console.log shows that the length is greater then the length of "rawr" which is 4, doing a .toString on it shows there is a null terminator of \x00 on the end.
To make a non-null terminated string this is how it is done:
var CStr_notNullTerminated = ctypes.jschar.array()('rawr'.split('')); // this is the same as doing: `ctypes.jschar.array()(['r', 'a', 'w', 'r'])` console.log(CStr_notNullTerminated); // outputs to browser console: `CData { length: 4 }` console.log(CStr_notNullTerminated.toString()); // outputs to browser console: `"ctypes.char16_t.array(5)(["r", "a", "w", "r"])"`
This method is just making an array. A quicker way to make a non-null terminated string is force a length on the ctypes array like this:
var CStr_notNullTerminated = ctypes.jschar.array(4)('rawr'); // notice the `4` we pass here console.log(CStr_notNullTerminated); // outputs to browser console: `CData { length: 4 }` console.log(CStr_notNullTerminated.toString()); // outputs to browser console: `"ctypes.char16_t.array(5)(["r", "a", "w", "r"])"`
This works fine as well. You cannot pass a length to the ctypes.array that is less then the string length, for example this throws:
ctypes.jschar.array(3)('rawr'); // browser console throws: `Error: ArrayType has insufficient length`
We can have multiple null-terminators by using a number larger then the length of the string, like this:
var CStr_notNullTerminated = ctypes.jschar.array(10)('rawr'); // notice the `4` we pass here console.log(CStr_notNullTerminated); // outputs to browser console: `CData { length: 10 }` console.log(CStr_notNullTerminated.toString()); // outputs to browser console: `"ctypes.char16_t.array(5)(["r", "a", "w", "r", "\x00", "\x00", "\x00", "\x00", "\x00", "\x00"])"`
Notice the 6 null terminations.
This example creates a pointer to an integer, then looks at the pointed-to data using the PointerType
object's contents
property.
var i = ctypes.int32_t(9); // Create a C integer whose value is 9 var p = i.address(); // Create a pointer to the integer variable i if (p.contents == 9) { // Look at the contents of the pointer // the value is 9 } else { // the value isn't 9 }
You can also use the contents
property to set the value pointed to by a variable.
var i = ctypes.int32_t(9); // Create a C integer variable whose value is 9 var p = i.address(); // Get a pointer to i p.contents = 12; // Change the value of i to 12
This example demonstrates the use of the isNull()
method to determine whether or not a pointer is null.
var p = someCDataObject.address(); if (p.isNull()) { // the pointer is null } else { // the pointer isn't null }
Due to quirks in how equality is determined in JavaScript, the best way to see if two pointers are equal is to convert them to strings, then compare the strings.
if (i.address().toString() == ctypes.int32_t.ptr(5).toString()) { // the integer i's address is 5 }
The example above not only compares the addresses, but also the type. So while the above comparison succeeds if the address of i
is 5, it also only succeeds if i
is in fact of type ctypes.int32_t
.
If you don't care about type equality, and simply want to compare two addresses, you can use type casting:
if (ctypes.cast(p, ctypes.uintptr_t).value.toString() == "5") { // the pointer p's address is 5 }
This casts the pointer to a ctypes.uintptr_t
, for which the value property returns a ctypes.UInt64
. Calling toString()
on that returns the pointer as a base 10 integer.
If you need to work with C functions that accept arrays of pointers as inputs, you can construct an array of pointers like this:
var ptrArrayType = ctypes.char.ptr.array(5); var myArray = ptrArrayType(); var someCFunction = library.declare("someCFunction", ctypes.default_abi, ctypes.void_t, ctypes.char.ptr.array() /*same as ctypes.char.ptr.ptr*/); someCFunction(myArray);
Line 1 declares a new array type, capable of containing 5 arrays of pointers to C characters. This might be an array of strings, for example. The next line instantiates an object of that type, creating a new array. Each pointer in the array is initialized to null. Line 4 declares the C function that accepts the array as an input, and the last line calls that function.
If a function expects an argument that is a pointer to a list of elements, this is how it is accomplished:
var myArrayType = ctypes.char.array(5); var myArray = myArrayType(); var someCFunction = library.declare("someCFunction", ctypes.default_abi, ctypes.void_t, ctypes.char.array() /*same as ctypes.char.ptr*/); someCFunction(myArray);
Second example:
var myStruct = ctypes.StructType('foo', [{bar: ctypes.bool}]); var myStructArrayType = myStruct.array(5); var myArray = myStructArrayType(); var someCFunction = library.declare("someCFunction", ctypes.default_abi, ctypes.void_t, myStruct.ptr); someCFunction(myArray);
This shows how to pass a buffer containing 5 elements of myStruct
, the elements in myArray are not pointers.
While most numeric types in js-ctypes are represented by standard JavaScript Number
objects, 64-bit integers cannot all be represented accurately by this type. For that reason, 64-bit and pointer-sized C values of numeric types don't get automatically converted to JavaScript numbers. Instead, they're converted to JavaScript objects that you can manipulate using the methods provided by the Int64
and UInt64
objects.
To create a new 64-bit number object, use the ctypes.Int64.Int64()
method to create a new 64-bit signed integer, or the ctypes.UInt64.UInt64()
method to create a new unsigned 64-bit integer. For example:
var num1 = ctypes.UInt64(5000); var num2 = ctypes.Int64(-42);
If you need to create a 64-bit integer whose value can't be represented by a JavaScript Number
, you have two options. You can build the new number using the high and low 32-bit numbers, or you can create it using a string representation.
You can simply pass a string representation of a 64-bit number into the Int64
or UInt64
constructor, like this:
var num1 = ctypes.Int64("400000000000"); var num2 = ctypes.UInt64("-0x1234567890ABCDEF");
As you can see, you can use this technique with both decimal and hexadecimal source strings.
The join()
method offered by the Int64
and UInt64
objects provides another way to construct 64-bit integers. It accepts as its input parameters the high and low 32-bit values and returns a new 64-bit integer. For example:
var num = ctypes.Int64.join(-0x12345678, 0x90ABCDEF);
The Int64
and UInt64
objects don't provide any methods for performing arithmetic, which means you'll have to do it yourself by pulling out the high and low 32-bit portions and doing math on them, then joining them back together if necessary to get the complete result. You'll also have to handle carry from the low to high word and back as appropriate.
For example, adding two 64-bit integers can be done like this. This example is for unsigned numbers, but a similar approach can be taken for signed values.
function add(a, b) { const MAX_INT = Math.pow(2, 32); var alo = ctypes.UInt64.lo(a); var ahi = ctypes.UInt64.hi(a); var blo = ctypes.UInt64.lo(b); var bhi = ctypes.UInt64.hi(b); var lo = alo + blo; var hi = 0; if (lo >= MAX_UINT) { hi = lo - MAX_UINT; lo -= MAX_UINT; } hi += (ahi + bhi); return ctypes.UInt64.join(hi, lo); };
This splits each of the two source numbers into their high and low components, then adds the low 32 bits of each, handling carry into the high 32 bits. Once the math is done, the two values are joined back together. join()
will throw an exception if the resulting number exceeds 64 bits in length.