Simplified API Development – Part I, Ground Rules

It seems that more and more as I talk with people, I begin with “way back in the day…”  I guess it’s a sign of aging.

Well, back in the day, the only “API” that existed was that of the raw assembly language of the machine you were programming on.  Since then, we’ve seen the rise of numerous languages, libraries, frameworks, and ‘platforms’.  Each one of these represents a particular view of the world, and offers up a simplification of how to get things done.

I’ve been doing a fair amount of interop layer coding of recent, so I’ve been getting fairly intimate with various APIs of multiple systems.  I’ve previously written about what would make for a good API (plain C, like OpenGL), and what makes for a challenging API (Windows COM).  I thought it might be useful to create a little series of articles on what makes for a good interop layer to existing APIs.  So, here it is.

First of all, the problem statement:  It’s very challenging to interoperate with different systems, written by different vendors, over the course of multiple years.  It’s even challenging trying to use the same APIs from the same vendor over the span of a decade.  Case in point, if you look at the Windows operating system, depending on which part of the system you touch, you get different flavors of API.

Here’s a sampling of API calls that might be typical of Windows:

void function1();

BOOL function2(void);

int function3(int value);

int function4(void *buff, int *bufflen);

function1 – Probably just performs some action, you can assume it always works, whatever it is. There’s no indicator of success or failure.

function2 – Kind of similar to function1, except there is a boolean value telling you whether it was successful or not. Like Beep().

function3 – These get more interesting. The return value is an integer, rather than a bool. So, it’s not just true/false. In these cases, a return value of ‘0’ might indicate success, any other value might indicate failure of some sort. Sometimes the return value represents the actual error number. In some cases, it just indicates that there was an error (not returning 0), and you have to call a separate function (like GetLastError()) to figure out what the actual module specific error was. This makes for a very challening interface. First of all, since ‘0’ is used for success, you can’t write code that looks like:

if function3(val) {
}

You have to do a little more work.

if function3(val) == 0 {
}

And you have to do a lot more work to handle the error condition.

Then we come to function4 – This one has all the challenges of function3, in terms of the return value, and adds additional challenges because you’re passing in a ‘buffer’ that is typically filled in, or just passed in. You have to indicate the size of the buffer, and you might also have to check the size of the returned buffer to see how much data was filled in. Of course, whether the value was valid or not is determined by the return value, which might be ‘0’, or something else to indicate success or failure.

These present several coding challenges, and plenty of opportunities for mistakes to be made. Is it wrong? No, not necessarily. In many cases what you’re seeing is the limitation of the implementation language that we all have used since time immemorial, ‘C’. It can only have a single return value, unlike languages such as Lua, which can have multiples.

The other part, about passing in values, and passing back sizes (function4) is an artifact of the same limitation. Ideally function 4 would be more like:

success/len, error function4(char *buff, int bufflen)

In this case, you pass in the buffer and its size. Upon returning, the first value will either be the amount of the buffer filled in, or false, If the first value is false, then the second value will indicate the error condition. That’s a little bit more sane to deal with, but C is not capable of providing such a facility. Of course you have to think about the case where the return value is supposed to be true/false. In that case, if ‘false’ is a valid return value, and there was an error as well, then you’d check both the success and the error. If there error were also nil, then there is no error. This is probably a fairly uncommon edge case.

In steps Lua, or any other modern ‘dynamic’ language.

Justin Cormack created this Lua interop layer to Linux System level programming called ljsyscall.  This is a very nice system as it essentially provides the programmer with a Lua interface to all of the Linux system level calls.  But, there’s something very specific I like about this implementation.  LJSysCall imposes a structure to the API that it presents to the programmer.  Two very simple rules.

1) All functions will have a return value

2) The first value is either true/false, or the return value of the function

And that’s it.  With these two simple rules, it means that you can consistently program your functions with this sort of logic:

if not function1() then
   -- error condition 
end

This will work for all cases, function1, function2, function3, function4

Additionally, all system functions are wrapped, so that teasing out error values and true/false, depending on whether the return value was BOOL or int, or whatever, is done in the wrapper. That way, the programmer only needs to know of one single semantic. They don’t have to go groveling through documentation to really figure things out. They can always know that they’ll get success or failure, or the value that needs to be returned. Of course with Lua you can go further and include even more values as return values, but these basic rules are enough to start.

Well, that’s enough for this first installment. Just the establishment of a couple of rules which will prove to be fairly useful when implementing some interop wrappers.

Next time, I’ll apply these rules to a couple of Windows specific APIs to see how they hold up.
 


3 Comments on “Simplified API Development – Part I, Ground Rules”

  1. […] Simplified API Development – Part I, Ground Rules […]

  2. […] API Development – Part 2, First Steps Simplified API Development – Part I, Ground Rules In the last article, I laid out some basic FFI interop calls to core BSD Socket functions.  If […]

  3. […] API Development – Part 3, Second Steps Simplified API Development – Part 2, First Steps Simplified API Development – Part I, Ground Rules So far, we’ve laid some ground […]


Leave a comment