diff --git a/INTEROP.md b/INTEROP.md index dd71ff65c13..4387edcc6ea 100644 --- a/INTEROP.md +++ b/INTEROP.md @@ -26,14 +26,28 @@ Build the dependencies and the compiler (see `README.md`). Prepare stubs for the system sockets library: - cd samples/socket - ../../dist/bin/cinterop -def src/main/c_interop/sockets.def \ - -o sockets +
+ +```bash + +cd samples/socket +../../dist/bin/cinterop -def src/main/c_interop/sockets.def \ + -o sockets +``` + +
Compile the echo server: - ../../dist/bin/kotlinc src/main/kotlin/EchoServer.kt \ - -library sockets -o EchoServer +
+ +```bash +../../dist/bin/kotlinc src/main/kotlin/EchoServer.kt \ + -library sockets -o EchoServer +``` + +
+ This whole process is automated in `build.sh` script, which also support cross-compilation to supported cross-targets with `TARGET=raspberrypi ./build.sh` (`cross_dist` target must @@ -41,11 +55,23 @@ be executed first). Run the server: - ./EchoServer.kexe 3000 & +
+ +```bash +./EchoServer.kexe 3000 & +``` + +
Test the server by connecting to it, for example with telnet: - telnet localhost 3000 +
+ +```bash +telnet localhost 3000 +``` + +
Write something to console and watch server echoing it back. @@ -54,14 +80,27 @@ Write something to console and watch server echoing it back. To create bindings for a new library, start by creating `.def` file. Structurally it's a simple property file, looking like this: +
+ +```c +headers = zlib.h +compilerOpts = -std=c99 +``` + +
- headers = zlib.h - compilerOpts = -std=c99 Then run `cinterop` tool with something like (note that for host libraries not included in sysroot search paths for headers may be needed): - cinterop -def zlib.def -copt -I/opt/local/include -o zlib +
+ +```bash +cinterop -def zlib.def -copt -I/opt/local/include -o zlib +``` + +
+ This command will produce `zlib.klib` compiled library and `zlib-build/kotlin` directory containing Kotlin source code for the library. @@ -104,10 +143,15 @@ The globs are applied to the header paths relative to the appropriate include path elements, e.g. `time.h` or `curl/curl.h`. So if the library is usually included with `#include `, then it would probably be correct to filter headers with -``` + +
+ +```c headerFilter = SomeLibrary/** ``` +
+ If `headerFilter` is not specified, then all headers are included. #### Filtering by module maps @@ -119,12 +163,18 @@ describes the correspondence between header files and modules. When the module maps are available, the headers from the modules that are not included directly can be filtered out using experimental `excludeDependentModules` option of the `.def` file: -``` + +
+ +```c headers = OpenGL/gl.h OpenGL/glu.h GLUT/glut.h compilerOpts = -framework OpenGL -framework GLUT excludeDependentModules = true ``` +
+ + When both `excludeDependentModules` and `headerFilter` are used, they are applied as intersection. @@ -136,7 +186,9 @@ additional header file with these declarations, you can include them directly to the end of the `.def` file, after separating line, containing only the separator sequence `---`: -``` +
+ +```c headers = errno.h --- @@ -146,6 +198,8 @@ static inline int getErrno() { } ``` +
+ Note that this part of the `.def` file is treated as part of the header file, so functions with body should be declared as `static`. The declarations are parsed after including the files from `headers` list. @@ -157,11 +211,15 @@ rather that assuming it is available within the user environment. To include a static library into `.klib` use `staticLibrary` and `libraryPaths` clauses. For example: -``` +
+ +```c staticLibraries = libfoo.a libraryPaths = /opt/local/lib /usr/local/opt/curl/lib ``` +
+ When given the above snippet the `cinterop` tool will search `libfoo.a` in `/opt/local/lib` and `/usr/local/opt/curl/lib`, and if found include the library binary into `klib`. @@ -205,14 +263,21 @@ C null pointer is represented as Kotlin's `null`, and the pointer type `CPointer` is not nullable, but the `CPointer?` is. The values of this type support all Kotlin operations related to handling `null`, e.g. `?:`, `?.`, `!!` etc: -``` + +
+ +```kotlin val path = getenv("PATH")?.toKString() ?: "" ``` +
+ Since the arrays are also mapped to `CPointer`, it supports `[]` operator for accessing values by index: -``` +
+ +```kotlin fun shift(ptr: CPointer, length: Int) { for (index in 0 .. length - 2) { ptr[index] = ptr[index + 1] @@ -220,6 +285,8 @@ fun shift(ptr: CPointer, length: Int) { } ``` +
+ The `.pointed` property for `CPointer` 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. @@ -230,48 +297,79 @@ the Kotlin binding accepts any `CPointer`. Casting any pointer (including `COpaquePointer`) can be done with `.reinterpret`, e.g.: -``` + +
+ +```kotlin val intPtr = bytePtr.reinterpret() ``` + +
+ or -``` + +
+ +```kotlin val intPtr: CPointer = bytePtr.reinterpret() ``` +
+ As in C, those reinterpret casts are unsafe and could potentially lead to subtle memory problems in an application. Also there are unsafe casts between `CPointer?` and `Long` available, provided by `.toLong()` and `.toCPointer()` extension methods: -``` + +
+ +```kotlin val longValue = ptr.toLong() val originalPtr = longValue.toCPointer() ``` +
+ Note that if the type of the result is known from the context, the type argument can be omitted as usual due to type inference. ### Memory allocation ### The native memory can be allocated using `NativePlacement` interface, e.g. -``` + +
+ +```kotlin val byteVar = placement.alloc() ``` + +
+ or -``` + +
+ +```kotlin val bytePtr = placement.allocArray(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: -``` +
+ +```kotlin val buffer = nativeHeap.allocArray(size) 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, @@ -280,7 +378,10 @@ 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 -``` + +
+ +```kotlin val fileSize = memScoped { val statBuf = alloc() val error = stat("/", statBuf.ptr) @@ -288,6 +389,8 @@ val fileSize = memScoped { } ``` +
+ ### Passing pointers to bindings ### Although C pointers are mapped to `CPointer` type, the C function @@ -309,18 +412,28 @@ methods are provided: For example: C: -``` + +
+ +```c void foo(int* elements, int count); ... int elements[] = {1, 2, 3}; foo(elements, 3); ``` +
+ Kotlin: -``` + +
+ +```kotlin foo(cValuesOf(1, 2, 3), 3) ``` +
+ ### Working with the strings ### Unlike other pointers, the parameters of type `const char*` are represented as @@ -334,19 +447,33 @@ manually: * `val String.cstr: CValuesRef`. 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. To skip automatic conversion and ensure raw pointers are used in the bindings `noStringConversion` statement in `.def` file could be used, i.e. + +
+ +```c +noStringConversion = LoadCursorA LoadCursorW ``` - noStringConversion = LoadCursorA LoadCursorW -``` + +
+ This way any value of type `CPointer` could be passed as an argument of `const char*` type. If Kotlin string shall me passed code like that could be used: + +
+ ```kotlin memScoped { LoadCursorA(null, "cursor.bmp".cstr.ptr) // for ASCII version @@ -354,12 +481,17 @@ memScoped { } ``` +
+ ### Scope-local pointers ### It is possible to create scope-stable pointer of C representation of `CValues` instance using `CValues.ptr` extension property available under `memScoped { ... }`. It allows to use APIs which requires C pointers with lifetime bound to certain `MemScope`. For example: -``` + +
+ +```kotlin memScoped { items = arrayOfNulls?>(6) arrayOf("one", "two").forEachIndexed { index, value -> items[index] = value.cstr.ptr } @@ -367,6 +499,9 @@ memScoped { ... } ``` + +
+ In this example all values passed to the C API `new_menu()` have lifetime of innermost `memScope` it belongs to. Once control flow will leave `memScoped` scope C pointers become invalid. @@ -388,10 +523,16 @@ methods available: `CValue` to the memory, and then runs the passed lambda with this placed value `T` as receiver. So to read a single field, the following code can be used: - ``` + +
+ + ```kotlin val fieldValue = structValue.useContents { field } ``` +
+ + ### Callbacks ### To convert Kotlin function to pointer to C function, @@ -417,28 +558,43 @@ the callback itself, to safely swim from Kotlin to Kotlin through the C world. Such wrapping is possible with `StableRef` class. To wrap the reference: -``` + +
+ +```kotlin val stablePtr = StableRef.create(kotlinReference) val voidPtr = stablePtr.value ``` + +
+ where the `voidPtr` is `COpaquePointer` and can be passed to the C function. To unwrap the reference: -``` +
+ +```kotlin val stablePtr = StableRef.fromValue(voidPtr) val kotlinReference = stablePtr.get() ``` + +
+ where `kotlinReference` is the original wrapped reference (however it's type is `Any` so it may require casting). The created `StableRef` should eventually be manually disposed using `.dispose()` method to prevent memory leaks: -``` +
+ +```kotlin stablePtr.dispose() ``` +
+ After that it becomes invalid, so `voidPtr` can't be unwrapped anymore. See `samples/libcurl` for more details. @@ -451,7 +607,9 @@ wrapping with supported declarations. E.g. function-like macro `FOO` can be exposed as function `foo` by [adding the custom declaration](#adding-custom-declarations) to the library: -``` +
+ +```c headers = library/base.h --- @@ -461,6 +619,8 @@ static inline int foo(int arg) { } ``` +
+ ### Definition file hints ### The `.def` file supports several options for adjusting generated bindings. @@ -484,9 +644,12 @@ neither implicit integer casts nor C-style integer casts (e.g. `(size_t) intValue`), so to make writing portable code in such cases easier, `convert` method is provided: -``` +
+ +```kotlin fun ${type1}.convert<${type2}>(): ${type2} ``` +
where each of `type1` and `type2` must be an integral type, either signed or unsigned. @@ -497,11 +660,15 @@ methods, depending on `type`. The example of using `convert`: -``` +
+ +```kotlin fun zeroMemory(buffer: COpaquePointer, size: Int) { memset(buffer, 0, size.convert()) } ``` +
+ Also the type parameter can be inferred automatically and thus may be omitted in some cases.