Follow our illustrated blog on Embedded Software Architecture

How to build a hybrid solar/wind energy harvester?
x
Energy2switch: 2 Channel MOSFET N-Channel Switch Board 1500W 50V 40A.
Available here.

Object-orientation in C – Part 4

VN:F [1.9.22_1171]
Rating: 0.0/10 (0 votes cast)

We continue to build on the example in Part 3, but we go another step further. Even more abstraction and information hiding can be achieved by partially mimicking (part of) the COM manner of programming: strictly talking with interfaces. This style of coding is not for everybody, nor is it a fit for every project. The amount of work that is involved in setting up the system structure is high. But let us see how it works out as a further evolution of previous episodes. Therefor, the metamorphose is not yet complete. It is an intermediate step! In the context of this example, it might be difficult to evaluate the advantages of this Part 4 when e.g. compared with Part 2. This Part 4 setup is more for bigger systems and interchangeable modules. But it is very clean and maintainable, and achieves encapsulation and information hiding very well. In fact, it does it better than how people usually do C++.

First we define a base vtable class. The mother (or father) of all vtables.


typedef struct _vtableItf
{
  const void* (*QueryItf)(const void* object, const void* vtable);
}
vtableItf;

Query

The idea is to query for interfaces (i.o.w. other vtables) and to hide all object implementation details from the object user. Only interfaces are exposed, which brings us even more information hiding. With real COM, a UUID (a very large unique number) is used to “query” the availability of an interface. Here, we will use the pointer to the vtable as our UUID within our program context.
The shape vtable is then defined as:


typedef struct _shape_vtable
{
  vtableItf BaseItf;
  shapePtr (*alloc)(void);
  void (*free)(shapePtr);
  void (*set_label)(shapePtr, const char*);
  const char* (*get_label)(const shapePtr);
  int (*get_area)(const shapePtr);
}
shape_vtable;

and the circle vtable:


typedef struct _circle_vtable
{
  shape_vtable base_vtable;
  void (*set_radius)(shapePtr s, int radius);
  int (*get_radius)(const shapePtr s);
}
circle_vtable;

So, shape_vtable inherits from vtableItf. And circle_vtable inherits from shape_vtable in this case (which is not required but was handy for the implementation details in this case and because it build on Part 3). We are not going into detail about the implementation code for shape or circle, but instead we look at the usage in the main.c file first:


#include <stdio.h>
#include "shape.h"
#include "circle.h"

int main(int argc, char ** argv)
{
  shapePtr s = circle_alloc(); // (1)
  const vtableItf* Itf = vtable(s); // (2)
  const shape_vtable* ItfShape =
    Itf->QueryItf(s, &shape_vtable_imp); // (3)
  const circle_vtable* ItfCircle =
    Itf->QueryItf(s, &circle_vtable_imp); // (4)
  ItfShape->set_label(s, "my_circle");  // (5)
  ItfCircle->set_radius(s, 10); // (6)
  printf("label by vtable: %s\n",
    ItfShape->get_label(s)); // (7)
  printf("area by vtable: %d\n",
    ItfShape->get_area(s)); // (8)
  ItfShape->free(s); // (9)
  return 0;
}

Interfaces

All object interaction is performed through interfaces.
In step (1), the object is allocated. A circle is allocated, but the function returns a shape pointer. Note, that this even could be an opaque pointer, since the inner object details are never revealed or exposed to the user. As a second note, we could also allocate the object via the already exposed circle_vtable_imp. In that case, we would call circle_vtable_imp.base_vtable.alloc().
In step (2), the base vtable (vtableItf) pointer is retrieved from the object. From then on, we can query for functionality or interfaces which might be supported (or not, in that case NULL is returned).
To set the label we need a basic shape interface functionality. To set the radius of a circle, we need the circle interface functionality. We query both interfaces in step (3) and (4), which will be successful since it – obviously – is a circle object.
We do our business in step (5), (6), (7) and (8).
At step (9), we can use the basic shape interface, to get rid of the object. And we are done.

The ‘magic’ is performed by the QueryItf function. The implementation for shape (in shape.c):


static const void* QueryItf(const void* object, const void* vtable)
{
  if (vtable == &shape_vtable_imp) {
    return ((shapePtr) object)->vtable;
  }
  return NULL;
}

and the implementation for circle (in circle.c):


static const void* QueryItf(const void* object, const void* vtable)
{
  if (vtable == &circle_vtable_imp) {
    return ((circlePtr) object)->vtable;
  }
  else if (vtable == &shape_vtable_imp) {
    return &((circlePtr) object)->vtable->base_vtable;
  }
  return NULL;
}

The source code of this example setup, is available for download here. In Part 5, we will cleanup the example and work with opaque handles.
 

VN:F [1.9.22_1171]
Rating: 0.0/10 (0 votes cast)
Share

Current limiting with PWM

For particular applications, such as our energy harvester or at least the battery charger part of it, the ability to control the current is an essential property. We must emphasize that the presented method will limit the average current; the … [Continue reading]

The embedded hierarchy – Part2

hw hierarchy groups

So how can we represent the embedded hierarchy in software? Before we can answer this question, we must make our goals clear: setting up and defining a new board should be as easy as possible, and code re-use should be optimal. With these … [Continue reading]

Coming Soon

energy2switch1

Finally, we are almost there. Our first product - a joint development result of cooperation between IACS and RTOS.BE - is about to be commercialized. The Energy2Switch is a DC switch board with 2 channels rated up to 50V and 40A. Continuous power is … [Continue reading]

Object-orientation in C — Part 3

For non-trivial class hierarchies the method proposed in Part2 is probably not optimal. The main reason is that there is only one C struct which is used by base and subclasses. Consequently, the hierarchical tree is only implicitly contained in the … [Continue reading]

Energy Harvester Telemetry

beagle_bone_black_picture

Everything becomes networked Development focus is very important in order to prevent scope creep, however it does not hurt to look a bit further in the future to try to see how things evolve. Our development track for the energy harvester is … [Continue reading]

The energy harvester is ready for iteration 4

charge controller

Iteration 3 overview Essentially, a charge controller is a high-power switching device. The switching core determines how much energy flows to batteries, and how much excess energy is burned by a dump load. Next picture is key and PMD2 of our system … [Continue reading]

The embedded hierarchy — Part 1

embedded hw hierarchy

Good programming skills is not the only quality a software engineer needs to have in order to write extendable, reusable and manageable software. Understanding the domain is equally important because it enables you to build good abstractions, and to … [Continue reading]

The linker demystified – part 2

linking overview

 In part 1, we explained the main purpose of a linker: to put pieces of object code together into a single executable file. In this second and final part, we will discuss how we can control and adjust this process using linker scripts. … [Continue reading]

Object-orientation in C — Part 2

uml shape

In Part1 we have laid the foundation of how a class could look like in C. This simple class is good for creating an Abstract Data Type but it doesn't support polymorphism and inheritance...yet. Here is an UML diagram (made with dia) of what we are … [Continue reading]