koffi
Version:
Fast and easy-to-use dynamic C FFI (foreign function interface) for Node.js
152 lines (121 loc) • 7.56 kB
Markdown
While the C standard allows for variation in the size of most integer types, Koffi enforces the same definition for most primitive types, listed below:
C type | JS type | Bytes | Signedness | Note
----------------------------- | ---------------- | ----- | ---------- | ---------------------------
void | Undefined | 0 | | Only valid as a return type
int8, int8_t | Number (integer) | 1 | Signed |
uint8, uint8_t | Number (integer) | 1 | Unsigned |
char | Number (integer) | 1 | Signed |
uchar, unsigned char | Number (integer) | 1 | Unsigned |
char16, char16_t | Number (integer) | 2 | Signed |
int16, int16_t | Number (integer) | 2 | Signed |
uint16, uint16_t | Number (integer) | 2 | Unsigned |
short | Number (integer) | 2 | Signed |
ushort, unsigned short | Number (integer) | 2 | Unsigned |
char32, char32_t | Number (integer) | 4 | Signed |
int32, int32_t | Number (integer) | 4 | Signed |
uint32, uint32_t | Number (integer) | 4 | Unsigned |
int | Number (integer) | 4 | Signed |
uint, unsigned int | Number (integer) | 4 | Unsigned |
int64, int64_t | Number (integer) | 8 | Signed |
uint64, uint64_t | Number (integer) | 8 | Unsigned |
longlong, long long | Number (integer) | 8 | Signed |
ulonglong, unsigned long long | Number (integer) | 8 | Unsigned |
float32 | Number (float) | 4 | |
float64 | Number (float) | 8 | |
float | Number (float) | 4 | |
double | Number (float) | 8 | |
Koffi also accepts BigInt values when converting from JS to C integers. If the value exceeds the range of the C type, Koffi will convert the number to an undefined value. In the reverse direction, BigInt values are automatically used when needed for big 64-bit integers.
Koffi defines a few more types that can change size depending on the OS and the architecture:
C type | JS type | Signedness | Note
---------------- | ---------------- | ----------- | ------------------------------------------------
bool | Boolean | | Usually one byte
long | Number (integer) | Signed | 4 or 8 bytes depending on platform (LP64, LLP64)
ulong | Number (integer) | Unsigned | 4 or 8 bytes depending on platform (LP64, LLP64)
unsigned long | Number (integer) | Unsigned | 4 or 8 bytes depending on platform (LP64, LLP64)
intptr | Number (integer) | Signed | 4 or 8 bytes depending on register width
intptr_t | Number (integer) | Signed | 4 or 8 bytes depending on register width
uintptr | Number (integer) | Unsigned | 4 or 8 bytes depending on register width
uintptr_t | Number (integer) | Unsigned | 4 or 8 bytes depending on register width
wchar_t | Number (integer) | | 2 bytes on Windows, 4 bytes Linux, macOS and BSD
Primitive types can be specified by name (in a string) or through `koffi.types`:
```js
// These two lines do the same:
let struct1 = koffi.struct({ dummy: 'long' });
let struct2 = koffi.struct({ dummy: koffi.types.long });
```
Koffi can convert JS strings to and from UTF-8, UTF-16 or UTF-32.
Type name | Aliases | JS type | Conversion / encoding
------------------------------ | --------------- | ------- | ---------------------
const char \*, char \* | str, string | String | UTF-8
const char16_t \*, char16_t \* | str16, string16 | String | UTF-16
const char32_t \*, char32_t \* | str32, string32 | String | UTF-32
Koffi also supports wide strings (`wchar_t *` or `wstring`), which use:
- UTF-16 on Windows (where wchar_t is 2 bytes)
- UTF-32 on other platforms (where wchar_t is 4 bytes)
# Enum types
*New in Koffi 3.0*
C enumeration values are stored as integers. The underlying integer type is implementation-defined but Koffi tries to match usual platform behavior.
On POSIX platforms, Koffi follows the following rules:
- If no negative value exists: `unsigned int` by default, `uint64_t` if needed
- If any negative value exists: `int` by default, `int64_t` if needed
On Windows, things are simpler, and `int` is used all the time. Koffi will throw an error if any enumeration value does not fit in a 32-bit integer.
```js
// For OpenResult, the underlying type will be unsigned int on POSIX, int on Windows
const OpenResult = koffi.enumeration('OpenResult', {
Success: 0,
MissingFile: 1,
AccessDenied: 2,
OtherError: 3
});
// For RelativePosition, the underlying type will be int everywhere
const RelativePosition = koffi.enumeration('RelativePosition', {
Left: -1,
Center: 0,
Right: 1
});
// For IntLimits, the underlying type will be int64_t on POSIX, and fail on Windows
const Int64Limits = koffi.enumeration('Int64Limits', {
Min: -9223372036854775808n,
Max: 9223372036854775807n
});
```
> [!WARNING]
> This behavior may not match your compiler:
>
> - On POSIX platforms, GCC and Clang will use a short integer type if `-fshort-enums` is specified and the enumeration values fit in `short` or `unsigned short`.
> - On Windows, MSVC (and Clang) always use `int` even if some values do not fit, which matches what Koffi does... unless the compiler flag `/Zc:enumTypes` is set, maybe.
>
> Use an explicit type specifier to work around these problems, as shown below.
You can access the constants in `values` member of the type object.
```js
console.log(OpenResult.values.MissingFile); // Prints 1
console.log(RelativePosition.values.Left); // Prints -1
```
You can specify the storage type explicitly as the last argument: `koffi.enumeration(name, values, type)`.
```js
// This one explictly uses int64_t as the underlying type, despite the fact that the values fit inside an int.
const ExplicitEnum = koffi.enumeration('ExplicitEnum', {
Zero: 0,
One: 1,
Two: 2
}, 'int64_t');
```
Koffi defines a bunch of endian-sensitive types, which can be used when dealing with binary data (network payloads, binary file formats, etc.).
C type | Bytes | Signedness | Endianness
---------------------- | ----- | ---------- | -------------
int16_le, int16_le_t | 2 | Signed | Little Endian
int16_be, int16_be_t | 2 | Signed | Big Endian
uint16_le, uint16_le_t | 2 | Unsigned | Little Endian
uint16_be, uint16_be_t | 2 | Unsigned | Big Endian
int32_le, int32_le_t | 4 | Signed | Little Endian
int32_be, int32_be_t | 4 | Signed | Big Endian
uint32_le, uint32_le_t | 4 | Unsigned | Little Endian
uint32_be, uint32_be_t | 4 | Unsigned | Big Endian
int64_le, int64_le_t | 8 | Signed | Little Endian
int64_be, int64_be_t | 8 | Signed | Big Endian
uint64_le, uint64_le_t | 8 | Unsigned | Little Endian
uint64_be, uint64_be_t | 8 | Unsigned | Big Endian
You can find an example that uses these types to [extract information from a PNG header](pointers#handling-void-pointers) with transparent endian conversion.