How do I convert a cdata structure into a lua string?
Asked Answered
C

2

5

I'm in the middle of writing a small application that needs to read some complex binary messages in LuaJit.

I've been using the bit module and string.rep a lot. However, it's all very cumbersome. I'm new to using LuaJit and think there might be a much easier way using FFI.

In C I can declare a structure like this:

struct mystruct
{
  uint32_t field1;
  char     field2[6];
  uin64_t  field3;
  short    field4;
} __attribute__(packed);

In reading LuaJit's FFI it seems you can declare

ffi.cdef[[
    #pragma pack(1)
    struct mystruct
    {
      uint32_t field1;
      char     field2[6];
      uin64_t  field3;
      short    field4;
    };
]]

I can then create a mystruct and access the fields like this:

local ms = ffi.new("mystruct")
ms.field1 = 32;
// ... etc

But, how do I convert this back into a lua string?

I tried this, but it didn't seem to do what I wanted.

local s = tostring(ms)

and this:

local s = ffi.string(ms)

produces the following error "bad argument #1 to 'string' (cannot convert 'struct mystruct' to 'const char *')"

So I tried a cast:

local s = ffi.string(ffi.cast("char*", ms))

No error, but it looks wrong on the wire.

Clamper answered 10/9, 2012 at 0:22 Comment(2)
oops, that should be pack not packed. Fixed.Clamper
What do you mean by "it looks wrong on the wire"? What do you see? What do you expect/want to see?Epinephrine
E
14

You have to explicitly specify the length when using ffi.string with a non-string-like parameter:

str = ffi.string(ptr [,len])

Creates an interned Lua string from the data pointed to by ptr.

If the optional argument len is missing, ptr is converted to a "char *" and the data is assumed to be zero-terminated. The length of the string is computed with strlen().

When running the following code, I get the expected (little endian) result:

ffi = require 'ffi'
ffi.cdef[[
    typedef unsigned long uint32_t;
    typedef unsigned long long uint64_t;
    #pragma pack(1)
    struct mystruct
    {
      uint32_t field1;
      char     field2[6];
      uint64_t  field3;
      short    field4;
    };
]]

function string.tohex(str)
    return (str:gsub('.', function (c)
        return string.format('%02X', string.byte(c))
    end))
end

ms = ffi.new('struct mystruct', 1, {2, 3, 4, 5, 6, 7}, 8, 9)
s = ffi.string(ms, ffi.sizeof(ms)) -- specify how long the byte sequence is
print(s:tohex()) --> 0100000002030405060708000000000000000900

Update: I know this is not a part of the original question, but I just learned this trick, and in order to be complete, here is a way to convert Lua string back to FFI cdata:

    data = ffi.new('struct mystruct')   -- create a new cdata
    ffi.copy(data, s, ffi.sizeof(data)) -- fill it with data from Lua string 's'
    print(data.field1, data.field4)     --> 1   9
Epinephrine answered 10/9, 2012 at 7:32 Comment(1)
it doesn't have to be copied. a simple cast will do. ``` local st = ffi.cast('struct mystruct*', s) print(st.field4) ```Ap
S
0

The previous has an error. According to current luaffi implementation, you can cast it to void* . Use ffi.string(ffi.cast("void*",ms),ffi.sizeof(ms)) to do so.

Sagerman answered 7/3, 2019 at 14:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.