18. C interoperability
It is said that the world runs on C. Many of the popular programming languages have the capability to interoperate with C, and Mojo is no exception. Mojo provides developers with the ability to call functions which are implemented in the C programming language. This enables them to leverage existing libraries and codebases written in C.
18.1. Calling a standard C function
Calling a C function from Mojo is straightforward. You can use the external_call function from the ffi module to call a C function. The external_call takes in the name of the C function and its return type as parameters. The arguments to the C function can be passed as arguments to the external_call function.
The following code listing provides an example of how to call a C function from Mojo. In this example, we are calling the rand function from the C standard library, which returns a random integer.
from sys.ffi import c_int, external_call
fn main():
random_no = external_call["rand", c_int]() # Call the C function 'rand' which returns a random integer
print("Random number:", random_no) # Prints: Random number: <some random integer>
In the above code listing, we are importing the c_int type and the external_call function from the ffi module. We then call the rand function using external_call, specifying the return type as c_int as its second parameter. Finally, we print the random number generated by the C function.
18.2. Calling a function from a shared library
You can also call functions from a shared library. To do this, you would use OwnedDLHandle to load the shared library and then get the desired function from it using the get_function method. Once you have the function, you can call it like a normal Mojo function.
The following is a simple custom C function. Create a file named mymul.c and add the following code to it:
int mul(int first, int second) {
return first * second;
}
After that, compile the C code into a shared library in the same directory as the calling Mojo file.
The following code demonstrates how to call the mul function from the shared library we just created.
from sys.ffi import c_int, OwnedDLHandle
fn main():
# Load the shared library containing the C function 'mul'.
# This assumes the library is for MacOS and is in the
# current directory.
try:
lib_handle = OwnedDLHandle("./libmymul.dylib")
mul_fn = lib_handle.get_function[fn(c_int, c_int) -> c_int]("mul") # Get the 'mul' function from the library
result = mul_fn(5, 10) # Call the C function 'mul' with arguments 5 and 10
print("Result is", result) # Prints: Result is 50
_ = lib_handle # Keep alive
except e:
print("Error loading library or calling function:", e)
In the above code listing, we are loading the shared library libmymul.dylib using OwnedDLHandle. We then get the mul function from the library using get_function. The get_function method takes a function type as a parameter, which specifies the types of the function’s arguments and its return type. It additionally takes the name of the function as argument. Finally, we call the mul function with the arguments 5 and 10, and print the result.
You may have noticed that towards the end of the code listing, we have a line _ = lib_handle. This is to ensure that the lib_handle variable is kept alive until the end of the function, preventing it from being deallocated while we are still using the function from the library. Remember that Mojo eagerly deallocates resources that are no longer in use, so it’s important to keep the library handle alive as long as you need to call functions from it.
18.3. Statically linking a C library
If you do not prefer to dynamically load a shared library, you can also statically link a C library with your Mojo code. This involves compiling the C code into an object file and then linking it with your Mojo code during the compilation process.
To statically link a C library, you would first compile the C code into an object file using a C compiler. Then, you can use the -Xlinker option with the Mojo compiler to link the object file with your Mojo code.
int mul(int first, int second) {
return first * second;
}
Make an object file from the C code using the following command:
cc -c mymul.c -o mymul.o
Usage in Mojo code:
from sys.ffi import c_int, external_call
fn main():
result = external_call["mul", c_int](5, 10) # Call the C function 'mul' with arguments 5 and 10
print("Result is", result) # Prints: Result is 50
The following code demonstrates how to statically link the mul function from the C object file.
mojo build -Xlinker mymul.o mul_static.mojo
After taking the above steps, you can run the mul_static Mojo program, and it will call the mul function from the statically linked C object file.
18.4. Calling Mojo from C
Just as you can call C functions from Mojo, you can also call Mojo functions from C. This involves compiling the Mojo code into an object file and then linking it with your C code.
@export
fn add(a: Int32, b: Int32) -> Int32:
return a + b
Compile the Mojo code into an object file using the following command:
mojo build --emit object add.mojo
This creates an object file named add.o. You can then link this object file with your C code to call the add function from C.
Usage:
#include <stdio.h>
int add(int a, int b); // Forward declaration of the 'add' function defined in add.mojo
int main() {
int a = 5;
int b = 10;
int result = add(a, b);
printf("The sum of %d and %d is %d\n", a, b, result); // Prints: The sum of 5 and 10 is 15
return 0;
}
Finally compile the C code and link it with the Mojo object file:
cc myadd.c add.o -o myadd
In the above code listing, we have a Mojo function add that takes two integers and returns their sum. We compile this Mojo code into an object file using the --emit object option. Then, we can link this object file with our C code to call the add function from C.
You may have noticed that the add function in the Mojo code is decorated with @export. This is necessary to make the function available for calling from C. The @export annotation tells the Mojo compiler to export the function so that it can be linked and called from C code.