Using C with C3
🚧 This is a work in progress, so it may not be complete or finalised.
We will create a basic wrapper over a C function, then use it within C3. To keep this relatively simple, we'll use the standard file structure of a project as it is the simplest way of managing our files.
Setup
We can do this by:
$ c3c init mycproject
If we intend on making this a library and using the compiler directly, we can do something like this:
$ mkdir lib
$ mkdir src
C Code
Let us start writing our tiny wrapper. First, we will create our C header file, as this is only a few lines. Here's our header:
littlec.h
#ifndef LITTLEC_H
#define LITTLEC_H
int add(int a, int b);
#endif
Obviously the most interesting function we could write here. Just to keep our code simple, we will just be adding two numbers in C and returning that to C3. Now for our implementation:
littlec.c
#include "littlec.h"
int add(int a, int b) {
return a + b;
}
From here, we can take two routes:
Using in Source
For very small bindings, like this example, we can get away with using it directly in source without a lib. Let's start by opening src/main.c3
(or your main file) and take a look:
src/main.c3
module cinterop;
import std::io;
extern fn int add(int a, int b);
This is pretty straight forward for our add function, as it is basically identical in our C3 code. If we wanted to use a function that has a different name, we can use the @extern
attribute, like so:
extern fn int add(int a, int b) @extern("ADD");
To use this function, we can now add our main function and call it like so:
src/main.c3
fn int main() {
io::printfn("3 + 4 = %s", add(3, 4));
return 0;
}
Before we can run the code, we must tell the compiler how to find our C code. If you're using a project, you can open the project.json
and uncomment the c-sources
property:
// C sources if the project also compiles C sources
// relative to the project file.
"c-sources": [ "csource/**" ],
If your C files are in a different directory, replace csource
with your directory. You can also include the file directly:
// C sources if the project also compiles C sources
// relative to the project file.
"c-sources": [ "littlec.c" ],
That's the final thing we needed. Let's try running our code.
If you're using a project:
$ c3c run
# OR:
$ c3c build
$ ./build/<your_executable>
To compile directly, we must compile our C code to an object file first:
$ gcc -O3 -c little.c
This assumes using Linux, so you might have a
.obj
file instead. If you don't havegcc
you can install mingw, or use your platforms C compiler.
Then we can compile our code, passing in the object file:
$ c3c compile src/main.c3 -o build/helloc -z ./csources/littlec.o
-z
will pass in our object file. Use the path to your object file, if you're not using csources and/or a different name.
Output
Then finally the output of the executable:
3 + 4 = 7
We are now done! Head down to the end to learn more, or continue reading to create a library.
Creating a Library
Let's start by creating a directory in the lib
folder called, "littlec.c3l". It will serve as our library that holds our wrapper code.
Note the
.c3l
extension of the directory. This is important, so it must be used.
Within our littlec.c3l
directory, we will have four files:
littlec.c
: C implementation codelittlec.h
: C header file describing our codelittlec.c3i
: C3 interface file, to describe our C code and how it binds to C3manifest.json
: JSON file outlining the library details
To begin with, we will get the manifest out of the way. We don't need too much here for our example, so we can keep this minimal:
{
"provides" : "littlec",
"c-sources": [ "littlec.c" ],
"targets" : {
"linux-x64" : {}
}
}
I am using Linux, so my target is
linux-x64
. You can list multiple targets here if you want to support many targets. If you don't know what to put here, you can usec3c --list-targets
to see all available targets.
This is a pretty small manifest and we don't need to add anything to our target for this example. For the project to compile, we need to list our target otherwise the compiler will error saying the platform is not supported.
Before we get to the code, lets go over what is here.
"provides"
: This is the name of our library. It does not need to be the same as the module name."c-sources"
: This is a list of our C source files that should be included in the build."targets"
: As before, these are our supported targets for our library. We can list other properties here to override or append to our global properties.
For more details and a list of properties that are allowed, use c3c --list-manifest-properties
to view.
We can now start wrapping our code with our c3i file.
littlec.c3i
module littlec;
fn int add(int a, int b) @extern("add");
So what is this file doing? First of all, we create a module for our library littlec
. This will be what we import into our code to use our library. This does not have to be the same as our library name, but it is preferred to keep it the same. If you need to change the module name, you should make a not somewhere to let users know what module is required.
Second, we are using a C3 function signature that reflects our add
function in C. Just after our signature, we have the extern
attribute which tells the compiler what symbol we're binding our function to. Our C function is called "add", so we use "add" inside our extern attribute.
And that's our little wrapper completed. Now onto using it.
Using our Wrapper
We can now use our wrapper within our C3 code. If you created a project, we will be using src/main.c3
, otherwise create a file within src
or somewhere close to the lib. Here's our code:
module cinterop;
import std::io;
// C wrapper module
import littlec;
fn int main()
{
io::printfn("3 + 4 = %s", littlec::add(3, 4));
return 0;
}
Nothing too crazy here. We can simply import our new library, then we call it within main using the arguments 3 and 4.
Running
If you're wanting to compile directly:
$ c3c compile src/main.c3 --obj-out temp --output-dir temp --libdir lib --lib littlec -o helloc
Or build and run:
$ c3c compile-run --run-once src/main.c3 --obj-out temp --output-dir temp --libdir lib --lib littlec
Currently there is a bug that requires you use
--obj-out <dir>
.
If you're using a project:
$ c3c run
# OR:
$ c3c build
$ ./build/<your_executable>
Output
Then finally the output of the executable:
3 + 4 = 7
The end
Congratulations, we have put together a C wrapper in C3! This was quite a simple example to get the idea across, but if you're interested in larger examples or wrapping C libraries, check out the vendor repository. This repo contains some libraries that are wrappers over C code. You can open up one of the libraries and checkout their .c3i
file to see how a more real-world wrapper library is created.