Writing a Native Library for the R3X runtime
Native libraries are often used to provide functionality that is not inbuilt in the VM and is platform-specific. Native libraries
are written in C, which is a widely used low-level programming language.
Required Files/Tools
The GCC Compiler toolchain is required, if you wish to use graphical components then an implementation of OpenGL (same as
the one used for compiling the VM) is needed.
If you have followed the build tutorial correctly, you should have two files in, src/lib directory namely, libntmalloc.a
and librxvm.a. These, especially the latter, are required. You will also need the headers in src/include.
Writing the Library
All libraries usually begin with:
#include <virtual-machine.h>
#include <nt_malloc.h>
The "Start" function
Whenever the runtime loads a native library, it looks for a Start function. The "Start" function is a mandatory initialisation
function which is present in the library. A minimal 'Start' function will take the pointer to the CPU structure (given in the
argument) and store it in a global variable. An example is given below:
r3x_cpu_t* CPU = NULL;
...
void Start(r3x_cpu_t* _CPU) {
CPU = _CPU;
...
}
...
Declaring functions is very simple, just like you would declare a normal function. The only exception being that, all
functions must return a uint32_t and should take 0 arguments i.e. voidT (here is a different way of
taking arguments that will be covered later on in this page. An example is given below:). After the function returns,
it's return value will be pushed to the VM's data stack.
...
uint32_t myfunction(void) {
...
}
Now you can simply declare this function in T++ as:
...
native(myfunction, "./myfunctionlib.so", number_of_arguments) // number_of_arguments is a constant
// ... And call it like a normal function
@myfunction(arg1, arg2, ...)
Taking Arguments
Since T++ follows the REXCall convention, arguments have to be taken in a different way, using the GetArgument function,
defined in <virtual-machine.h>. An example is given below:
...
uint32_t add_two_numbers(void) {
// CPU is an r3x_cpu_t*, the pointer to the CPU structure saved by the Start()
uint32_t arg1 = GetArgument(CPU, 1); // First argument. Arguments are 1 indexed!
uint32_t arg2 = GetArgument(CPU, 2);
return arg1 + arg2;
}
Pointers as arguments
If you want a linear address or a pointer from an argument, like a buffer or string, you will have to use
the GetLinearAddress function to convert the relative address given by the argument to an actual linear address.
An example of this, by implmenting the 'strcmp' function is given below:
uint32_t my_strcmp(void) {
// CPU is an r3x_cpu_t*, the pointer to the CPU structure saved by the Start()
char* str1 = GetLinearAddress(CPU, GetArgument(CPU, 1)); // Get Linear Address of argument 1
char* str2 = GetLinearAddress(CPU, GetArgument(CPU, 2)); // same as above, but argument 2 this time
strcmp(str1, str2); // now you can use pointers as you will in a normal program.
return 0;
}
NOTE: Never return malloc'd or internal addresses as arguments! Your program can only access memory given to it
by the runtime, anything outside will cause a segmentation fault.
Compiling
Once you have written your library, you can compile it using:
gcc -Idirectory_to_src/lib/include/ -std=gnu99 -Wall -fpic mylib.c -o mylib.o
gcc -shared -o mylib.so ./mylib.o librxvm.a libntmalloc.a
mylib.so is your native library which can now be used by R3X programs.