Wrote a macro to make ccall look a little more like Julia. Need an opinion on the return type placement

printf and other varargs functions aren’t called the way normal C functions are

That’s not generally true. They aren’t called like a normal ANSI-C function because they’re typically required to be a normal K&R-C function. (But then those are often required to be the same, so you can usually get away with ignoring the difference.) In practice, some APIs that provide a ... varargs function use a void** implementation underneath (and expose that to users too) because the vararg itself can actually be fairly difficult to work with in the general case since it has a opaque representation.

Are you saying that you can call a varargs function through ccall like this and expect it to work?

I’ve successfully used printf with ccall before, just not with argument expansion on the Julia side. The message makes it look like ccall is already doing something macro-y (i.e. it seems not to expand arg…)

This works some of the time, but is not portable or reliable. For example, this works for me on MacOS:

julia> ccall(:printf, Cint, (Cstring,), "Hello world!\n")
Hello world!
13

julia> ccall(:printf, Cint, (Cstring, Cdouble), "A number %g.\n", 3.14159)
A number 3.14159.
18

but on Windows I get:

julia> ccall(:printf, Cint, (Cstring,), "Hello world!\n")
Hello world!
13

julia> ccall(:printf, Cint, (Cstring, Cdouble), "A number %g.\n", 3.14159)
A number 0.
12

(Cstring, Cdouble...) for both cases.

True, this works! But I have to admit I don’t know why. Could you explain please?

Ah, that works for me on Windows. So ccall supports varargs reliably these days as long as you use ... in the argument list?

What if they’re not all Cdoubles?

It has always been supported and this is the syntax. It doesn’t support mixing types though (llvm obviously support that just fine but we just don’t have a syntax for it…) I believe this is documented last time I check…

Note that on x86 (64bit only iirc) vararg floating point is one of the few cases this is needed. The calling convention requires the number of fp vararg to be passed in rax so without the ... the result may be wrong.

We don’t have syntax for that now…

Just cast them all to void* first. har har.

mostly joking.

edit: how’s that now? Ptr{Nothing}(pointer(myobject)). Charming!

One benefit of having a macro is that we could have such a syntax: one option would be to wrap all the varargs in a tuple and call ..., e.g.

@ccall printf(fmt::Cstring, (x::Cdouble, y::Cint)...)::Cint
2 Likes

No that’s not the right calling convention. Whether you cast it first is also a completely orthogonal issue…

1 Like

Not to this question:

I don’t know if it works, but my idea use Ptr{Cvoid}… as your type in the case of printf because it’s going to recast all the arguments anyway based on what’s in the format string.

Or so I am lead to believe…

No. printf looks for certain arguments based on the format string, but ccall still needs to pass them as the correct types.

Well it does something based on the string for sure but that by no mean spare you from following the calling convention. The caller and callee always has to agree on that.

Of course you can make the calling convention passing everything by pointer for vararg, it’s not done for the same reason it’s not for normal argument: there are more efficient way to pass them.

Yes that seems like the best syntax option I’ve seen for this.

Could this be done from within Julia, or would it require C-level changes?

There’s no C-level handling for this either. If you look at Meta.@lower, you’ll see that ccall is actually implemented as a macro. But the lowered form (foreigncall) also doesn’t define support for varargs of different types.

1 Like

I thought so. If you try to use args... in the arguments section, they don’t get expanded, and you get a sort of unexpected error message.