Photo by Clint Adair on Unsplash

Why C programmers should use interfaces too and how to do it!

Ubrflgr
3 min readAug 25, 2019

--

First of all, what is an interface? An interface can be thought of as a contract between the user of a software component and the provider of that component. It defines which parameters it needs in order to provide a certain functionality. The aim is to hide as much implementation details and thus maintain independence from a specific implementation. In Object Oriented programming, interfaces are well known and Object Orientated languages usually offer some kind of concept to enforce the usage of an interface.

C programmers often do not think in objects because the concept of objects is not embedded in the language itself. Additionally, many C developers come from low level platforms or even bare metal and also often work on smaller code bases where software architecture is sometimes not a top priority. But when it comes to keeping the software flexible and having clear cuts in the architecture, interfaces are an unavoidable necessity.

So, how is it done in C?

Since an interface is just a concept C, we can easily replicate it in C and benefit from it’s advantages. An interface in C is basically just a bunch of function pointer packed into a struct, That’s it.

In the .h file you would typically have your interface definition:

Definition of an interface

The pointer itf that is passed to the functions is the object the function is working on (basically this in Java or selfin Python). The destroy function acts as the destructor and makes sure the object is properly deallocated.

An implementation of this interface includes that .hfile and provides a create function (the constructor in OO terms), which returns a pointer to a struct itf.

The .c file contains the implementation of the public functions, private functions and other private data.

Implementation of interface itf

A user of the interface would look as follows:

Usage of interface

Lets look at a more concrete example

Imagine you want to create a data logger application that waits for a trigger signal, then reads a sensor and stores it in storage device.

Depending on the device type (A or B), the application has to support different options:

Type_A consists of a timer trigger, reads a pressure sensor and writes to an eeprom.

Type_B consists of a event trigger, reads a temperature sensor and writes to a file on the file system.

First, let’s define the interfaces for the trigger, storage and sensor:

Definition of trigger interface
Definition of storage interface
Definition of sensor interface

And then we define an interface for the datalogger itself.

Next, let’s implement the datalogger:

Implementation of datalogger

The private data structure has the suffix _priv which contains a struct datalogger_itf. The datalogger_itfis populated with the interface functions and returned when creating the object. container_of is a macro to get the private structure from the public structure (see here how it works).

And finally the main:

main.c

This provides the advantage that the data logger doesn’t have any detailed knowledge about trigger, sensor and storage and just knows how to use these components. Each component can be changed without any changes required to the datalogger code.

Improve your tests

Furthermore, when testing, it is very convenient to replace a component with a mocked component. For example, if I want to simulate a failing sensor, I can implement a failing sensor as shown in the example below:

Mocked faulty sensor with interface

If I need to simulate a failing sensor in one test case but in the next test case a proper behaviour, I am able to separate this behaviour in two separate implementations. If no interfaces were used I have to implement a mock function for the e.g. temperature_sensor_readfunction which mocks both behaviours in the same function. Whether it has to fail or not has to be communicated to the function via global variable.

Mocked faulty sensor without interface

It may not look terribly complicated in the example above but I am sure you can imagine that it can get quite hairy if several sensor behaviours have to be simulated in a single mock function.

Let’s wrap it up

We have learnt that it makes sense to use interfaces when implementing in C and learnt a way to define and implement such an interface. Furthermore, we have seen that interfaces increase the flexibility of the software and we could show advantages when writing unit tests.

--

--