This guide is meant to be a short introduction on very basic methods to achieve an “Object Oriented” design on your application written in the ANSI-C programming language.
The text is focused on Test Driven Embedded Development, but could be used for any area where C is used. In this example, I want to write an application for a hardware device containing:
- A display
- A keyboard
- A motor
As usual, when starting an object oriented design – start identifying your objects. I.e. the “things” in your design. You have a display, a keyboard and a motor. You will also have some control logic. These are your classes.
In C, there is no “class” keyword. Ofcourse, there is the “struct” keyword, with which you can encapsule data in some way. We will look at this later on.
To begin with, in C, the object oriented design starts in the file system. For my example, create a directory structure for the classes as follows:
Classes = Directories
The class methods are implemented in separate C-files in your directory structure. For example, the keyboard class contains the public methods “readline”, “readchar” and “sleep”.
The structure of your project will now look like:
Class Methods = Files
The data in your classes should be encapsulated and private (accessed via get/set methods). In C, there is no “secure” way of hiding data, but the directory structure and using the keyword “static” are helpful.
For example, the keyboard “class” needs a buffer to store characters in. In each class, create a “class structure” that holds all data needed by the class. This structure will be passed to each class method (function), just as the “this” pointer in object oriented languages.
Class Data = Structures
Each directory in your design contains a *_main.c. This file should contain any initialization and desctruction code (i.e. the constructor and desctructor). For example, the keyb_main.c file initializes the “class private” data:
Always pass this structure as the first argument to the class methods, like this:
To allow your application (the control logic) to talk with the objects in your design, they need public interfaces. In C, these interfaces are declared in header files.
Depending on the size of your project, you can now choose if the public interfaces should be put in separate directories, or just be a part of the existing directory structure (as above).
In my small example project, I choose to put the header files directly in the existing directory structure. We need (at least) two header files for each class:
- One interface (header file) for the public methods (external functions).
- One internal interface (header file) for internal communication between methods (static functions).
In the keyboard example, the directory now looks like:
Interfaces = Header files
The “keyb.h” file is the public interface for the keyboard class.
The “keyb_int.h” file is the internal interface between the methods.
For example, the “keyb.h” file contains the following public methods:
The internal interface file “keyb_int.h” contains declarations of the internal data (structure) and “class internal” helper methods:
The encapsulation in objects consist of:
- Separate C files for methods.
- Structures for data.
- Internal and external interfaces.
As you may have noted above, only the “class internal” and static methods (file internal functions) has the class private data structure as the first argument (the this pointer). The public methods (external functions) do not require the “class private” data as an argument. By designing this way, the class data is “hidden” from external users.
If you need more than one object of a class, the “class private” data needs to be dynamically allocated on the heap (instead of having it allocated once and for all as “static struct keyb_data” above). If you have followed the design above so far, this is a very simple change in the application. Instead of having the “static struct keyb”, just allocate the structure on the heap instead, by doing:
For larger projects (many files, many developers), the interfaces between “classes” needs to be pretty fixed at an early stage of the project. For this reason, it is nice to separate the public interface totally from the actual implementation of the classes. The classes might also need further dividing into components.
For the example above, the structure could be extended to something like:
An advantage of the above structure is that the application can be developed by many developers, working far apart.
The public interfaces in the *_i directories have been “fixed” from project start, so there will be no conflicts or incompatible method calls as the project evolves. Another advantage is that this structure is very easy to maintain in a revision control system (for example SubVersion, Git, CVS). By separating methods and interfaces in different files there will be minimal need for merging different code between developers.
Test Driven Development
When using a structure as above, missing (not yet implemented) code can easily be implemented as “test stubs”. From start, add a _stubs (or test) directory to each “class” directory structure. For example, for the keyboard class:
At project start, the method “keyb_readchar” in file “keyb_readchar.c” might not be implemented.
To be able to test the keyb component anyway, just add “keyb_readchar” to keyb_stubs.c (with a simple implementation), to enable further testing (by the component test or system tests).
There is a very good test framework for doing these tests (and stubs):
CppUTest – http://www.cpputest.org/
What about inheritance?
Inheritance is missing from the C language. With a basic design as above, there is often no need for inheritance. In real-world applications, there is seldom a need for language supported inheritance of the type “a keyboard is a kind of input device” (and a base class InputDevice with (often) only empty methods). Instead, public interface header files can be used as a kind of replacement.
For example, if there would be more input devices in the example above, we might want to add a “generic” or “base” header file for input devices, with methods that should be implemented (Just like when doing “implements” interface in Java). For example:
Then, all input devices should include and implement the function in this “base” interface. Note that since there is no such thing as function overloading in C, you will need to add a prefix to each device’s own function. There are ways to overcome this (using function pointers, but that is another story).
Reasons for prefixes
In the example above, there are prefixes such as “keyb_” on data, methods and defines in keyb_*.c. This is to avoid global name conflicts. There will never be a global define “SLEEP_TIME” or “READ” that confuses the project (they will be called KEYB_SLEEP_TIME or KEYB_READ). These prefixes also enables the inclusion of third-party libraries (and standard libraries) without problems.
Reasons for having short filenames and prefixes
The C standard recommends that the first 31 characters of names should be uniquely identifiable (this also enables shorter lines and more readable code).
Write short comments on what functions are meant to do (i.e. only what they are meant to do, not how they do it). Also write about the function’s arguments (in/out) etc. Especially in the public interfaces. It is recommended to have a code template that has an empty comment above every function. The code implementation inside functions should be self-explaining and easy to maintain.
I.e. no “smart” one-line constructions that needs extra comments. This only makes it difficult for (1) the compiler and (2) the person who is going to fix the bugs in the “smart” code later on.