Headers – Working with Multiple Files

Pre-waffle: What headers are for

Multiple source files can included in one shared libraryAn executable file containing definitions (function/variables/...) but no main method. just by passing more than one source file to cslug.CSlug. The namespace for each source file will be merged (clashes cause a build error). If you want to be able to use functions from one file in another file then you need a header file.

To demonstrate how to do this we'll be using the following uninspired example:

file1.c
int do_nothing(int x) {
  return x;
}
file2.c
int uses_do_nothing(int x) {
  /* Calls ``do_nothing()`` from ``file1.c``. */
  return do_nothing(x);
}

This refuses to compile because there is a reference to do_nothing() from file1.c inside file2.c (although some compiler versions let you off with just a warning):

from cslug import CSlug
slug = CSlug("lib_nothing", "file1.c", "file2.c")
slug.make()

To keep the compiler happy we need to put a function prototype for do_nothing() somewhere visible to file2.c. In this simple case it's enough to just put int do_nothing(int x); at the top of file2.c but if a third file also needed do_nothing() then this would require duplicating the prototype. The more general solution is to put function prototypes in a header file which any other file can #include.

Auto-generating header files

Writing header files is boring and generally devolves to copy/paste (bad). cslug.Header tries to do it for you. The following writes a header file containing prototypes for every function from file1.c and file2.c.

from cslug import Header, CSlug
header = Header("my-header.h", "file1.c", "file2.c")
slug = CSlug("lib_nothing", "file1.c", "file2.c", headers=header)

Passing the header to CSlug means that calling slug.make() will implicitly call header.make() so that you still only have one make() command (although you may use header.make() directly or header.write() to experiment with Headers without going through a CSlug).

The resulting header file looks something like:

my-header.h
// Header file generated automatically by cslug.
// Do not modify this file directly as your changes will be overwritten.

#ifndef MY_HEADER_H
#define MY_HEADER_H

// file1.c
int do_nothing(int x);

// file2.c
int uses_do_nothing(int x);

#endif

Now any file which references anything from either file1.c or file2.c need only #include "my-header.h".

Note

You don't need to pass header files (automatically generated or otherwise) to cslug.CSlug, although there is no harm in doing so.

Sharing constants between Python and C

Header files are also a good place to put constants or enumerations where other C files can easily see them. With cslug, you may define constants in Python, using either a class from the enum package or just a plain dictionary, then let cslug.Header propagate it into a header file for you.

import enum
from cslug import Header

class ErrorCode(enum.Enum):
    OK = enum.auto()
    NOT_OK = enum.auto()
    VERY_BAD = enum.auto()
    APOCALYPSE = enum.auto()

header = Header("constants.h", defines=ErrorCode) # defines=[ErrorCode] is also ok.

Quickly inspect using:

>>> header.write()
#ifndef CONSTANTS_H
#define CONSTANTS_H

// ErrorCode
#define OK 1
#define NOT_OK 2
#define VERY_BAD 3
#define APOCALYPSE 4

#endif

Now any Python code can access the APOCALYPSE constant via ErrorCode.APOCALYPSE and any C code can #include "constants.h" and use APOCALYPSE directly.

Other relatives of enum.Enum such as enum.IntEnum and enum.IntFlag as well as dicts can also be passed to the defines= option. Values are cast to strings using str then substituted into the header as is (without surrounding quotes). If that isn't what you want then you may need to pass a dictionary comprehension based on the enum.Enum.__members__ attribute instead.

Add #includes to generated headers

If you use types which are defined in other header files such as wchar_t then you need to #include those headers to the generated header. For local headers use:

Header(..., includes='some-header.h')

or for system-wide ones libraries enclose with angle brackets:

Header(..., includes='<stddef.h>')

Pass a list if you have more than one:

Header(..., includes=['<stddef.h>', '<stdint.h>'])

Add arbitrary code to generated headers

This is intentionally not supported. If you want custom code in an automatically generated header then put your code in a separate file which either #includes or is #included by the generated one.