Switch branches/tags
Find file History

README.md

C to Kotlin/Native interoperability example

This example shows how to use Kotlin/Native programs from other execution environments, such as Python. Python has C native interface, which could be used to organize a bridge with the Kotlin/Native application. File kotlin_bridge.c contains translation code between Kotlin and Python lands. This demo works on Linux, Windows (64-bit) or macOS hosts.

To build and run the sample do the following:

  • Install Python with development headers, i.e. on Linux

    sudo apt install python-dev
    
  • Run build.sh, it will ask for superuser password to install Python extension. Use build.bat on Windows.

  • On macOS copy Kotlin binary to extension's directory and change install name with install_name_tool tool, i.e.

    sudo cp libserver.dylib  /Library/Python/2.7/site-packages/
    sudo install_name_tool /Library/Python/2.7/site-packages/kotlin_bridge.so \
          -change libserver.dylib @loader_path/libserver.dylib
    
  • On Linux copy Kotlin binary in some place where libraries could be loaded from, i.e.

    cp libserver.so /usr/local/lib/
    ldconfig
    

    or modify dynamic loader search path with

    export LD_LIBRARY_PATH=`pwd`
    
  • run Python code using Kotlin functionality with

    python src/main/python/main.py
    
  • it will show you result of using several Kotlin/Native APIs, accepting and returning both objects and primitive types

The example works as following. Kotlin/Native API is implemented in Server.kt, and we run Kotlin/Native compiler with -produce dynamic option. Compiler produces two artifacts: server_api.h which is C language API to all public functions and classes available in the application. libserver.dylib or libserver.so or server.dll shared object contains C bridge to all above APIs.

This C bridge looks like a C struct, reflecting all scopes in program, with operations available. For example, for class Server

   class Server(val prefix: String) {
          fun greet(session: Session) = "$prefix: Hello from Kotlin/Native in ${session}"
          fun concat(session: Session, a: String, b: String) = "$prefix: $a $b in ${session}"
          fun add(session: Session, a: Int, b: Int) = a + b + session.number
   }

following C API is produced

   typedef struct {
     server_KNativePtr pinned;
   } server_kref_demo_Session;
   typedef struct {
     server_KNativePtr pinned;
   } server_kref_demo_Server;

   typedef struct {
      /* Service functions. */
      void (*DisposeStablePointer)(server_KNativePtr ptr);
      void (*DisposeString)(const char* string);
      server_KBoolean (*IsInstance)(server_KNativePtr ref, const server_KType* type);

      /* User functions. */
      struct {
        struct {
          struct {
            server_KType* (*_type)(void);
            server_kref_demo_Session (*Session)(const char* name, server_KInt number);
          } Session;
          struct {
            server_KType* (*_type)(void);
            server_kref_demo_Server (*Server)(const char* prefix);
            const char* (*greet)(server_kref_demo_Server thiz, server_kref_demo_Session session);
            const char* (*concat)(server_kref_demo_Server thiz, server_kref_demo_Session session, const char* a, const char* b);
            server_KInt (*add)(server_kref_demo_Server thiz, server_kref_demo_Session session, server_KInt a, server_KInt b);
          } Server;
        } demo;
      } kotlin;
   } server_ExportedSymbols;
   extern server_ExportedSymbols* server_symbols(void);

So every class instance is represented with a single element structure, encapsulating stable pointer to an instance. Once no longer needed, DisposeStablePointer() with that stable pointer shall be called, and if value is not stored somewhere else - it is disposed. For primitive types and kotlin.String smart bridges converting to C primitive types or to C strings (which has to be manually freed with DisposeString()) are implemented.

For example, running constructor of class Server taking a string will look like

server_kref_demo_Server server = server_symbols()->kotlin.demo.Server.Server("the server");

And disposing no longer needed instance will look like

server_symbols()->DisposeStablePointer(server.pinned);

To make code easier readable, macro definitions like

#define T_(name) server_kref_demo_ ## name
#define __ server_symbols()->

will transform above, overly verbose lines to more readable

T_(Server) server = __ kotlin.demo.Server.Server("the server");

_type() function will return opaque type pointer, which could be checked with IsInstance() operation, like

__ IsInstance(ref.pinned, __ kotlin.demo.Server._type())