One of most interesting parts of uEZ is how it makes use of double pointers to track device driver workspaces and APIs. Unfortunately, for many software engineers that see the double pointers, they get confused on what is happening and are tempted to go around the system. This article will outline what is happening and how it works and why we choose to use this method.
But before we get into the details of double pointers, a bit of background information is required. When we designed uEZ, we had a few goals we wanted to achieve. First, we wanted to create a system that facilitated reusable code regardless of processor and hardware. We had fallen into a pattern of creating code and libraries for each design we built and although we could sometimes use the code from a previous project, a certain amount of grind was being hit on each project that we knew we could remove. Second, we wanted to create a system that was easy to extend and modify as new features become available. Just because we are reusing code didn’t mean we did not have new requirements. We wanted a framework that grew as new features were determined necessary. Plus, we didn’t have to design everything possible in version 1.00. Third, we wanted to use the most common language for embedded software. For this age, it is current C. Although we desired an object oriented language such as C++, we could not justify the move as many issues with compilers and general community acceptance still exists.
But we still wanted to use many of the features of an object oriented language in C. So, we devised a system of classes and objects which became known in uEZ as interfaces (classes) and workspaces (objects). We changed the names to reflect the fact that these objects and classes are specific to the requirements of the device driver system and should not be thought of as a generic system.
So with the background out of the way, we can return our focus on the double pointer system of uEZ. So, what do we really mean when we say uEZ uses double pointers? Let’s deconstruct what is happening….
When we write “DEVICE_Something **”, the “**” is a double pointer. A better way to say “double pointer” is “pointer to pointer”. For a moment, let’s take this at the surface – A pointer to a pointer. Although a pointer is merely an address in memory, it has a second component – a type – that is tracked by the compiler. So, again, when we write “DEVICE_Something **” we get a pointer to a pointer to a DEVICE_Something structure.
Ok … why? The “DEVICE_Something **” in the above example is really the interface (class) to the device driver workspace (object). Where is this workspace object? The object (called “Workspaces” in uEZ) is in the first dereferencing of the pointer to the pointer. We get it by type casting the pointer to an object. A workspace (“object”) is a block of data that starts with a pointer to its interface (“class”). Therefore, we get something that looks like this:
So, for the price of one double pointer, we get two pieces of information – workspace object and interface class. In theory, we could have just called it “Workspace *” and the first field of all objects contain a pointer to “Interface”. Instead, we wanted to use the type of the pointer to help define what type of object we are dealing with much in the same way in object oriented projects passing around variables of a specific class. By type casting the pointer and deference, all information about that object can be extracted.
*(DEVICE_Something **) = ptr to interface (or cast to T_uezDeviceInterface *)
(DEVICE_Something **) = ptr to workspace (or cast to T_uezDeviceWorkspace *)
*(DEVICE_Something **) -> function = ptr to function in interface
Each function in the API needs access to the workspace, so we pass in the double pointer as the first parameter. The function can then typecast it to its private workspace with code like the following:
T_Temperature_AnalogDevices_ADT7420_Workspace *p =
A workspace is merely a block of data allocated with the singular requirement of a having a pointer to its interface at the start – effectively making it a subclass of T_uezDeviceWorkspace.
So, is all of this double pointer code worth it? In the recent uEZ v2.00 release, almost all of the drivers have been hidden by the uEZ System functions and handles to the devices. Although we could have continued to use a handle terminology, we wanted the device drivers to be as fast as possible and not require another subsystem to call for every function call.
What do you think? Should we abandon the double pointers for another system or should we keep this system? Do you have a problem with double pointers? Or would more and better documentation be enough?