User Tools

Site Tools


native-libraries

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:

interface MyLib{
   void libFunction()
   }
 
component provides MyWrapper requires native MyLib myLib {
 
   void MyWrapper:doSomething() {
      myLib.libFunction()
      }
 
   }

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). You will need to re-run the above compiler command any time you change the definition of the interface type which interacts with the native library. 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:

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:

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.txt · Last modified: 2018/08/09 06:21 by barryfp

Page Tools