User Tools

Site Tools


native-libraries

This is an old revision of the document!


Writing Dana native libraries

Dana native libraries allow you to call C code from Dana, as if that C code were itself a Dana component. This is useful when you need to interface directly with OS or hardware-level functionality in a way not provided by the Dana standard library. All of our current native libraries are available as open-source code at https://github.com/projectdana/native_libraries.

The process to create a native library involves a Dana interface, a wrapper component implementing that interface and talking to the native library through a Dana library interface, and a native library which implements the Dana library interface.

We begin by defining a Dana interface for our wrapper component:

interface MyWrapper{
   void doSomething()
   }

Write the above code in a text file, save it as MyWrapper.dn, and place it somewhere in the Dana central source tree (in the resources directory tree).

Next we create the wrapper component which implements the above interface:

library interface MyLib{
   void libFunction()
   }
 
component provides MyWrapper, Service requires NativeLoader loader {
   static library MyLib myLib
   implementation MyWrapper {
      void MyWrapper:doSomething() {
         myLib.libFunction()
         }
      }
 
   implementation Service {
      void Service:start() {
         myLib = new MyLib() from loader.load("mylib") :< MyLib
         }
 
      void Service:stop() {
         }
      }
   }

We put this component in a symmetrical place to the MyWrapper.dn interface in the Dana central source tree, within the components directory tree.

Finally we create the native library itself.

Create a new file mylib.c in the libraries repository. In this file, write the following code:

#include "dana_lib_defs.h"
#include "nli_util.h"
#include "vmi_util.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
static CoreAPI *api;
 
INSTRUCTION_DEF op_my_function(VFrame *cframe)
	{
	printf("A native call!");
	return RETURN_OK;
	}
 
Interface* load(CoreAPI *capi)
	{
	api = capi;
	setInterfaceFunction("libFunction", op_my_function);	
	return getPublicInterface();
	}
 
void unload()
	{	
	}

Don't worry about the RETURN_OK line; this is a standard way to return from a native library function and indicates to the Dana runtime that everything went OK with the native call. Returning actual values back to Dana code is handled via helper functions (see below).

Next we need to use the Dana compiler to generate a C file containing all of the glue code between our native library and the Dana runtime. To do this we open a command prompt in the same directory as our wrapper component (yes, the component, not the interface), and type:

dnc MyWrapper.dn -gni

Assuming that you saved your component in a file called MyWrapper.dn. This will create a new text file called MyLib_dni.c. Copy this file to the libraries repository (i.e. the same folder as your library code). Now we create a makerule for this library in the Makefile of the libraries repository. Open Makefile in a text editor and add the rule:

my_new_lib:
	$(CC) -Os -s MyLib_dni.c vmi_util.c mylib.c -o mylib[$(PLATFORM).$(CHIP)].dnl $(STD_INCLUDE) $(CCFLAGS)
	$(CP_CMD) mylib[$(PLATFORM).$(CHIP)].dnl "$(DANA_HOME)/resources-ext"

And run the makerule with:

make my_new_lib

Now, if you write a quick test program to call your wrapper component's doSomething() function, you should see the text printed from the native library. Simple :-)

The creation of every native library follows the same procedure.

Passing parameters and returning values

You can pass parameters into native library calls from Dana, and also return values from them back to Dana code.

Let's assume that our library API had been defined in Dana as follows:

library interface MyLib{
   void libFunction(char input[], int value)
   }

Remember that whenever you change the library interface definition you'll need to regenerate the C header using dnc MyWrapper.dn -gni

In the native library, we can access the parameters passed into this function using helper functions in vmi_util.h:

INSTRUCTION_DEF op_my_function(VFrame *cframe)
	{
	printf("A native call!");
        char *param1 = getParam_char_array(cframe, 0);
        size_t param2 = getParam_int(cframe, 1);
 
        printf("Parameters were '%s' and %u\n", param1, param2);
 
	return RETURN_OK;
	}

Next, let's look at how to return values from native library functions. Again we can use helper functions to do this. Let's update our library interface to now be defined like this:

library interface MyLib{
   char[] libFunction(char input[], int value)
   }

We can now return a string of characters from our native library call like this:

INSTRUCTION_DEF op_my_function(VFrame *cframe)
	{
	printf("A native call!");
        char *param1 = getParam_char_array(cframe, 0);
        size_t param2 = getParam_int(cframe, 1);
 
        printf("Parameters were '%s' and %u\n", param1, param2);
 
        return_char_array(cframe, api, "Done!");
 
	return RETURN_OK;
	}

Take a look at the source code of vmi_util.h to see the full set of types that can be easily passed as parameters and returned. In reality native libraries can accept and return any Dana type, but we do not yet have helper functions for constructing more complex types than those in vmi_util.h – the easiest approach is often to encode more complex data in a character array and then decode that character array into a complex type on the other side of the function call.

After that, for now, you're on your own! Use the existing libraries as a reference - they collectively do just about everything you might want to, so play around and figure things out, and ask questions when you get stuck. We'll write more documentation when we can!

native-libraries.1533201228.txt.gz · Last modified: 2018/08/02 05:13 by barryfp

Page Tools