LuaJIT Structure Pointer Interaction

In LuaJIT, you’re able to declare C Type structures like the following:

ffi.cdef[[ 
typedef struct {  
    int x;  int y; 
} point;
]]

Basically, just take whatever was in your C style .h files, and wrap it up in a ffi.cdef[[]] function call, and you now have access to that type from “the Lua side”.

This is where things get really interesting though. The first thing that can be done is to create an instance of this type. Easily handled with the following:

pt1 = ffi.new("point")
pt2 = ffi.new("point", 10, 20);

In the first case, an instance is instantiated, with default values of ‘0’. In the second, you can explicitly set the values of the fields. List them in field order, and the values will be assigned.

At this point, if you then want to compare two points to see if they are equal, you might be tempted to do the following:

same = pt1 == pt2

The result of doing this though, will likely be an error message that looks something like this:

luajit: test_struct.lua:72: attempt to compare 'struct 95' with 'struct 95'

That’s the Lua talking. It’s basically saying, “I have two structures here, and even though they are in fact the same structure, I don’t see a ‘_eq’ metamethod on either one of them, so I really don’t know how to compare them”.

Great. So, all that needs to exist is an equals operator somewhere and everything will be fine. So, next step in the metatype excursion is to create a ‘metatype’ and associate it with the typedef.

Point = ffi.typeof("point")
Point_mt = {
    __eq = function(self, rhs)
        return self.x == rhs.x and self.y == rhs.y;
    end;
}
ffi.metatype(Point, Point_mt);

If this little bit of code exists, then the previous comparison will now call the ‘__eq()’ function, and that function will decide whether things are equal or not.

There are now a couple of ways in which you can construct that point instance. In the first example, ffi.new() was used directly. But, now that we have the “Point” thing declared, you can actually use it, in pretty much the same way:

p3 = Point()
p4 = Point(10,20)

So great, life is grand, and I can just happily use these instances now…

But way, there’s another patter that I run across, particularly when I’m looking at some C code. There is another way to allocate things, and another way to compare them.

nullpt = ffi.cast("point *", ffi.new("char[?]", ffi.sizeof("point")));
p6 = ffi.cast("point *", ffi.new("char[?]", ffi.sizeof("point")));

In this case, the ffi is used to allocate a little chunk of memory. The size is taken from the size of the point structure. This chunk is then cast to a pointer to a point structure. This is standard operating procedure for typical C programming. What is gained and what is lost?

First of all, LuaJIT can still figure out that even though this is a pointer to a structure, it still knows about the associated metatype information, so you can call functions. So, if ‘__index’ field is added to the metatype information:

Point = ffi.typeof("point")
Point_mt = {
  __eq = function(self, rhs)
    return self.x == rhs.x and self.y == rhs.y;
  end;

  __index = {
    PrintSelf = function(self)
      print(self.x, self.y);
    end;
  };
}
ffi.metatype(Point, Point_mt);

I can do this:

pt2:PrintSelf();   -- created using ffi.new()
p4:PrintSelf();    -- created using Point() metatype
p6:PrintSelf();    -- created using alloc/cast

Here’s where things get a bit dicey though, and you have to really pay attention to what you’re doing. Even though you can call the same metatype functions no matter how the thing was allocated, the ‘==’ semantics will be different.

p5 and p6 were created as pointers to structures. So, in these two cases, the comparison will be a pointer comparison:

print(nullpt == nullpt)  ==>  true
print(p6 == nullpt)  ==>  false

The first returns true, because the two pointers are the same. Similarly, the second returns false, even though the values both have ‘0’ for both elements. That’s because it’s pointer comparison.

Getting the best of both worlds would require some small changes.

Point = ffi.typeof("point")
Point_mt = {
  __eq = function(self, rhs)
    return self:Equals(rhs);
  end;

  __index = {
    Equals = function(self, rhs)
      return self.x == rhs.x and self.y == rhs.y;
    end;

    PrintSelf = function(self)
      print(self.x, self.y);
    end;
  };
}
ffi.metatype(Point, Point_mt);

Here, there is the addition of the “Equals()” function. The ‘__eq()’ metamethod simply calls this function.

Now, when I want value comparison, and I’m not sure whether the two instances were created using the alloc/cast method, or the straight up ‘Point()’ method, I can simply use the Equals:

p1 = Point(20,30)
p2 = ffi.cast("point *",ffi.new("char[?]", ffi.sizeof("point")));
p2.x = 20
p2.y = 30

print("p2:Equals(p1): ", p2:Equals(p1))  -- returns true

And there you have it. In some cases, it’s very beneficial to be able to play with pointers. At the same time, it is also highly beneficial to ready access to metatype functions. With a little bit of good planning, and some slight change in mechanisms used, it is possible to live comfortably in both worlds at the same time. The same is not totally true in the world of C. If you have a struct, access to fields is though ‘.’ notation, and pointers is through ‘->’ notation. Similarly, there’s a difference when you declare a function. In one case, you have a value passing semantics, and in the other, you have reference passing semantics.

LuaJIT allows you to have a single calling convention for both cases, using the ‘:’ to call metatype functions. And when passing a structure to a function, it’s just the same, and the runtime figures out the right thing to do.

This can make a lazy programmer even more lazy, especially when it comes to thinking about passing values to the C world. But, assuming you’re staying constrained to the Lua world, everything is perfectly fine, and life could not be any easier.


6 Comments on “LuaJIT Structure Pointer Interaction”

  1. danothom says:

    Great stuff Mr Adams. Very useful for my adventure into luaJIT!

  2. Andrew Senior says:

    In the following
    ffi.cdef[[
    /*
    * Data structure for setting window attributes.
    */
    typedef struct {
    Pixmap background_pixmap; /* background or None or ParentRelative */
    unsigned long background_pixel;
    ……………..
    Cursor cursor; /* cursor to be displayed (or None) */
    } XSetWindowAttributes;
    ]]

    whatever I try to do to it after
    local watts = ffi.new (“XSetWindowAttributes”)

    watts.background_pixel = X.XWhitePixel(pnl.dis, scr)

    luajit tells me this or any other field is non- existant.

    • Andrew Senior says:

      ‘Sworking now. It was a conflict with your libX11jit libraries. My code is stalled though. How do you dig out (for export) a struct from these libraries?

      • I was just going to reply. You can post issues on the individual repos so I can fix. I do intend to make a separate core which these will variously include. Look at lj2core, which will ultimately become lj2linux.

        Not quite sure what you mean by ‘dig out (for export) a struct from these libraries’. Dig out for the purpose of what? And do you mean dig out from the ffi.cdef?

      • Andrew Senior says:

        Working now. Don’t know what I did differently but we’re away with the mixer thanks. I have exposed one or two constanst for export. My intention is to expose them all. I imagine that’s your aim also. Thank you for 35 years of programming goodness, You have allowed me to resume with my favourite pastimes: Linux and Lua

  3. lispythonic says:

    Hello. does the sentence “And when passing a structure to a function, it’s just the same, and the runtime figures out the right thing to do.” means you can either pass struct pointer or struct to a C function that receives struct pointer parameter?

    More info here:http://www.freelists.org/post/luajit/Struct-pointer-and-struct-both-seems-ok-to-time-functions-that-receive-struct-pointer-is-this-normal


Leave a comment