Simplified API Development – Part 3, Second Steps

The Series so far:

Simplified 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 you’re a typical ‘C’ programmer, this might be the end of the road for you.  You’ve already got everything you need in order to get the job done.  With those basic functions, you can almost port standard C code verbatim to work within the lua environment, with the addition of all the extra Lua goodness.

But, looking back at the ground rules, we want to do better.  We want to create an interface that is consistent, and leverages some of the features of the dynamic language to provide an even easier, less error prone, experience for the developer.  So, let’s get to it.

First of all, the way I arrange things is to create two separate files.  The first file, winsock_ffi.lua, contains all the stuff we did in the last article.  That is, all the constants, data structures, function prototypes and the like that are gathered from the various header files involved.  The general idea here is that anyone who just wants to do core interop can ‘require’ this one file, and be on their way.  They are not burdened with having to include more files that they may not actually want or need.

The second file I create is more of a ‘utility’ file:  Winsock_Utils.lua

This is where we go beyond the basics and start to impose some structure on our interface.   Here’s the first few lines of the file:

local ffi = require "ffi"

local wsock = require "winsock_ffi"
local SocketType = wsock.SocketType

local SocketLib = ffi.load("ws2_32")

The first line gives us a local alias for the ffi module. That might be handy later. the second line (wsock =) loads in the ffi file that we created. The third line creates a local alias for any of the structures or constants that I might use frequently. Any number of lines of this type might be added. the last line dynamically loads the ws2_32.dll library file into memory, and gives me a local handle to it. This will come in handy later.

Then I define some ‘macros’. These are typically one line functions, that might have been expressed in #define statements in various of the header files. Or, they might just be extremely useful functions that are simple, but might have been in the library. In general, I try to implement on the Lua side what might be a simple function on the ‘C’ side. The reasoning here is, it’s faster to simply execute the Lua code, than it is to do the interop to the C library. Also, the more code that ends up in Lua, the more consistant it will be across multiple environments.

function IN4_CLASSA(i)
  return (band(i, 0x00000080) == 0)
end

function IN4_CLASSB(i)
  return (band(i, 0x000000c0) == 0x00000080)
end

function IN4_CLASSC(i)
  return (band(i, 0x000000e0) == 0x000000c0)
end

function IN4_CLASSD(i)
  return (band(i, 0x000000f0) == 0x000000e0)
end

IN4_MULTICAST = IN4_CLASSD

Next I create some meta methods and attach them to some of the core data structures. What? Ok, take the simple Ipv4 address structure:

ffi.cdef[[
typedef struct sockaddr_in {
    int16_t	sin_family;
    uint16_t	sin_port;
    IN_ADDR 	sin_addr;
    uint8_t 	sin_zero[8];
} SOCKADDR_IN, *PSOCKADDR_IN;
]]

This is the data structure used to create an address for a service on a particular port. There are tricks to using it though. For example, the sin_port is a short, but it needs to be in network byte order. Here is a common mistake:

local addr = ffi.new("struct sockaddr_in")
addr.port = 80;

You might think you’re assigning port 80, but you’re not. You need to do the following:

addr.port = SocketLib.htons(80);

htons – Host TO Network Short. Yah, it’s one of those little/big endian things that has plagued programmers since the internet emerged. By the way, that htons() function is a perfect candidate for being implemented in Lua.

So, we would like to have a ‘constructor’ for this object, where we could pass a couple of parameters and ensure things were done correctly. Besides this, we want an easy “tostring()” so that we can print the address easily. So, here’s how we create that structure:

sockaddr_in = ffi.typeof("struct sockaddr_in")
sockaddr_in_mt = {
	__new = function(ct, port, family)
		port = port or 80
		family = family or AF_INET;

		local obj = ffi.new(ct)
		obj.sin_family = family;
		obj.sin_addr.S_addr = SocketLib.htonl(INADDR_ANY);
		obj.sin_port = SocketLib.htons(port);

		return obj
	end,

	__tostring = function(self)
		return string.format("Family: %s  Port: %d Address: %s",
			families[self.sin_family], SocketLib.ntohs(self.sin_port), 
                        tostring(self.sin_addr));
	end,
}
sockaddr_in = ffi.metatype(sockaddr_in, sockaddr_in_mt);

With this little structure in place, you can now do the following:

addr = sockaddr_in(80);

If you were creating a server, this would just work, you’d get an address bound to port 80 on the local machine. This is very convenient, and basically what your typical object oriented library would do for you. Of particular note though is that you don’t want this to be too heavy weight. It does the basics in the constructor, and that’s about it. Not too much hidden magic, not too many assumptions, other than you are probably using AF_INET, and port 80 by default.

There’s another thing I’m typically doing with respect to parameters to functions. Since Lua has the concept of ‘varargs’, I can specify as many or as few of the arguments as I want. If I don’t specify an argument, it will take the value of ‘nil’. So, what I do when placing the arguments, is place the ones that I least care about, and are most likely to be a fixed value at the end of the list, and the most volatile at the beginning. That way you decrease the typical amount of typing needed.

Well, now our feet are getting wet.  The basic FFI layer has been laid out, the convenience functions are being created, and now the metamethods are showing up to make working with the core structures that much easier.

Next time, I’ll lay out the function calls, and wrap things up into a tidy package.

 


One Comment on “Simplified API Development – Part 3, Second Steps”

  1. […] Series Thus far: Simplified API Development – Part 3, Second Steps Simplified API Development – Part 2, First Steps Simplified API Development – Part I, […]


Leave a comment