Repository navigation
purego: new mechanism for calling C functions - #49
Conversation
hajimehoshi
left a comment
There was a problem hiding this comment.
Pretty nice. Are you working on the TODOs now?
|
Yes, I am! |
|
I've ported Oto to use |
I'm confused. Do you mean the current |
|
Yes it can replace all occurrences of SyscallN in Oto and Ebitengine in its current state |
|
So
I'm fine to merge this with leaving a comment about the TODOs! |
| // func => C function | ||
| // []T, unsafe.Pointer, *T => void* | ||
| // | ||
| // There is a special case when the last argument of fptr is a variadic interface (or []interface} |
There was a problem hiding this comment.
Just out of curiosity, would it be hard to support other types than interface{}?
There was a problem hiding this comment.
Do you have an example?
Also note that this does not support calling variadic functions like int printf(const char* format, ...); It's basically a cast to the desired C function. (*(char*, int)())(printf)("%d", 3) is not the same as printf("%d, 3) since the second uses va_args under the hood. The first is what is currently handled
There was a problem hiding this comment.
For example: https://pkg.go.dev/reflect#Append (I don't think this can be a C function but this is an example with a non-interface{} type)
There was a problem hiding this comment.
I now understand that the purpose of RegisterFunc is to have a Go function that is a counterpart of a C function. So, having another type than interface{} would not make sense.
By the way, in the comment, the order of types is (Go type) => (C type), but is there an order here?
There was a problem hiding this comment.
Yes the Go function should have the same signature as the C function being called.
There is no order except for how I thought of the type
There was a problem hiding this comment.
OK so I'm fine with =>. Still LGTM
There was a problem hiding this comment.
I was about to do the <=> for the types that can be returned. Do you think that's a good idea?
There was a problem hiding this comment.
Yes that's still a good idea, but I thought you meant this was one direction. Am I misunderstanding?
I like that since some types like slice cannot be returned from C to Go since there is no way to get its length
There was a problem hiding this comment.
Oh my bad. I did not include a period after "I like that." I was stating why I thought it was a good idea to have bidirectional arrows. Unless you think it's better to have another column for C => Go conversions?
There was a problem hiding this comment.
IMO I prefer <=> from the whole discussion. Let's go with <=> then :-)
|
I'm gonna sleep so I might reply tomorrow. Thank you for your hard work! |
hajimehoshi
left a comment
There was a problem hiding this comment.
Still LGTM. You can merge if you are ready.
The RegisterFunc type table has func <=> C function in it, but that only holds in the argument direction. Declare a C function as returning a func, and calling it panics: panic: reflect.MakeFunc: value of type *func(int32, int32) int32 is not assignable to type func(int32, int32) int32 Registering is fine, it's the call that goes, and it goes for every signature. Every other case in that return switch leaves the result as the declared type. The reflect.Func case swapped in a *func, registered into that, and handed the pointer back to MakeFunc, which wont take it. If the C function returns NULL the inner RegisterFunc still panics purego: cfn is nil. Left that alone, since whether it should hand you a nil Go func instead looked like your call rather than a bug. Test is a block in TestABI plus a return_func_ptr helper in abi_test.c, and it fails on main with teh panic above. Been broken since #49 back in 2022, which is probably why nobody's hit it: you'd normally take the pointer back as a uintptr and register it youself, and nothing in the repo declares a func return. Closes #501
This PR adds a new function
purego.Funcwhich takes a shared object handle, the name of a symbol in the object and a pointer to a function matching the calling convention. This is the start of a better way of calling C functions. First, it is type safe from the callers perspective. Secondly, it removes repeated code such asstrings.CStringandruntime.KeepAliveinto one place (purego.Func). I ported the objective-c runtime to use this new mechanism to test it out and see how it looks in code. I think it's a big improvement.The downside is that this is a slower method than calling
purego.SyscallNdirectly but will eventually allow for more complex arguments and returns such as floats, complex, and structs.We need to decide on if
purego.Funcshould return an error?I'm kind of leaning towards no since the only error you could potentially recover from is one where the symbol isn't found in the shared object but then what would you do? Stub the function? The other errors (fptr not being a function pointer, too many return arguments) just aren't recoverable in any meaningful way which is why I think it should panic.
Should the objc package use this method or call
purego.SyscallNdirectly?I think the later since this package gets called a LOT in code ported from Objective-C and we want it to be as fast as possible. The overhead of determining the types seems like a waste for these core functions.
TODO:
[X] Document the conversion from Go types to C types as part of
purego.Func.[X] Keep pointer types alive
[ ] Implement those other types.
[ ] Port oto/ebitengine to use this new method?