Extend docs about interop

This commit is contained in:
SvyatoslavScherbina
2017-04-01 11:07:53 +03:00
committed by GitHub
parent 2d92cc8bda
commit 6350ad5c7b
+232
View File
@@ -77,3 +77,235 @@ output of config script with `--cflags` flag (maybe without exact paths).
Output of config script with `--libs` shall be passed as `-linkedArgs` `kotlinc`
flag value (quoted) when compiling.
## Using bindings ##
### Basic interop types ###
All supported C types have corresponding representations in Kotlin:
* Singed, unsigned integral and floating point types are mapped to their
Kotlin counterpart with the same width.
* Pointers and arrays are mapped to `CPointer<T>?`.
* Enums can be mapped to either Kotlin enum or integral values, depending on
heuristics and definition file hints (see "Definition file hints" below).
* Structs are mapped to types having fields available via dot notation,
i.e. `someStructInstance.field1`.
* `typedef`s are represented as `typealias`es.
Also any C type has the Kotlin type representing the lvalue of this type,
i.e. the value located in memory rather than simple immutable self-contained
value. Think C++ references, as similar concept.
For structs (and `typedef`s to structs) this representation is the main one
and has the same name as the struct itself, for Kotlin enums it is named
`${type}.Var`, for `CPointer<T>` it is `CPointerVar<T>`, and for most other
types it is `${type}Var`.
For those types that have both representations, the "lvalue" one has mutable
`.value` property for accessing value.
#### Pointer types ####
The type argument `T` of `CPointer<T>` must be one of the "lvalue" types
described above, e.g. the C type `struct S*` is mapped to `CPointer<S>`,
`int8_t*` is mapped to `CPointer<int_8tVar>`, and `char**` is mapped to
`CPointer<CPointerVar<ByteVar>>`.
C null pointer is represented as Kotlin's `null`, and the pointer type
`CPointer<T>` is not nullable, but the `CPointer<T>?` is. The values of this
type support all Kotlin operations related to handling `null`, e.g. `?:`, `?.`,
`!!` etc:
```
val path = getenv("PATH")?.toKString() ?: ""
```
Since the arrays are also mapped to `CPointer<T>`, it supports `[]` operator
for accessing values by index:
```
fun shift(ptr: CPointer<BytePtr>, length: Int) {
for (index in 0 .. length - 2) {
ptr[index] = ptr[index + 1]
}
}
```
The `.pointed` property for `CPointer<T>` returns the lvalue of type `T`,
pointed by this pointer. The reverse operation is `.ptr`: it takes the lvalue
and returns the pointer to it.
`void*` is mapped to `COpaquePointer` the special pointer type which is the
supertype for any other pointer type. So if the C function takes `void*`, then
the Kotlin binding accepts any `CPointer`.
Casting any pointer (including `COpaquePointer`) can be done with
`.reinterpret<T>`, e.g.:
```
val intPtr = bytePtr.reinterpret<IntVar>()
```
or
```
val intPtr: CPointer<IntVar> = bytePtr.reinterpret()
```
As in C, those reinterpret casts are unsafe and could potentially lead to
subtle memory problems in an application.
### Memory allocation ###
The native memory can be allocated using `NativePlacement` interface, e.g.
```
val byteVar = placement.alloc<ByteVar>()
```
or
```
val bytePtr = placement.allocArray<ByteVar>(5):
```
The most "natural" placement is object `nativeHeap`.
It corresponds to allocating native memory with `malloc` and provides additional
`.free()` operation to free allocated memory:
```
val buffer = nativeHeap.allocArray<ByteVar>(size)
<use buffer>
nativeHeap.free(buffer)
```
However the lifetime of allocated memory is often bound to lexical scope.
It is possible to define such scope with `memScoped { ... }`.
Inside the braces the temporary placement is available as implicit receiver,
so it is possible to allocate native memory with `alloc` and `allocArray`,
and the allocated memory will be automatically freed after leaving the scope.
For example, the C function returning values through pointer parameters can be
used like
```
val fileSize = memScoped {
val statBuf = alloc<statStruct>()
val error = stat("/", statBuf.ptr)
statBuf.st_size
}
```
### Passing pointers to bindings ###
Although C pointers are mapped to `CPointer<T>` type, the C function
pointer-typed parameters are mapped to `CValuesRef<T>`. When passing
`CPointer<T>` as the value of such parameter, it is passed to C function as is.
However, the sequence of values can be passed instead of pointer. In this case
the sequence is passed "by value", i.e. the C function receives the pointer to
the temporary copy of that sequence, which is valid only until the function returns.
The `CValuesRef<T>` representation of pointer parameters is designed to support
C array literals without explicit native memory allocation.
To construct the immutable self-contained sequence of C values, the following
methods are provided:
* `${type}Array.toCValues()`, where `type` is the Kotlin primitive type
* `Array<CPointer<T>?>.toCValues()`, `List<CPointer<T>?>.toCValues()`
* `cValuesOf(vararg elements: ${type})`, where `type` is primitive or pointer
For example:
C:
```
void foo(int* elements, int count);
...
int elements[] = {1, 2, 3};
foo(elements, 3);
```
Kotlin:
```
foo(cValuesOf(1, 2, 3), 3)
```
### Working with the strings ###
Unlike other pointers, the parameters of type `const char*` are represented as
Kotlin `String`. So it is possible to pass any Kotlin string to the binding
expecting C string.
There are also available some tools to convert between Kotlin and C strings
manually:
* `fun CPointer<ByteRef>.toKString(): String`
* `val String.cstr: CValuesRef<ByteRef>`.
To get the pointer, `.cstr` should be allocated in native memory, e.g.
```
val cString = kotlinString.cstr.getPointer(nativeHeap)
```
In all cases the C string is supposed to be encoded as UTF-8.
### Passing and receiving structs by value ###
When C function takes or returns a struct `T` by value, the corresponding
argument type or return type is represented as `CValue<T>`.
### Callbacks ###
To convert Kotlin function to pointer to C function,
`staticCFunction(::kotlinFunction)` can be used. Currently `staticCFunction`
heavily relies on type inference, so the expression `staticCFunction(...)`
should be either assigned to the variable having proper type explicitly
specified, or passed to the function, e.g.
```
glutDisplayFunc(staticCFunction(::display))
```
Note that some function types are not supported currently. For example,
it is not possible to get pointer to function that receives or returns structs
by value.
### Definition file hints ###
The `.def` file supports several options for adjusting generated bindings.
* `excludedFunctions` property value specifies a space-separated list of names
of functions that should be ignored. This may be required because a function
declared in C header is not generally guaranteed to be really callable, and
it is often hard or impossible to figure this out automatically. This option
can also be used to workaround a bug in the interop itself.
* `strictEnums` and `nonStrictEnums` properties values are space-separated
lists of the enums that should be generated as Kotlin enum or as integral
values correspondingly. If the enum is not included into any of these lists,
than it is generated according to the heuristics.
### Portability ###
Sometimes the C libraries have function parameters or struct fields of
platform-dependent type, e.g. `long` or `size_t`. Kotlin itself doesn't provide
neither implicit integer casts nor C-style integer casts (e.g.
`(size_t) intValue`), so to make writing portable code in such cases easier,
the following methods are provided:
* `fun ${type1}.sizeExtend<${type2}>(): ${type2}`
* `fun ${type1}.narrow<${type2}>(): ${type2}`
where each of `type1` and `type2` must be an integral type.
The `signExtend` converts the integer value to more wide, i.e. the result must
have the same or greater size.
The `narrow` converts the integer value to smaller one (possibly changing the
value due to loosing significant bits), so the result must have the same or
less size.
Any allowed `.signExtend<${type}>` or `.narrow<${type}>` have the same
semantics as one of the `.toByte`, `.toShort`, `.toInt` or `.toLong` methods,
depending on `type`.
The example of using `signExtend`:
```
fun zeroMemory(buffer: COpaquePointer, size: Int) {
memset(buffer, 0, size.signExtend<size_t>())
}
```
Also the type parameter can be inferred automatically and thus may be omitted
in some cases.