David Greenspan wrote:
> I'm trying to pass a struct by value to a varargs API call over the LuaJIT
> FFI, like so:
>
> =====
> ffi.cdef [[
> typedef void *id;
> typedef void *SEL;
> id objc_msgSend(id theReceiver, SEL theSelector, ...);
> typedef struct _NSRect { double x, y, width, height; } NSRect;
> ]]
> local r, s, a, b, rect = ..., ffi.new("NSRect", {0,0,32,32}})
> ffi.C.objc_msgSend(r, s, rect, a, b)
> =====
Ouch. Passing structs by value is bad enough, but passing them to
varargs is really evil. Ok, so that's a side-effect of the central
dispatch model of ObjC.
BTW: ffi.new("NSRect", 0, 0, 32, 32) works as well.
> What happens is the arguments seem to get garbled, and as best I can tell,
> the reason is that the implementation of objc_msgSend is expecting to pull
> the NSRect struct off the stack, but LuaJIT is passing a pointer, because it
> "infers" that objc_msgSend is expecting a pointer based on the fact that I'm
> passing a struct. In fact, LuaJIT seems to explicitly replace struct types
> with corresponding pointer types in ccall_ctid_vararg.
Yes, that's deliberate. And it's the only reasonable solution with
the current automatic conversion rules. Otherwise you'd have to
cast every struct to a pointer-to-struct before passing them to
varargs (most APIs only want to receive structs by reference).
> Is there some way to pass the struct by value? Is there a good explanation
> for the current behavior? I'm trying to avoid writing any C stubs or
> wrappers at all when calling C APIs. :)
Well, I don't know how to solve that right now. I'd need to add a
special pass-by-value cast or some such ... stumped ...
But there are two workarounds:
1. On x86, you can pass the components of the struct to simulate
pass-by-value, i.e.:
ffi.C.objc_msgSend(r, s, 0, 0, 32, 32, a, b)
This doesn't work on x64 because of the crazy rules for passing
structs by value in the x64 ABI.
2. Define a couple of function pointers with fixed args for those
methods that need struct-by-value args and assign objc_msgSend to
them. Then use the function pointers to call those methods.
[Technically using a fixarg prototype for a vararg function is
wrong on x64 (missing $al = nfpr). But ffi.C.objc_msgSend is only
a forwarder and the receiver is in fact a fixarg function.]
--Mike