Basics

This tutorial assumes you have cslug and gcc installed and ready. If that is not the case then first refer to the Installation guide.

Throughout these mini-tutorials we will use the convention of yellow background code-blocks for Python and blue for C. We will also assume, for convenience, that all Python and C files are in the same folder and that that folder is the current working directory unless indicated otherwise.

Hello cslug

Typically, we write C source code in its own .c file but for the purposes of getting started we'll lazily embed the C source into our Python code using io.StringIO. For C programmers who have never written a shared libraryAn executable file containing definitions (function/variables/...) but no main method. before - it is exactly the same as writing an executable except that you don't define an int main() function.

import io
from cslug import CSlug

slug = CSlug("my-first-slug", io.StringIO("""
    int add_1(int x) {
        return x + 1;
    }
"""))
>>> slug.dll.add_1(10)
11

Ta-da! If instead of 11 you got a cslug.exceptions.NoGccError then please go back to the installation section to setup your C compiler.

Let's talk through what just happened. cslug should have:

Finally, print(slug.dll.add_1(10)) will call our C function add_1() on 10 and print the answer.

Note

C doesn't give a wet-slap about indentation so there is no need textwrap.dedent()-ify embedded C source code.

Making slugs

Everything in cslug revolves around the cslug.CSlug class which is responsible for compiling your code and loading the output via ctypes. It takes one output name, and an arbitrary number of source files.

from cslug import CSlug
CSlug("output", "input.c")

Note the lack of a suffix for the output - this is because the suffix is platform dependent and should therefore not be hard-coded. Leave cslug in charge of slapping a .so on the end.

Slugs containing just one source file may specify only the source file and the output filename will default to the same name with the .c stripped. i.e:

CSlug("kangaroo.c")

is equivalent to:

CSlug("kangaroo", "kangaroo.c")

A CSlug can take multiple source files (provided there are no name collisions) and will merge them into one shared libraryAn executable file containing definitions (function/variables/...) but no main method..

CSlug("some-library", "file1.c", "file2.c")

However, if you want to use functions from one file in the other, then you will need a header file.

Compiling and Recompiling

cslug compiles implicitly only if any of its output files don't already exist. To invoke a recompile use slug.make().

slug.make()

If your source code is C file then just modify it, save it and call make - no need to create a new CSlug. If you're using io.StringIOs as source files you can edit a source like below, although it's generally easier either to create a new slug or to start putting C code into dedicated .c files.

# Rewrite an `io.StringIO()` source.
slug.sources[0] = io.StringIO("New C code")
# Recompile the changes.
slug.make()

If you want to see how it's being compiled see cslug.CSlug.compile_command().

Accessing Functions

Functions defined in C are available as attributes of cslug.CSlug.dll. The cslug.CSlug.dll is a ctypes.CDLL, which is an open file and should therefore be treated with care. In order to be able recompile the underlying binary safely it is recommended to only access functions using:

slug.dll_function_name()

instead of either:

lib = slug.dll
lib.function_name()

or:

function_name = slug.dll.function_name
function_name()

The second and third forms won't update lib or function_name() if you call either slug.close() or slug.make(), leaving dangling pointersA pointer whose target object has been deleted leaving it pointing to trash memory.. If you have no intention of invoking a recompile whilst running Python (makes debugging much quicker) then you may ignore this and any of the above forms.

Warning

The cslug.CSlug needs to be kept alive for the ctypes.CDLL and any of its functions to be kept alive. This means both of the following are dangling pointersA pointer whose target object has been deleted leaving it pointing to trash memory.:

function = CSlug("library", "source.c").dll.function
lib = CSlug("library", "source.c").dll

See also

Accessing Global Variables and Constants for accessing variables (constant or otherwise).

Current Working Dir Independence

The examples in these tutorials assume that your current working directory is the same as the folder your Python and C code is in. This is OK for experimentation but shouldn't be relied upon generally or your code will raise FileNotFoundErrors as soon as you take it out of the safety bubble of your favourite IDE. Instead the usual behaviour is to locate files relative to your Python code's location (typically using __file__):

from pathlib import Path
from cslug import CSlug

HERE = Path(__file__).resolve().parent
slug = CSlug(HERE / "name", HERE / "c-code.c")

This gets clunky very quickly so cslug provides an anchor() function replace it. The above can be rewritten as:

from cslug import cslug, anchor
slug = CSlug(anchor("name"), anchor("c-code.c"))

But, to avoid having to write anchor() over and over, it takes multiple arguments. The above can also be rewritten as:

slug = CSlug(*anchor("name", "c-code.c"))

CSlug automatically flattens iterables of arguments so the * may be omitted:

slug = CSlug(anchor("name", "c-code.c"))

You may specify paths rather than just filenames if your Python, C and binary files are in different places.

slug = CSlug(anchor("bin/name", "src/c-code.c"))

Note

The underlying C code beneath ctypes.CDLL is hard-coded to read from a true file. This means that any non pure Python package is automatically not zip-safe. There is therefore no advantage to using pkgutil.get_data() or any of its relatives.