C Interoperability

Vortex provides the ability to interface with C (and C++ via "extern C") libraries at runtime, allowing you to extend the language to your needs.

How it works

The current process of interfacing with an external C library is a little manual and involves some (but not a lot) of work on the programmer's side to get it working.

Essentially, you'll need to create an intermediary C/C++ file that acts as a bridge between Vortex code and C code. After compiling that as a dynamic library, you can call it directly from within Vortex. However, it's best to create a small module in your Vortex project that wraps the library calls for readability.

Let's create a small library that simply adds two numbers and returns the result:

add_lib.cpp
#include "include/Vortex.hpp"

extern "C" Value add(std::vector<Value> &args)
{
    int num_required_args = 2;

    if (args.size() != num_required_args)
    {
        return error_object(
        "Function 'add' expects " + std::to_string(num_required_args) 
        + " argument(s)"
        );
    }

    Value a = args[0];
    Value b = args[1];

    if (!a.is_number())
    {
        return error_object("Parameter 'a' must be a number");
    }
    
    if (!b.is_number())
    {
        return error_object("Parameter 'b' must be a number");
    }

    double new_value = a.get_number() + b.get_number();
    return number_val(new_value);
}

Here we've defined one function, add, that takes two numeric arguments. We perform type checks on the arguments to make sure we've passed in the correct values.

If any of the checks fail, we return an Error object that's constructed by calling error_object(<error_message>).

By returning an error object on error, we ensure that our interpreter handles it correctly. For example if we're in the middle of a try/catch statement.

We mark the function as extern "C" so that we can export and interface with it.

Our add function returns a Value, which is a smart pointer type to a struct that our Vortex interpreter understands. This struct is provided to us through the Vortex.hpp header file. This header file also exposes the number_val function which handles the creation of new Vortex number.

We then create a new Value of type Number, and insert the value of the add calculation into it. We then return this object.

After compiling our module, we can now use it within our Vortex code. Here we create a middleware file that loads the library and exposes the add function in a more natural way.

Note that the function load_lib expects the path to the newly created dynamic library, as well as a list of the functions we want to expose.

modules/add.vtx
const lib = load_lib(<path_to_add_lib>, ["add"])
const add = (a, b) => lib.add(a, b)

Finally we import the module and use it within our Vortex code.

main.vtx
import [add] : "../modules/add"

const res = add(10, 20)
println(res) // 30

And just like that, we've extended the functionality of the language.

Advanced Modules

Sometimes we need to build more advanced modules that rely on other existing libraries. Doing so isn't much harder than the module we created above, however it does require some structure.

All current modules in the standard library adhere to the following file structure:

<module_name>
    bin
        <module_name>  <- compiled library
    include
        Vortex.hpp     <- Vortex header file
        ...            <- any other includes
    lib
        ...            <- any external libs
    <module_name>.cpp  <- C++ file
    <module_name>.vtx  <- Vortex wrapper file

By following the structure above for new modules, we can use the build_module scripts (varies by platform) to compile our module.

To use the module, our Vortex code will then need to import the wrapper file.

An example of a more advanced library is the requests module. You'll notice that it includes some external library files (in the lib directory) as well as some header files (in the include directory).

Note: Windows looks for dynamic libraries in a different way to Linux and Mac, hence why the DLLs need to be in the top level module directory.

Last updated