Confirmed users
3,339
edits
(Bug 1063962 renamed ctypes.jschar to ctypes.char16_t and, to maintain backwards compatibility, bug 1064935 added an alias for ctypes.jschar to ctypes.char16_t.) |
|||
| (147 intermediate revisions by 4 users not shown) | |||
| Line 1: | Line 1: | ||
'''js-ctypes''' is a library for calling C/C++ functions from JavaScript without having to write or generate any C/C++ "glue code". | |||
< | |||
js-ctypes is already in mozilla-central, but the API is subject to change. This page contains design proposals for the eventual js-ctypes API. | |||
= Libraries = | |||
:'''<code>ctypes.open(''name'')</code>''' - Open a library. ''(TODO: all the details)'' This always returns a <code>Library</code> object or throws an exception. | |||
/ | <code>Library</code> objects have the following methods: | ||
:'''<code>''lib''.declare(''name'', ''abi'', ''rtype'', ''<nowiki>[argtype1, ...]</nowiki>'')</code>''' - Declare a function. ''(TODO: all the details)'' This always returns a new callable <code>CData</code> object representing a function pointer to ''name'', or throws an exception. | |||
:If ''rtype'' is an array type, this throws a <code>TypeError</code>. | |||
:If any ''argtypeN'' is an array type, the result is the same as if it had been the corresponding pointer type, <code>''argtypeN''.elementType.ptr</code>. ''(Rationale: This is how C and C++ treat array types in function declarations.)'' | |||
''(TODO: Explain what happens when you call a declared function. In brief: It uses <code>ImplicitConvert</code> to convert the JavaScript arguments to C and <code>ConvertToJS</code> to convert the return value to JS.)'' | |||
< | |||
= Types = | |||
A ''type'' maps JS values to C/C++ values and vice versa. They're used when declaring functions. They can also be used to create and populate C/C++ data structures entirely from JS. | |||
''(Types and their prototypes are extensible: scripts can add new properties to them. Rationale: This is how most JavaScript constructors behave.)'' | |||
== Built-in types == | |||
ctypes provides the following types: | |||
:'''<code>ctypes.int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t, float32_t, float64_t</code>''' - Primitive numeric types that behave the same way on all platforms (with the usual caveat that every platform has slightly different floating-point behavior, in corner cases, and there's a limit to what we can realistically do about it). | |||
:Since some 64-bit values are outside the range of the JavaScript number type, <code>ctypes.int64_t</code> and <code>ctypes.uint64_t</code> do not autoconvert to JavaScript numbers. Instead, they convert to objects of the wrapper types <code>ctypes.Int64</code> and <code>ctypes.UInt64</code> (which are JavaScript object types, not <code>CType</code>s). See "64-bit integer objects" below. | |||
< | |||
< | |||
:'''<code>ctypes.size_t, ssize_t, intptr_t, uintptr_t</code>''' - Primitive types whose size depends on the platform. ''(These types do not autoconvert to JavaScript numbers. Instead they convert to wrapper objects, even on 32-bit platforms. See "64-bit integer objects" below. Rationale: On 64-bit platforms, there are values of these types that cannot be precisely represented as JS numbers. It will be easier to write code that works on multiple platforms if the builtin types autoconvert in the same way on all platforms.)'' | |||
:'''<code>ctypes.bool, short, unsigned_short, int, unsigned, unsigned_int, long, unsigned_long, float, double</code>''' - Types that behave like the corresponding C types. As in C, <code>unsigned</code> is always an alias for <code>unsigned_int</code>. | |||
:''(<code>ctypes.long</code> and <code>ctypes.unsigned_long</code> autoconvert to 64-bit integer objects on all platforms. The rest autoconvert to JavaScript numbers. Rationale: Some platforms have 64-bit <code>long</code> and some do not.)'' | |||
< | |||
< | |||
:'''<code>ctypes.char, ctypes.signed_char, ctypes.unsigned_char</code>''' - Character types that behave like the corresponding C types. (These are very much like <code>int8_t</code> and <code>uint8_t</code>, but they differ in some details of conversion. For example, <code>ctypes.char.array(30)(str)</code> converts the string ''str'' to UTF-8 and returns a new <code>CData</code> object of array type.) | |||
:'''<code>ctypes.char16_t</code>''' - A 16-bit unsigned character type representing a UTF-16 code unit. (This is distinct from <code>uint16_t</code> in details of conversion behavior. js-ctypes autoconverts C <code>char16_t</code>s to JavaScript strings of length 1.) For backwards compatibility, <code>ctypes.jschar</code> is an alias for <code>char16_t</code>. | |||
< | |||
:'''<code>ctypes.void_t</code>''' - The special C type <code>void</code>. This can be used as a return value type. (<code>void</code> is a keyword in JavaScript.) | |||
:'''<code>ctypes.voidptr_t</code>''' - The C type <code>void *</code>. | |||
- | The ''wrapped integer types'' are the types <code>int64_t</code>, <code>uint64_t</code>, <code>size_t</code>, <code>ssize_t</code>, <code>intptr_t</code>, <code>uintptr_t</code>, <code>long</code>, and <code>unsigned_long</code>. These are the types that autoconvert to 64-bit integer objects rather than to primitive JavaScript numbers. | ||
== User-defined types == | |||
Starting from the builtin types above, these functions can be used to create additional types: | |||
:'''<code>new ctypes.PointerType(''t'')</code>''' - If ''t'' is a <code>CType</code>, return the type "pointer to ''t''". The result is cached so that future requests for this pointer type produce the same <code>CType</code> object. If ''t'' is a string, instead return a new opaque pointer type named ''t''. Otherwise throw a <code>TypeError</code>. | |||
:'''<code>new ctypes.FunctionType(''abi'', ''rt'', [ ''at1'', ... ])</code>''' - Return a function pointer <code>CType</code> corresponding to the C type <code>rt (*) (at1, ...)</code>, where ''abi'' is a ctypes ABI type and ''rt'' and ''at1'', ... are <code>CType</code>s. Otherwise throw a <code>TypeError</code>. | |||
:'''<code>new ctypes.ArrayType(''t'')</code>''' - Return an array type with unspecified length and element type ''t''. If ''t'' is not a type or <code>''t''.size</code> is <code>undefined</code>, throw a <code>TypeError</code>. | |||
'''ctypes. | :'''<code>new ctypes.ArrayType(''t'', ''n'')</code>''' - Return the array type ''t''[''n'']. If ''t'' is not a type or <code>''t''.size</code> is <code>undefined</code> or ''n'' is not a size value (defined below), throw a <code>TypeError</code>. If the size of the resulting array type, in bytes, would not be exactly representable both as a <code>size_t</code> and as a JavaScript number, throw a <code>RangeError</code>. | ||
''' | :A ''size value'' is either a non-negative, integer-valued primitive number, an <code>Int64</code> object with a non-negative value, or a <code>UInt64</code> object. | ||
''( | :''(Array types with 0 elements are allowed. Rationale: C/C++ allow them, and it is convenient to be able to pass an array to a foreign function, and have it autoconverted to a C array, without worrying about the special case where the array is empty.)'' | ||
'''ctypes. | :'''<code>new ctypes.StructType(''name'', ''fields'')</code>''' - Create a new struct type with the given ''name'' and ''fields''. ''fields'' is an array of field descriptors, of the format | ||
:<code>[ { field1: type1 }, { field2: type2 }, ... ]</code> | |||
''' | :where <code>field''n''</code> is a string denoting the name of the field, and <code>type''n''</code> is a ctypes type. js-ctypes calculates the offsets of the fields from its encyclopedic knowledge of the architecture's struct layout rules. If ''name'' is not a string, or any <code>type''n''</code> is such that <code>type''n''.size</code> is <code>undefined</code>, throw a <code>TypeError</code>. If the size of the struct, in bytes, would not be exactly representable both as a <code>size_t</code> and as a JavaScript number, throw a <code>RangeError</code>. | ||
'' | ''(Open issue: Specify a way to tell <code>ctypes.StructType</code> to use <code>#pragma pack(n)</code>.)'' | ||
These constructors behave exactly the same way when called without the <code>new</code> keyword. | |||
Examples: | Examples: | ||
| Line 122: | Line 77: | ||
const DWORD = ctypes.uint32_t; | const DWORD = ctypes.uint32_t; | ||
const HANDLE = new ctypes.PointerType("HANDLE"); | const HANDLE = new ctypes.PointerType("HANDLE"); | ||
const HANDLES = new ctypes.ArrayType(HANDLE); | |||
const FILE = new ctypes.StructType("FILE").ptr; | |||
const IOBuf = new ctypes.ArrayType(ctypes.uint8_t, 4096); | |||
const struct_tm = new ctypes.StructType('tm', [{'tm_sec': ctypes.int}, ...]); | |||
const FILE = new ctypes. | const comparator_t = new ctypes.FunctionType(ctypes.default_abi, ctypes.int, [ ctypes.voidptr_t, ctypes.voidptr_t ]); | ||
const | |||
== Properties of types == | |||
All the fields described here are read-only. | |||
All types have these properties and methods: | |||
:'''<code>''t''.size</code>''' - The C/C++ <code>sizeof</code> the type, in bytes. The result is a primitive number, not a <code>UInt64</code> object. | |||
:If ''t'' is an array type with unspecified length, <code>''t''.size</code> is <code>undefined</code>. | |||
:<code>ctypes.void_t.size</code> is <code>undefined</code>. | |||
:'''<code>''t''.name</code>''' - A string, the type's name. It's intended that in ordinary use, this will be a C/C++ type expression, but it's not really meant to be machine-readable in all cases. | |||
:For primitive types this is just the name of the corresponding C/C++ type. | |||
:For struct types and opaque pointer types, this is simply the string that was passed to the constructor. For other function, pointer, and array types this should try to generate valid C/C++ type expressions, which isn't exactly trivial. | |||
:''(Open issue: This conflicts with the usual meaning of .name for functions, and types are callable like functions.)'' | |||
ctypes.int32_t.name | |||
===> "int32_t" | |||
ctypes.void_t.name | |||
===> "void" | |||
ctypes.char16_t.ptr.name | |||
===> "char16_t *" | |||
const FILE = new ctypes.StructType("FILE").ptr; | |||
FILE.name | |||
===> "FILE*" | |||
const fn_t = new ctypes.FunctionType(ctypes.stdcall, ctypes.int, [ ctypes.voidptr_t, ctypes.voidptr_t ]); | |||
fn_t.name | |||
===> "int (__stdcall *)(void*, void*)" | |||
const struct_tm = new ctypes.StructType("tm", [{tm_sec: ctypes.int}, ...]); | |||
struct_tm.name | |||
===> "tm" | |||
// Pointer-to-array types are not often used in C/C++. | |||
// Such types have funny-looking names. | |||
const ptrTo_ptrTo_arrayOf4_strings = | |||
new ctypes.PointerType( | |||
new ctypes.PointerType( | |||
new ctypes.ArrayType(new ctypes.PointerType(ctypes.char), 4))); | |||
ptrTo_ptrTo_arrayOf4_strings.name | |||
===> "char *(**)[4]" | |||
:'''<code>''t''.ptr</code>''' - Return <code>ctypes.PointerType(''t'')</code>. | |||
:'''<code>''t''.array()</code>''' - Return <code>ctypes.ArrayType(''t'')</code>. | |||
:'''<code>''t''.array(''n'')</code>''' - Return <code>ctypes.ArrayType(''t'', ''n'')</code>. | |||
:Thus a quicker (but still almost as confusing) way to write the type in the previous example would be: | |||
const ptrTo_ptrTo_arrayOf4_strings = ctypes.char.ptr.array(4).ptr.ptr; | |||
:''(<code>.array()</code> requires parentheses but <code>.ptr</code> doesn't. Rationale: <code>.array()</code> has to be able to handle an optional parameter. Note that in C/C++, to write an array type requires brackets, optionally with a number in between: <code>int [10]</code> --> <code>ctypes.int.array(10)</code>. Writing a pointer type does not require the brackets.)'' | |||
:'''<code>''t''.toString()</code>''' - Return <code>"type " + ''t''.name</code>. | |||
:'''<code>''t''.toSource()</code>''' - Return a JavaScript expression that evaluates to a <code>CType</code> describing the same C/C++ type as ''t''. | |||
ctypes.uint32_t.toSource() | |||
===> "ctypes.uint32_t" | |||
ctypes.string.toSource() | |||
===> "ctypes.string" | |||
const charPtr = new ctypes.PointerType(ctypes.char); | |||
charPtr.toSource() | |||
===> "ctypes.char.ptr" | |||
const | const Point = new ctypes.StructType( | ||
"Point", [{x: ctypes.int32_t}, {y: ctypes.int32_t}]); | |||
Point.toSource() | |||
===> "ctypes.StructType("Point", [{x: ctypes.int32_t}, {y: ctypes.int23_t}])" | |||
Pointer types also have: | |||
:'''<code>''t''.targetType</code>''' - Read-only. The pointed-to type, or <code>null</code> if ''t'' is an opaque pointer type. | |||
Function types also have: | |||
:'''<code>''t''.abi</code>''' - Read-only. The ABI of the function; one of the ctypes ABI objects. | |||
:'''<code>''t''.returnType</code>''' - Read-only. The return type. | |||
:'''<code>''t''.argTypes</code>''' - Read-only. A sealed array of argument types. | |||
Struct types also have: | |||
:'''<code>''t''.fields</code>''' - Read-only. A sealed array of field descriptors. ''(TODO: Details.)'' | |||
Array types also have: | |||
:'''<code>''t''.elementType</code>''' - The type of the elements of an array of this type. E.g. <code>IOBuf.elementType === ctypes.uint8_t</code>. | |||
:'''<code>''t''.length</code>''' - The number of elements, a non-negative integer; or <code>undefined</code> if this is an array type with unspecified length. ''(The result, if not <code>undefined</code>, is a primitive number, not a <code>UInt64</code> object. Rationale: Having <code>.length</code> produce anything other than a number is foreign to JS, and arrays of more than 2<sup>53</sup> elements are currently unheard-of.)'' | |||
Minutiae: | |||
:'''<code>ctypes.CType</code>''' is the abstract-base-class constructor of all js-ctypes types. If called, it throws a <code>TypeError</code>. (This is exposed in order to expose <code>ctypes.CType.prototype</code>.) | |||
:The <nowiki>[[Class]]</nowiki> of a ctypes type is <code>"CType"</code>. | |||
:The <nowiki>[[Class]]</nowiki> of the type constructors <code>ctypes.{C,Array,Struct,Pointer}Type</code> is <code>"Function"</code>. | |||
:Every <code>CType</code> has a read-only, permanent <code>.prototype</code> property. The type-constructors <code>ctypes.{C,Pointer,Struct,Array}Type</code> each have a read-only, permanent <code>.prototype</code> property as well. | |||
:Types have a hierarchy of prototype objects. The prototype of <code>ctypes.CType.prototype</code> is <code>Function.prototype</code>. The prototype of <code>ctypes.{Array,Struct,Pointer,Function}Type.prototype</code> and of all the builtin types except <code>ctypes.voidptr_t</code> is <code>ctypes.CType.prototype</code>. The prototype of an array type is <code>ctypes.ArrayType.prototype</code>. The prototype of a struct type is <code>ctypes.StructType.prototype</code>. The prototype of a pointer type is <code>ctypes.PointerType.prototype</code>. The prototype of a function type is <code>ctypes.FunctionType.prototype</code>. | |||
:Every <code>CType</code> ''t'' has <code>''t''.prototype.constructor === ''t''</code>; that is, its <code>.prototype</code> has a read-only, permanent, own <code>.constructor</code> property that refers to the type. The same is true of the five type constructors <code>ctypes.{C,Array,Struct,Pointer,Function}Type</code>. | |||
== Calling types == | == Calling types == | ||
<code>CType</code>s are JavaScript constructors. That is, they are functions, and they can be called to create new objects. (The objects they create are called <code>CData</code> objects, and they are described in the next section.) | |||
:'''<code>new ''t''</code>''' or '''<code>new ''t''()</code>''' or '''<code>''t''()</code>''' - Create a new <code>CData</code> object of type ''t''. | |||
:Without arguments, these allocate a new buffer of <code>''t''.size</code> bytes, populate it with zeroes, and return a new <code>CData</code> object referring to the complete object in that buffer. | |||
:If <code>''t''.size</code> is <code>undefined</code>, this throws a <code>TypeError</code>. | |||
:'''<code>new ''t''(''val'')</code>''' or '''<code>''t''(''val'')</code>''' - Create a new <code>CData</code> object as follows: | |||
:* If <code>''t''.size</code> is not <code>undefined</code>: Convert ''val'' to type ''t'' by calling <code>ExplicitConvert(''val'', ''t'')</code>, throwing a <code>TypeError</code> if the conversion is impossible. Allocate a new buffer of <code>''t''.size</code> bytes, populated with the converted value. Return a new <code>CData</code> object of type ''t'' referring to the complete object in that buffer. (When ''val'' is a <code>CData</code> object of type ''t'', the behavior is like <code>malloc</code> followed by <code>memcpy</code>.) | |||
:* If ''t'' is an array type of unspecified length: | |||
::* If ''val'' is a size value (defined above): Let ''u'' = <code>ArrayType(''t''.elementType, ''val'')</code> and return <code>new ''u''</code>. | |||
::* If <code>''t''.elementType</code> is <code>char16_t</code> and ''val'' is a string: Return a new <code>CData</code> object of type <code>ArrayType(ctypes.char16_t, ''val''.length + 1)</code> containing the contents of ''val'' followed by a null character. | |||
::* If <code>''t''.elementType</code> is an 8-bit character type and ''val'' is a string: If ''val'' is not a well-formed UTF-16 string, throw a <code>TypeError</code>. Otherwise, let ''s'' = a sequence of bytes, the result of converting ''val'' from UTF-16 to UTF-8, and let ''n'' = the number of bytes in ''s''. Return a new <code>CData</code> object of type <code>ArrayType(''t''.elementType, ''n'' + 1)</code> containing the bytes in ''s'' followed by a null character. | |||
::* If ''val'' is a JavaScript array object and <code>''val''.length</code> is a nonnegative integer, let ''u'' = <code>ArrayType(''t''.elementType, ''val''.length)</code> and return <code>new ''u''(''val'')</code>. ''(Array <code>CData</code> objects created in this way have <code>''cdata''.constructor === ''u''</code>, not ''t''. Rationale: For all <code>CData</code> objects, <code>cdata.constructor.size</code> gives the size in bytes, unless a struct field shadows <code>cdata.constructor</code>.)'' | |||
::* Otherwise, throw a <code>TypeError</code>. | |||
:* Otherwise, ''t'' is <code>void_t</code>. Throw a <code>TypeError</code>. | |||
let a_t = ctypes.ArrayType(ctypes.int32_t); | |||
let a = new a_t(5); | |||
a.length | |||
===> 5 | |||
a.constructor.size | |||
===> 20 | |||
= CData objects = | |||
A <code>CData</code> object represents a C/C++ value located in memory. The address of the C/C++ value can be taken (using the <code>.address()</code> method), and it can be assigned to (using the <code>.value</code> property). | |||
Every <code>CData</code> object has a ''type'', the <code>CType</code> object that describes the type of the C/C++ value. | |||
Minutiae: | |||
:The <nowiki>[[Class]]</nowiki> of a <code>CData</code> object is <code>"CData"</code>. | |||
:The prototype of a <code>CData</code> object is the same as its type's <code>.prototype</code> property. | |||
''(Implementation notes: A <code>CData</code> object has a reserved slot that points to its type; a reserved slot that contains <code>null</code> if the object owns its own buffer, and otherwise points to the base <code>CData</code> object that owns the backing buffer where the data is stored; and a data pointer. The data pointer points to the actual location within the buffer of the C/C++ object to which the <code>CData</code> object refers. Since the data pointer might not be aligned to 2 bytes, PRIVATE_TO_JSVAL is insufficient; a custom JSClass.trace hook will be needed. If the object owns its own buffer, its finalizer frees it. Other <code>CData</code> objects that point into the buffer keep the base <code>CData</code>, and therefore the underlying buffer, alive.)'' | |||
== Properties and methods of CData objects == | |||
All <code>CData</code> objects have these methods and properties: | |||
''''' | :'''<code>''cdata''.address()</code>''' - Return a new <code>CData</code> object of the pointer type <code>ctypes.PointerType(cdata.constructor)</code> whose value points to the C/C++ object referred to by ''cdata''. | ||
:''(Open issue: Does this pointer keep ''cdata'' alive? Currently not but we could easily change it. It is impossible to have all pointers keep their referents alive in a totally general way--consider pointers embedded in structs and arrays. But this special case would be pretty easy to hack: put a <code>.contents</code> property on the resulting pointer, referring back to ''cdata''.)'' | |||
== | :'''<code>''cdata''.constructor</code>''' - Read-only. The type of ''cdata''. ''(This is never <code>void_t</code> or an array type with unspecified length. Implementation note: The prototype of ''cdata'' is an object that has a read-only <code>constructor</code> property, as detailed under "minutiae".)'' | ||
:'''<code>''cdata''.toSource()</code>''' - Return the string "''t''(''arg'')" where ''t'' and ''arg'' are implementation-defined JavaScript expressions (intended to represent the type of <code>''cdata''</code> and its value, respectively). The intent is that <code>eval(''cdata''.toSource())</code> should ideally produce a new <code>CData</code> object containing a copy of ''cdata'', but this can only work if the type of <code>''cdata''</code> happens to be bound to an appropriate name in scope. | |||
:'''<code>''cdata''.toString()</code>''' - Return the same string as <code>''cdata''.toSource()</code>. | |||
The <code>.value</code> property has a getter and a setter: | |||
:'''<code>''cdata''.value</code>''' - Let ''x'' = <code>ConvertToJS(''cdata'')</code>. If <code>''x'' === ''cdata''</code>, throw a <code>TypeError</code>. Otherwise return ''x''. | |||
:'''<code>''cdata''.value = ''val''</code>''' - Let ''cval'' = <code>ImplicitConvert(''val'', ''cdata''.constructor)</code>. If conversion fails, throw a <code>TypeError</code>. Otherwise assign the value ''cval'' to the C/C++ object referred to by ''cdata''. | |||
== Structs == | |||
<code>CData</code> objects of struct types also have this method: | |||
:'''<code>''cstruct''.addressOfField(''name'')</code>''' - Return a new <code>CData</code> object of the appropriate pointer type, whose value points to the field of ''cstruct'' with the name ''name''. If ''name'' is not a JavaScript string or does not name a member of ''cstruct'', throw a <code>TypeError</code>. | |||
They also have getters and setters for each struct member: | |||
:'''<code>''cstruct''.''member''</code>''' - Let ''F'' be a <code>CData</code> object referring to the struct member. Return <code>ConvertToJS(''F'')</code>. | |||
:'''<code>''cstruct''.''member'' = ''val''</code>''' - Let ''cval'' = <code>ImplicitConvert(''val'', the type of the member)</code>. If conversion fails, throw a <code>TypeError</code>. Otherwise store ''cval'' in the appropriate member of the struct. | |||
These getters and setters can shadow the properties and methods described above. | |||
== Pointers == | |||
<code>CData</code> objects of pointer types also have this property: | |||
:'''<code>''cptr''.''contents''</code>''' - Let ''C'' be a <code>CData</code> object referring to the pointed-to contents of ''cptr''. Return <code>ConvertToJS(''C'')</code>. | |||
:'''<code>''cptr''.''contents'' = ''val''</code>''' - Let ''cval'' = <code>ImplicitConvert(''val'', the base type of the pointer)</code>. If conversion fails, throw a <code>TypeError</code>. Otherwise store ''cval'' in the pointed-to contents of ''cptr''. | |||
== Functions == | |||
<code>CData</code> objects of function types are callable: | |||
:'''<code>''let result = cfn(arg''1'', ...)''</code>''' - Let ''(carg''1'', ...)'' be <code>CData</code> objects representing the arguments to the C function ''cfn'', and ''cresult'' be a <code>CData</code> object representing its return value. Let ''carg''n = <code>ImplicitConvert(''arg''n, the type of the argument)</code>, and let ''result'' = <code>ConvertToJS(''cresult'')</code>. Call the C function with arguments represented by ''(carg''1'', ...)'', and store the result in ''cresult''. If conversion fails, throw a <code>TypeError</code>. | |||
== Arrays == | |||
Likewise, <code>CData</code> objects of array types have getters and setters for each element. Arrays additionally have a <code>length</code> property. | |||
Note that these getters and setters are only present for integers ''i'' in the range 0 ≤ i < <code>''carray''.length</code>. ''(Open issue: can we arrange to throw an exception if ''i'' is out of range?)'' | |||
:'''<code>''carray''[''i'']</code>''' - Let ''E'' be a <code>CData</code> object referring to the element at index ''i''. Return <code>ConvertToJS(''E'')</code>. | |||
:'''<code>''carray''[''i''] = ''val''</code>''' - Let ''cval'' = <code>ImplicitConvert(''val'', ''carray''.elementType)</code>. If conversion fails, throw a <code>TypeError</code>. Otherwise store ''cval'' in element ''i'' of the array. | |||
:'''<code>''carray''.length</code>''' - Read-only. The length of the array as a JavaScript number. ''(The same as <code>carray.constructor.length</code>. This is not a <code>UInt64</code> object. Rationale: Array <code>CData</code> objects should behave like other array-like objects for easy duck typing.)'' | |||
:'''<code>''carray''.addressOfElement(''i'')</code>''' - Return a new <code>CData</code> object of the appropriate pointer type (<code>ctypes.PointerType(''carray''.constructor.elementType)</code>) whose value points to element ''i'' of ''carray''. If ''i'' is not a JavaScript number that is a valid index of ''carray'', throw a <code>TypeError</code>. | |||
''(TODO: specify a way to read a C/C++ string and transcode it into a JS string.)'' | |||
== Aliasing == | |||
Note that it is possible for several <code>CData</code> objects to refer to the same or overlapping memory. (In this way <code>CData</code> objects are like C++ references.) For example: | |||
const Point = new ctypes.StructType( | |||
"Point", [[ctypes.int32_t, 'x'], [ctypes.int32_t, 'y']]); | |||
const Rect = new ctypes.StructType( | |||
"Rect", [[Point, 'topLeft'], [Point, 'bottomRight']]); | |||
var r = Rect(); // a new CData object of type Rect | |||
var p = r.topLeft; // refers to the topLeft member of r, not a copy | |||
r.topLeft.x = 100; // This would not work if `r.topLeft` was a copy! | |||
r.topLeft.x | |||
===> 100 // It works... | |||
p.x // and p refers to the same C/C++ object... | |||
===> 100 // so it sees the change as well. | |||
r.toSource() | |||
===> "Rect({topLeft: {x: 100, y: 0}, bottomRight: {x: 0, y: 0}})" | |||
p.x = 1.0e90; // Assigning a value out of range is an error. | |||
**** TypeError | |||
// The range checking is great, but it can have surprising | |||
// consequences sometimes: | |||
p.x = 0x7fffffff; // (the maximum int32_t value) | |||
p.x++; // p.x = 0x7fffffff + 1, which is out of range... | |||
**** TypeError // ...so this fails, leaving p.x unchanged. | |||
// But JS code doesn't need to do that very often. | |||
// To make this to roll around to -0x80000000, you could write: | |||
p.x = (p.x + 1) | 0; // In JS, `x|0` truncates a number to int32. | |||
== Casting == | |||
:'''<code>ctypes.cast(''cdata'', ''t'')</code>''' - Return a new <code>CData</code> object which points to the same memory block as ''cdata'', but with type ''t''. If <code>''t''.size</code> is undefined or larger than <code>''cdata''.constructor.size</code>, throw a <code>TypeError</code>. This is like a C cast or a C++ <code>reinterpret_cast</code>. | |||
== Equality == | |||
According to the ECMAScript standard, if ''x'' and ''y'' are two different objects, then <code>x === y</code> and <code>x == y</code> are both false. This has consequences for code that uses js-ctypes pointers, pointer-sized integers, or 64-bit integers, because all these values are represented as JavaScript objects. In C/C++, the <code>==</code> operator would compare values of these types for equality. Not so in js-ctypes: | |||
const HANDLE = new ctypes.PointerType("HANDLE"); | |||
const INVALID_HANDLE_VALUE = HANDLE(-1); | |||
const kernel32 = ctypes.open("kernel32"); | |||
const CreateMutex = kernel32.declare("CreateMutex", ...); | |||
var h = CreateMutex(null, false, null); | |||
if (h == INVALID_HANDLE_VALUE) // BAD - always false | |||
... | |||
This comparison is always false because <code>CreateMutex</code> returns a new <code>CData</code> object, which of course will be a different object from the existing value of <code>INVALID_HANDLE_VALUE</code>. | |||
''(Python ctypes has the same issue. It isn't mentioned in the docs, but:'' | |||
>>> from ctypes import * | |||
>>> c_void_p(0) == c_void_p(0) | |||
False | |||
>>> c_int(33) == c_int(33) | |||
False | |||
''We could overload operator== using the nonstandard hook <code>JSExtendedClass.equality</code> but it might not be worth it.)'' | |||
= 64-bit integer objects = | |||
Since JavaScript numbers are floating-point values, they cannot precisely represent all 64-bit integer values. Therefore 64-bit and pointer-sized C/C++ values of numeric types do not autoconvert to JavaScript numbers. Instead they autoconvert to JavaScript objects of type <code>ctypes.Int64</code> and <code>ctypes.UInt64</code>. | |||
<code>Int64</code> and <code>UInt64</code> objects are immutable. | |||
It's not possible to do arithmetic <code>Int64Object</code>s using the standard arithmetic operators. JavaScript does not have operator overloading (yet). A few convenience functions are provided. (These types are intentionally feature-sparse so that they can be drop-in-replaced with a full-featured bignum type when JavaScript gets one.) | |||
== Int64 == | |||
:'''<code>ctypes.Int64(''n'')</code>''' or '''<code>new ctypes.Int64(''n'')</code>''' - If ''n'' is an integer-valued number such that -2<sup>63</sup> ≤ ''n'' < 2<sup>63</sup>, return a sealed <code>Int64</code> object with that value. Otherwise if ''n'' is a string consisting of an optional minus sign followed by either decimal digits or <code>"0x"</code> or <code>"0X"</code> and hexadecimal digits, and the string represents a number within range, convert the string to an integer and construct an <code>Int64</code> object as above. Otherwise if ''n'' is an <code>Int64</code> or <code>UInt64</code> object, and represents a number within range, use the value to construct an <code>Int64</code> object as above. Otherwise throw a <code>TypeError</code>. | |||
<code>Int64</code> objects have the following methods: | |||
:'''<code>''i64''.toString(''[radix]'')</code>''' - If ''radix'' is omitted, assume 10. Return a string representation of ''a'' in base ''radix'', consisting of a leading minus sign, if the value is negative, followed by one or more lowercase digits in base ''radix''. | |||
:'''<code>''i64''.toSource()</code>''' - Return a string. ''(This is provided for debugging purposes, and programs should not rely on details of the resulting string, which may change in the future.)'' | |||
The following functions are also provided: | |||
:'''<code>ctypes.Int64.compare(''a'', ''b'')</code>''' - If ''a'' and ''b'' are both <code>Int64</code> objects, return <code>-1</code> if ''a'' < ''b'', <code>0</code> if ''a'' = ''b'', and <code>1</code> if ''a'' > ''b''. Otherwise throw a <code>TypeError</code>. | |||
:'''<code>ctypes.Int64.lo(''a'')</code>''' - If ''a'' is an <code>Int64</code> object, return the low 32 bits of its value. (The result is an integer in the range 0 ≤ ''result'' < 2<sup>32</sup>.) Otherwise throw a <code>TypeError</code>. | |||
:'''<code>ctypes.Int64.hi(''a'')</code>''' - If ''a'' is an <code>Int64</code> object, return the high 32 bits of its value (like <code>''a'' >> 32</code>). Otherwise throw a <code>TypeError</code>. | |||
:'''<code>ctypes.Int64.join(''hi'', ''lo'')</code>''' - If ''hi'' is an integer-valued number in the range -2<sup>31</sup> ≤ ''hi'' < 2<sup>31</sup> and ''lo'' is an integer-valued number in the range 0 ≤ ''lo'' < 2<sup>32</sup>, return a sealed <code>Int64</code> object whose value is ''hi'' × 2<sup>32</sup> + ''lo''. Otherwise throw a <code>TypeError</code>. | |||
== UInt64 == | |||
<code>UInt64</code> objects are the same except that the ''hi'' values are in the range 0 ≤ ''hi'' < 2<sup>32</sup> and the <code>.toString()</code> method never produces a minus sign. | |||
= Conversions = | |||
These functions are not exactly JS functions or C/C++ functions. They're algorithms used elsewhere in the spec. | |||
'''<code>ConvertToJS(''x'')</code>''' - This function is used to convert a <code>CData</code> object or a C/C++ return value to a JavaScript value. The intent is to return a simple JavaScript value whenever possible without loss of data or different behavior on different platforms, and a <code>CData</code> object otherwise. The precise rules are: | |||
* If the type of ''x'' is <code>void</code>, return <code>undefined</code>. | |||
* If the type of ''x'' is <code>bool</code>, return the corresponding JavaScript boolean. | |||
* If ''x'' is of a number type but not a wrapped integer type, return the corresponding JavaScript number. | |||
* If ''x'' is a signed wrapped integer type (<code>long</code>, <code>int64_t</code>, <code>ssize_t</code>, or <code>intptr_t</code>), return a <code>ctypes.Int64</code> object with value ''x''. | |||
* If ''x'' is an unsigned wrapped integer type (<code>unsigned long</code>, <code>uint64_t</code>, <code>size_t</code>, or <code>uintptr_t</code>), return a <code>ctypes.UInt64</code> object with value ''x''. | |||
* If ''x'' is of type <code>char16_t</code>, return a JavaScript string of length 1 containing the value of ''x'' (like <code>String.fromCharCode(x)</code>). | |||
* If ''x'' is of any other character type, return the JavaScript number equal to its integer value. (This is sensitive to the signedness of the character type. Also, we assume no character types are so wide that they don't fit into a JavaScript number.) | |||
* Otherwise ''x'' is of an array, struct, or pointer type. If the argument ''x'' is already a <code>CData</code> object, return it. Otherwise allocate a buffer containing a copy of the C/C++ value ''x'', and return a <code>CData</code> object of the appropriate type referring to the object in the new buffer. | |||
Note that null C/C++ pointers do not convert to the JavaScript <code>null</code> value. ''(Open issue: Should we? Is there any value in retaining the type of a particular null pointer?)'' | |||
''(Arrays of characters do not convert to JavaScript strings. Rationale: Suppose <code>x</code> is a <code>CData</code> object of a struct type with a member <code>a</code> of type <code>char[10]</code>. Then <code>x.a[1]</code> should return the character in element 1 of the array, even if <code>x.a[0]</code> is a null character. Likewise, <code>x.a[0] = '\0';</code> should modify the contents of the array. Both are possible only if <code>x.a</code> is a <code>CData</code> object of array type, not a JavaScript string.)'' | |||
<code>'''ImplicitConvert(''val'', ''t'')'''</code> - Convert the JavaScript value ''val'' to a C/C++ value of type ''t''. This is called whenever a JavaScript value of any kind is passed to a parameter of a ctypes-declared function, passed to <code>''cdata''.value = ''val''</code>, or assigned to an array element or struct member, as in <code>''carray''[''i''] = ''val''</code> or <code>''cstruct''.''member'' = ''val''</code>. | |||
This function is intended to lose precision only when there is no reasonable alternative. It generally does not coerce values of one type to another type. | |||
C/C++ values of all supported types round trip through <code>ConvertToJS</code> and <code>ImplicitConvert</code> without any loss of data. That is, for any C/C++ value ''v'' of type ''t'', <code>ImplicitConvert(ConvertToJS(''v''), ''t'') </code> produces a copy of ''v''. ''(Note that not all JavaScript can round-trip to C/C++ and back in an analogous way. JavaScript primitive numbers can round-trip to <code>double</code> on all current platforms, <code>Int64</code> objects to <code>int64_t</code>, JavaScript booleans to <code>bool</code>, and so on. But some JavaScript values, such as functions, cannot be <code>ImplicitConvert</code>ed to any C/C++ type without loss of data.)'' | |||
''t'' must not be <code>void</code> or an array type with unspecified length. ''(Rationale: C/C++ variables and parameters cannot have such types. The parameter of a function declared <code>int f(int x[])</code> is <code>int *</code>, not <code>int[]</code>.)'' | |||
* First, if ''val'' is a <code>CData</code> object of type ''u'' and <code>SameType(''t'', ''u'')</code>, return the current value of the C/C++ object referred to by ''val''. Otherwise the behavior depends on the target type ''t''. | |||
* If ''t'' is <code>ctypes.bool</code>: | |||
:* If ''val'' is a boolean, return the corresponding C/C++ boolean value. | |||
:* If ''val'' is the number +0 or -0, return <code>false</code>. | |||
:* If ''val'' is the number 1, return <code>true</code>. | |||
:* Otherwise fail. | |||
* If ''t'' is a numeric type: | |||
:* If ''val'' is a boolean, the result is a 0 or 1 of type ''t''. | |||
:* If ''val'' is a <code>CData</code> object of a numeric type, and every value of that type is precisely representable in type ''t'', the result is a precise representation of the value of ''val'' in type ''t''. (This is more conservative than the implicit integer conversions in C/C++ and more conservative than what we do if ''val'' is a JavaScript number. This is sensitive to the signedness of the two types.) | |||
:* If ''val'' is a number that can be exactly represented as a value of type ''t'', the result is that value. | |||
:* If ''val'' is an <code>Int64</code> or <code>UInt64</code> object whose value can be exactly represented as a value of type ''t'', the result is that value. | |||
:* If ''val'' is a number and ''t'' is a floating-point type, the result is the <code>jsdouble</code> represented by ''val'', cast to type ''t''. (This can implicitly lose bits of precision. The rationale is to allow the user to pass values like 1/3 to <code>float</code> parameters.) | |||
:* Otherwise fail. | |||
* If ''t'' is <code>ctypes.char16_t</code>: | |||
:* If ''val'' is a string of length 1, the result is the 16-bit unsigned value of the code unit in the string. <code>''val''.charCodeAt(0)</code>. | |||
:* If ''val'' is a number that can be exactly represented as a value of type <code>char16_t</code> (that is, an integer in the range 0 ≤ ''val'' < 2<sup>16</sup>), the result is that value. | |||
:* Otherwise fail. | |||
* If ''t'' is any other character type: | |||
:* If ''val'' is a string: | |||
::* If the 16-bit elements of ''val'' are not the UTF-16 encoding of a single Unicode character, fail. ''(Open issue: If we support <code>wchar_t</code> we may want to allow unpaired surrogate code points to pass through without error.)'' | |||
::* If that Unicode character can be represented by a single character of type ''t'', the result is that character. ''(Open issue: Unicode conversions.)'' | |||
::* Otherwise fail. | |||
:* If ''val'' is a number that can be exactly represented as a value of type ''t'', the result is that value. (This is sensitive to the signedness of ''t''.) | |||
:* Otherwise fail. | |||
* If ''t'' is a pointer type: | |||
:* If ''val'' is <code>null</code>, the result is a C/C++ <code>NULL</code> pointer of type ''t''. | |||
:* If ''val'' is a <code>CData</code> object of array type ''u'' and either ''t'' is <code>ctypes.voidptr_t</code> or <code>SameType(''t''.targetType, ''u''.elementType)</code>, return a pointer to the first element of the array. | |||
:* If ''t'' is <code>ctypes.voidptr_t</code> and ''val'' is a <code>CData</code> object of pointer type, return the value of the C/C++ pointer in ''val'', cast to <code>void *</code>. | |||
:* Otherwise fail. ''(Rationale: We don't convert strings to pointers yet; see the "Auto-converting strings" section below. We don't convert JavaScript arrays to pointers because this would have to allocate a C array implicitly, raising issues about who should deallocate it, and when, and how they know it's their responsibility.)'' | |||
* If ''t'' is an array type: | |||
:* If ''val'' is a JavaScript string: | |||
::* If <code>''t''.elementType</code> is <code>char16_t</code> and <code>''t''.length >= ''val''.length</code>, the result is an array of type ''t'' whose first <code>''val''.length</code> elements are the 16-bit elements of ''val''. If <code>''t''.length > ''val''.length</code>, then element <code>''val''.length</code> of the result is a null character. The values of the rest of the array elements are unspecified. | |||
::* If <code>''t''.elementType</code> is an 8-bit character type: | |||
:::* If ''t'' is not well-formed UTF-16, fail. | |||
:::* Let ''s'' = a sequence of bytes, the result of converting ''val'' from UTF-16 to UTF-8. | |||
:::* Let ''n'' = the number of bytes in ''s''. | |||
:::* If <code>''t''.length < ''n''</code>, fail. | |||
:::* The result is an array of type ''t'' whose first ''n'' elements are the 8-bit values in ''s''. If <code>''t''.length > ''n''</code>, then element ''n'' of the result is 0. The values of the rest of the array elements are unspecified. | |||
::* Otherwise fail. | |||
:* If ''val'' is a JavaScript array object: | |||
::* If <code>''val''.length</code> is not a nonnegative integer, fail. | |||
::* If <code>''val''.length !== ''t''.length</code>, fail. | |||
::* Otherwise, the result is a C/C++ array of <code>''val''.length</code> elements of type <code>''t''.elementType</code>. Element ''i'' of the result is <code>ImplicitConvert(''val''[''i''], ''t''.elementType)</code>. | |||
:* Otherwise fail. ''(Rationale: The clause "If ''val'' is a JavaScript array object" requires some justification. If we allowed arbitrary JavaScript objects that resemble arrays, that would include CData objects of array type. Consequently, <code>arr1.value = arr2</code> where <code>arr1</code> is of type <code>ctypes.uint8_t.array(30)</code> and <code>arr2</code> is of type <code>ctypes.int.array(30)</code> would work as long as the values in <code>arr2</code> are small enough. We considered this conversion too astonishing and too error-prone.)'' | |||
* Otherwise ''t'' is a struct type. | |||
:* If ''val'' is a JavaScript object that is not a <code>CData</code> object: | |||
::* If the enumerable own properties of ''val'' are exactly the names of the members of the struct ''t'', the result is a C/C++ struct of type ''t'', each of whose members is <code>ImplicitConvert(''val''[''the member name''], ''the type of the member'')</code>. | |||
::* Otherwise fail. | |||
:* Otherwise fail. | |||
<code>'''ExplicitConvert(''val'', ''t'')'''</code> - Convert the JavaScript value ''val'' to a C/C++ value of type ''t'', a little more forcefully than <code>ImplicitConvert</code>. | |||
This is called when a JavaScript value is passed as a parameter when calling a type, as in <code>''t''(''val'')</code> or <code>new ''t''(''val'')</code>. | |||
* If <code>ImplicitConvert(''val'', ''t'')</code> succeeds, use that result. Otherwise: | |||
* If ''t'' is <code>ctypes.bool</code>, the result is the C/C++ boolean value corresponding to <code>ToBoolean(''val'')</code>, where the operator <code>ToBoolean</code> is as defined in the ECMAScript standard. ''(This is a bit less strict than the conversion behavior specified for numeric types below. This is just for convenience: the operators <code>&&</code> and <code>||</code>, which produce a boolean value in C/C++, do not always do so in JavaScript.)'' | |||
* If ''t'' is an integer or character type and ''val'' is an infinity or NaN, the result is a 0 of type ''t''. | |||
* If ''t'' is an integer or character type and ''val'' is a finite number, the result is the same as casting the <code>jsdouble</code> value of ''val'' to type ''t'' with a C-style cast. ''(I think this basically means, start with ''val'', discard the fractional part, convert the integer part to a bit-pattern, and mask off whatever doesn't fit in type ''t''. But whatever C does is good enough for me. --jorendorff)'' | |||
* If ''t'' is an integer or character type and ''val'' is an <code>Int64</code> or <code>UInt64</code> object, the result is the same as casting the <code>int64_t</code> or <code>uint64_t</code> value of ''val'' to type ''t'' with a C-style cast. | |||
* If ''t'' is a pointer type and ''val'' is a number, <code>Int64</code> object, or <code>UInt64</code> object that can be exactly represented as an <code>intptr_t</code> or <code>uintptr_t</code>, the result is the same as casting that <code>intptr_t</code> or <code>uintptr_t</code> value to type ''t'' with a C-style cast. | |||
* If ''t'' is an integer type (not a character type) and ''val'' is a string consisting entirely of an optional minus sign, followed by either one or more decimal digits or the characters "0x" or "0X" and one or more hexadecimal digits, then the result is the same as casting the integer named by ''val'' to type ''t'' with a C-style cast. | |||
* Otherwise fail. | |||
'''<code>SameType(''t'', ''u'')</code>''' - True if ''t'' and ''u'' represent the same C/C++ type. | |||
*If ''t'' and ''u'' represent the same built-in type, even <code>void</code>, return true. | |||
*If they are both pointer types, return <code>SameType(''t''.targetType, ''u''.targetType)</code>. | |||
*If they are both array types, return <code>SameType(''t''.elementType, ''u''.elementType) && ''t''.length === ''u''.length</code>. | |||
*If they are both struct types, return <code>''t'' === ''u''</code>. | |||
*Otherwise return false. | |||
''(<code>SameType(int, int32_t)</code> is false. Rationale: As it stands, <code>SameType</code> behaves the same on all platforms. By making types match if they are typedef'd on the current platform, we could make e.g. <code>ctypes.int.ptr</code> and <code>ctypes.int32_t.ptr</code> compatible on platforms where we just have <code>typedef int int32_t</code>. But it was unclear how much that would matter in practice, balanced against cross-platform consistency. We might reverse this decision.)'' | |||
= Examples = | |||
Cu.import("ctypes"); // imports the global ctypes object | |||
// searches the path and opens "libmylib.so" on linux, | |||
// "libmylib.dylib" on mac, and "mylib.dll" on windows | |||
let mylib = ctypes.open("mylib", ctypes.SEARCH); | |||
// declares the C function: | |||
// int32_t myfunc(int32_t); | |||
let myfunc = mylib.declare("myfunc", ctypes.default_abi, | |||
ctypes.int32_t, ctypes.int32_t); | |||
let ret = myfunc(2); // calls myfunc | |||
Note that for simple types (integers and characters), we will autoconvert the argument at call time - there's no need to pass in a <code>ctypes.int32_t</code> 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. | |||
Here is how to create an object of type <code>int32_t</code>: | |||
let i = new ctypes.int32_t; // new int32_t object with default value 0 | |||
This allocates a new C++ object of type <code>int32_t</code> (4 bytes of memory), zeroes it out, and returns a JS object that manages the allocated memory. Whenever the JS object is garbage-collected, the allocated memory will be automatically freed. | |||
Of course you don't normally need to do this, as js-ctypes will autoconvert JS numbers to various C/C++ types for you: | |||
let myfunc = mylib.declare("myfunc", ctypes.default_abi, | |||
ctypes.int32_t, ctypes.int32_t); | |||
let ret = myfunc(i); | |||
print(typeof ret); // The result is a JavaScript number. | |||
'''number''' | |||
<code>ctypes.int32_t</code> is a <code>CType</code>. Like all other CTypes, it can be used for type specification when passed as an object, as above. (This will work for user-defined <code>CTypes</code> such as structs and pointers also - see later.) | |||
The object created by <code>new ctypes.int32_t</code> is called a <code>CData</code> object, and they are described in detail in the "<code>CData</code> objects" section above. | |||
Opaque pointers: | |||
// A new opaque pointer type. | |||
FILE_ptr = new ctypes.StructType("FILE").ptr; | |||
let fopen = mylib.declare("fopen", ctypes.default_abi, | |||
FILE_ptr, ctypes.char.ptr, ctypes.char.ptr); | |||
let file = fopen("foo", "r"); | |||
if (file.isNull()) | |||
throw "fopen failed"; | |||
file.contents(); // TypeError: type is unknown | |||
''(Open issue: <code>fopen("foo", "r")</code> does not work under js-ctypes as currently specified.)'' | |||
Declaring a struct: | |||
// C prototype: struct s_t { int32_t a; int64_t b; }; | |||
const s_t = new ctypes.StructType("s_t", [{ a: Int32 }, { b: Int64 }]); | |||
let myfunc = mylib.declare("myfunc", ctypes.default_abi, ctypes.int32_t, s_t); | |||
let s = new s_t(10, 20); | |||
This creates an s_t object which allocates enough memory for the whole struct, creates getters and setters to access the binary fields via their offset, and assigns the values 10 and 20 to the fields. The new object's prototype is <code>s_t.prototype</code>. | |||
let i = myfunc(0, s); // checks the type of s | |||
Nested structs: | |||
const u_t = ctypes.StructType("u_t", [{ 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. | |||
An out parameter: | |||
// allocate sizeof(uint32_t)==4 bytes, | |||
// initialize to 5, and return a new CData object | |||
let i = new ctypes.uint32_t(5); | |||
// Declare a C function with an out parameter. | |||
const getint = ctypes.declare("getint", ctypes.abi.default, | |||
ctypes.void_t, ctypes.uint32_t.ptr); | |||
getint(i.address()); // explicitly take the address of allocated buffer | |||
(Python ctypes has <code>byref(i)</code> as an alternative to <code>i.address()</code>, but we do not expect users to do the equivalent of <code>from ctypes import *</code>, and <code>setint(ctypes.byref(i))</code> is a bit much.) | |||
Pointers: | |||
// Declare a C function that returns a pointer. | |||
const getintp = ctypes.declare("getintp", ctypes.abi.default, | |||
ctypes.uint32_t.ptr); | |||
let p = getintp(); // A CData object that holds the returned uint32_t * | |||
// cast from (uint32_t *) to (uint8_t *) | |||
let q = ctypes.cast(p, ctypes.uint8_t.ptr); | |||
// first byte of buffer | |||
let b0 = q.contents(); // an integer, 0 <= b0 < 256 | |||
Struct fields: | |||
const u_t = new ctypes.StructType('u_t', | |||
[[ctypes.uint32_t, 'x'], [ctypes.uint32_t, 'y']]); | |||
// allocates sizeof(2*uint32_t) and creates a CData object | |||
let u = new u_t(5, 10); | |||
u.x = 7; // setter for u.x modifies field | |||
let i = u.y; // getter for u.y returns ConvertToJS(reference to u.y) | |||
print(i); // ...which is the primitive number 10 | |||
'''10''' | |||
i = 5; // doesn't touch u.y | |||
print(u.y); | |||
'''10''' | |||
const v_t = new ctypes.StructType('v_t', | |||
[[u_t, 'u'], [ctypes.uint32_t, 'z']]); | |||
// allocates 12 bytes, zeroes them out, and creates a CData object | |||
let v = new v_t; | |||
let w = v.u; // ConvertToJS(reference to v.u) returns CData object | |||
w.x = 3; // invokes setter | |||
setint(v.u.x); // TypeError: setint argument 1 expects type uint32_t *, got int | |||
let p = v.u.addressOfField('x'); // pointer to v.u.x | |||
setint(p); // ok - manually pass address | |||
64-bit integers: | |||
// Declare a function that returns a 64-bit unsigned int. | |||
const getfilesize = mylib.declare("getfilesize", ctypes.default_abi, | |||
ctypes.uint64_t, ctypes.char.ptr); | |||
// This autoconverts to a UInt64 object, not a JS number, even though the | |||
// file is presumably much smaller than 4GiB. Converting to a different type | |||
// each time you call the function, depending on the result value, would be | |||
// worse. | |||
let s = getfilesize("/usr/share/dict/words"); | |||
print(s instanceof ctypes.UInt64); | |||
'''true''' | |||
print(s < 1000000); // Because s is an object, not a number, | |||
'''false''' // JS lies to you. | |||
print(s >= 1000000); // Neither of these is doing what you want, | |||
'''false''' // as evidenced by the bizarre answers. | |||
print(s); // It has a nice .toString() method at least! | |||
'''931467''' | |||
// There is no shortcut. To get an actual JS number out of a | |||
// 64-bit integer, you have to use the ctypes.{Int64,UInt64}.{hi,lo} | |||
// functions. | |||
print(ctypes.UInt64.lo(s)) | |||
'''931467''' | |||
// (OK, I lied. There is a shortcut. You can abuse the .toString() method. | |||
// WARNING: This can lose precision!) | |||
print(Number(s.toString())) | |||
'''931467''' | |||
let i = new ctypes.int64_t(5); // a new 8-byte buffer | |||
let j = i; // another variable referring to the same CData object | |||
j.value = 6; // invokes setter on i, auto-promotes 6 to Int64 | |||
print(typeof j.value) // but j.value is still an Int64 object | |||
'''object''' | |||
print(j.value instanceof ctypes.Int64) | |||
'''true''' | |||
print(j.value); | |||
'''6''' | |||
const m_t = new ctypes.StructType( | |||
'm_t', [[ctypes.int64_t, 'x'], [ctypes.int64_t, 'y']]); | |||
let m = new m_t; | |||
const getint64 = ctypes.declare("getint64", ctypes.abi.default, | |||
ctypes.void_t, ctypes.Pointer(ctypes.int64_t)); | |||
getint64(m.x); // TypeError: getint64 argument 1 expected type int64_t *, | |||
// got Int64 object | |||
// (because m.x's getter autoconverts to an Int64 object) | |||
getint64(ctypes.addressOfField(m, 'x')); // works | |||
''(Open issue: As above, the implicit conversion from JS string to <code>char *</code> in <code>getfilesize("/usr/share/dict/words")</code> does not work in js-ctypes as specified.)'' | |||
''(TODO - make this a real example:)'' | |||
let i1 = ctypes.int32_t(5); | |||
let i2 = ctypes.int32_t(); | |||
i2.value = i1 // i2 and i1 have separate binary storage, this is memcpy | |||
//you can copy the guts of one struct to another, etc. | |||
=Future directions= | |||
==Callbacks== | |||
The libffi part of this is presumably not too bad. Issues: | |||
'''Lifetimes.''' C/C++ makes it impossible to track an object pointer. Both JavaScript's GC and experience with C/C++ function pointers will tend to discourage users from caring about function lifetimes. | |||
I think the best solution to this problem is to put the burden of keeping the function alive entirely on the client. | |||
'''Finding the right context to use.''' If we burn the cx right into the libffi closure, it will crash when called from a different thread or after the cx is destroyed. If we take a context at random from some internal JSAPI structure, it might be thread-safe, but the context's options and global will be random, which sounds dangerous. Perhaps ctypes itself can create a context per thread, on demand, for the use of function pointers. In a typical application, that would only create one context, if any. | |||
==Converting strings== | |||
I think we want an explicit API for converting strings, very roughly: | |||
<code>CData</code> objects of certain pointer and array types have methods for reading and writing Unicode strings. These methods are present if the target or element type is an 8-bit character or integer type. | |||
'''<code>''cdata''.readString(''[encoding[, length]]'')</code>''' - Read bytes from ''cdata'' and convert them to Unicode characters using the specified ''encoding'', returning a string. Specifically: | |||
* If ''cdata'' is an array, let ''p'' = a pointer to the first element. Otherwise ''cdata'' is a pointer; let ''p'' = the value of ''cdata''. | |||
* If ''encoding'' is <code>undefined</code> or omitted, the selected encoding is UTF-8. Otherwise, if ''encoding'' is a string naming a known character encoding, that encoding is selected. Otherwise throw a <code>TypeError</code>. | |||
* If ''length'' is a size value, ''cdata'' is an array, and <code>''length'' > ''cdata''.length</code>, then throw a <code>TypeError</code>. | |||
* Otherwise, if ''length'' is a size value, take exactly ''length'' bytes starting at ''p'' and convert them to Unicode characters according to the selected encoding. ''(Open issue: Error handling.)'' Return a JavaScript string containing the Unicode characters, represented in UTF-16. ''(The result may contain null characters.)'' | |||
* Otherwise, if ''length'' is <code>undefined</code> or omitted, convert bytes starting at ''p'' to Unicode characters according to the selected encoding. Stop when the end of the array is reached (if ''cdata'' is an array) or when a null character (U+0000) is found. ''(Open issue: Error handling.)'' Return a JavaScript string containing the Unicode characters, represented in UTF-16. ''(If ''cdata'' is a pointer and there is no trailing null character, this can crash.)'' | |||
* Otherwise throw a <code>TypeError</code>. | |||
'''<code>''cdata''.writeString(''s'', ''[encoding[, length]]'')</code>''' - Determine the starting pointer ''p'' as above. If ''s'' is not a well-formed UTF-16 string, throw a <code>TypeError</code>. ''(Open issue: Error handling.)'' Otherwise convert ''s'' to bytes in the specified ''encoding'' (default: UTF-8) and write at most ''length'' - 1 bytes, or all the converted bytes, if ''length'' is <code>undefined</code> or omitted, to memory starting at ''p''. Write a converted null character after the data. Return the number of bytes of data written, not counting the terminating null character. | |||
''(Open issue: ''<code>''cdata''.writeString(...)</code>'' is awkward for the case where you want an autosized <code>ctypes.char.array()</code> to hold the converted data. If <code>''cdata''</code> happens to be too small for the resulting string, and you don't supply ''length'', you crash; and if you do supply ''length'', you don't know whether conversion was halted because the target array was of insufficient length.)'' | |||
''(Open issue: As proposed, these are not suitable for working with encodings where a zero byte might not indicate the end of text. For example, a string encoded in UTF-16 will typically contain a lot of zero bytes. Unfortunately, in the case of readString, the underlying library demands the length up front.)'' | |||
''(Open issue: These methods offer no error handling options, which is pretty weak. Real-world code often wants to allow a few characters to be garbled rather than fail. For now we will likely be limited to whatever the underlying codec library, <code>nsIScriptableUnicodeConverter</code>, can do.)'' | |||
''(Open issue: 16-bit versions too, for UTF-16?)'' | |||
==isNull== | |||
If we do not convert NULL pointers to JS <code>null</code> (and I may have changed my mind about this) then we need: | |||
'''<code>''cptr''.isNull()</code>''' - Return <code>true</code> if ''cptr''<nowiki>'</nowiki>s value is a null pointer, <code>false</code> otherwise. | |||
==Auto-converting strings== | |||
There are several issues: | |||
'''Lifetimes.''' This problem arises when autoconverting from JS to C/C++ only. | |||
When passing a string to a foreign function, like <code>foo(s)</code>, what is the lifetime of the autoconverted pointer? We're comfortable with guaranteeing <code>s</code> for the duration of the call. But then there are situations like | |||
TenStrings = char.ptr.array(10); | |||
var arr = new TenStrings(); | |||
arr[0] = s; // What is the lifetime of the data arr[0] points to? | |||
' | The more implicit conversion we allow, the greater a problem this is; it's a tough trade-off. | ||
'''Non-null-terminated strings.''' This problem arises when autoconverting from C/C++ to JS only. It applies to C/C++ character arrays as well as pointers (but it's worse when dealing with pointers). | |||
In C/C++, the type <code>char *</code> effectively promises nothing about the pointed-to data. Autoconverting would make it hard to use APIs that return non-null-terminated strings (or structs containing <code>char *</code> pointers that aren't logically strings). The workaround would be to declare them as a different type. | |||
'''Unicode.''' This problem does not apply to conversions between JS strings and <code>char16_t</code> arrays or pointers; only <code>char</code> arrays or pointers. | |||
Converting both ways raises issues about what encoding should be assumed. We assume JS strings are UTF-16 and <code>char</code> strings are UTF-8, which is not the right thing on Windows. However Windows offers a lot of APIs that accept 16-bit strings and, for those, <code>char16_t</code> is the right thing. | |||
'' | '''Casting away const.''' This problem arises only when converting from a JS string to a C/C++ pointer type. The string data must not be modified, but the C/C++ types <code>char *</code> and <code>char16_t *</code> suggest that the referent might be modified. | ||