Using Functions from C Libraries Inside Alusus Programs


In this lesson we will learn how to use a dynamic library written in C inside Alusus program. When a dynamic library is imported all its exported functions will be available to use inside Alusus program, but you need to declare these functions manually using the command `function` with the modifier `@expname`, in the following way:

@expname[c_function_name] function function_name (arg_defs): return_type;

c_function_name: The name of the function as defined in C, i.e. as mentioned in the dynamic library's export table.
function_name: The name of the function in Alusus. This is the name that will be used to call the function and it is not necessary to be the same as C function name.
arg_defs: Function arguments definitions. This should match the C function definitions in types and order, but not necessarily in names.
return_type: The type of the return value.

Example:
In the next example we will have a dynamic library "libtest.so" that we wrote in C with which contains the following function:

#include <stdio.h>
int square(int i) {
  return i * i;
}

Now we will include this library and define its functions (here we have only `square` function) inside Alusus program, then we will use it:

import "Srl/Console.alusus";
import "libtest.so"; // include the library.
@expname[square] function square (i: int):int; // define the function
def x: int = 10;
Srl.Console.print("%d", square(x)); // 100

Notes:

  • When the compiler reaches `import` statement it will look up the required library in the folder that contains the file (that we write the code in it) being compiled, and if it's not found the compiler will look in a Alusus Libs folder, and if it's not found there either the compiler will look in system folders.
  • It is not necessary that the Alusus function name is the same as the C function name. So in the previous example you can call the function `pow` or any other name, as can be seen from the next example:
    import "Srl/Console.alusus";
    import "libtest.so"; // include the library.
    use Srl.Console;
    @expname[square] function pow (i: int):int; // define the function.
    def x: int = 10;
    print("%d", pow(x)); // 100
    

Using C Libraries with Non-Basic Types


In case the C library functions use non-basic types (i.e. structs) we will need to define the same type in Alusus. Which means defining a class with the same variable types and in the same order.

Example:

In addition to the function `square` we have the following function that is defined in the previous library, which adds a specific values to the real and imaginary parts of a complex number.

#include <stdio.h>
int square(int i) {
  return i * i;
}

typedef struct Complex {
  float real;
  float imag;
} complex;

void addNumbers(complex c, int a,int b, complex *result) {
 result->real = c.real + a;
 result->imag = c.imag + b;
}

As we can notice the function `addNumbers` takes 4 arguments, the first is the complex number, the second is the value we want to add to the real part, the third is the value we want to add to the imaginary part, and the last is a pointer to `Complex`.
If we want to use these functions inside Alusus program we should add the definition of the class `Complex` inside the Alusus program then define the functions as we learned in the previous section.

import "Srl/Console.alusus";
import "Srl/String.alusus"
import "libtest.so";
use Srl;
// define a class.
class Complex {
  def real: float;
  def imag: float;
}
// define the function that will use this class.
@expname[addNumbers]
function sum (c: Complex, a: int, b: int, res: ptr[Complex]);

def c: Complex; // define an object from that class to represent a complex number.
def result: Complex; // another object to store the result.
// assign a value for the complex number.
c.real = 1.1;
c.imag = -2.4;
// using the previous function.
sum(c,30,2.4,result~ptr);
Console.print("\nresult.real = %.1f\n", result.real~cast[Float[64]]);
Console.print("result.imag = %.1f", result.imag~cast[Float[64]]);
/*
result.real = 31.1
result.imag = 0.0
*/

Notes:

  • In the previous example it was possible to use a reference from class `Complex` instead of a pointer.
  • The compiler, in case of user-defined class, does not care neither about the name of the class nor the names of the members. It only cares about the order of the variables and their types. Which means in the previous example it was possible to use other names for the variables `real` and `imag`.
  • defining all the details of the class is not necessary in case you were dealing only with a pointer to that class in Alusus program, and you don't deal directly with its inner members (which means in case you don't need to access its members directly).

To understand that last point consider the following example:
Here we have the library "libtest.so" which contains `Point` struct and two functions: the first function creates an object of that struct type and returns a pointer to it, and the second function takes a pointer of this struct type and prints its information.

// C
#include < stdio.h>
struct Point {
  int x;
  int y;
};
// a function  that create an object from the previous struct and returns a pointer to it.
struct Point* createPoint(int x, int y) {
  static struct Point p;
  struct Point *ptr;
  p.x = x;
  p.y = y;
  ptr = &p;
  return ptr;
};
// a function that takes a pointer to the previous struct and prints its information.
void printPoint(struct Point *p) {
  printf("\nDisplaying information\n");
  printf("\nx: %d", p->x);
  printf("\ny: %d", p->y);
};

Now in Alusus we can write:

// Alusus
import "Srl/Console.alusus";
import "libtest.so";
use Srl.Console;

class Point {}
@expname[createPoint] function createPoint (x: int, y: int): ptr[Point];
@expname[printPoint] function printPoint (p: ptr[Point]);

def p: ptr[Point];
p = createPoint(5, 10);
printPoint(p);
/*
Displaying information
x: 5
y: 10
*/

Note how we defined the class `Point` inside Alusus program; we left it empty because we don't need to use any of its members directly inside Alusus program, all we do is sending pointers to C functions and getting pointers from them.
If we tried to access the variables `x` or `y` in Alusus program we will get an error because the variable is not defined inside the Alusus program.

p~cnt.x = 15; // error: x does not exist in type Point
Also if we tried to something like:
def p: Point;
printPnt(p~ptr);

This will not work because in this case the allocation of the variable is done in Alusus, using a class that does not contain `x` and `y` so Alusus allocates less memory for it than what the C functions need.

So, if all you need from a C class is exchanging pointers to it (as is the case with the class `Fs.File`) then you don't have to define the inner details of the class. It's important to note, though, that allocating a variable of a given class requires dealing with the inner elements of the class even if you aren't explicitly accessing the inner members of the class. This is because the compiler deals with the class members behind the scenes by allocating memory for them as well as initializing them. In other words, if you want to define a variable from a C class in Alusus you need to define the full details of the class to ensure correct memory allocation and initialization.

To summarize, if the following conditions are all met then you can ignore the class body when defining Alusus versions of C classes, otherwise the body will have to be defined as well:

  1. Alusus programs only deal with pointers to the class, and not with instances of the class.
  2. Alusus programs do not access class members.
  3. Alusus programs do not create the class or terminate it, instead it relies on imported functions to create and terminate that class.