Re: sdl2 and ffi
- Posted by ghaberek (admin) Dec 05, 2022
- 1226 views
I'm afraid these types being squirted at the C functions are a big issue a the moment, as far as I can tell, all the SDL_ routines need pointers of structures as parameters, not types, or actual structures, and the SDL library is littered with these, you can't send the ffi types - yet.
I not sure I understand the problem. What do you mean by "you can't send the ffi types yet?" Maybe you're misunderstanding my intentions for using structures with FFI? If a function expects a pointer to a structure, then it expects a pointer to a structure, period. There's no changing that and I've no intention to change that.
What we need to keep in mind is that, while I'm trying to make wrapping C libraries easier, that doesn't mean it'll ever be easy. Such is the nature of interfacing from another language. When wrapping any C library, it's important to reflect on what parts should work "as is" and what parts should be made more "Euphorian."
Let's take a look at SDL_GetVersion() to start with. For reference, here's how you'd call the function in C. The first is more "traditional" but the second is more "Euphorian."
#include "SDL.h" // // Method 1 - Using the stack // This is typically what you'll see when calling a function directly from C code. // int main( int argc, char *argv[] ) { // Declaring a structure this way creates it on "the stack" not in memory ("the heap") SDL_Version ver; // Using "&" gives us the pointer to the structure, which the function will fill for us SDL_GetVersion( &ver ); // Since this is structure is "local" (on the stack) we use dots to access the members printf( "major: %d\n", ver.major ); printf( "minor: %d\n", ver.minor ); printf( "patch: %d\n", ver.patch ); // No free() here, the structure is cleared from the stack when it goes out of scope return 0; } // // Method 2 - Using the heap // Manually allocate the memory. This is typically avoided and is technically slower. // int main( int argc, char *argv[] ) { // Allocate a pointer to some memory (on "the heap") the size of our structure SDL_Version *ver = malloc( sizeof(SDL_Version) ); // Here we pass "ver" directly since it is already a pointer to some memory SDL_GetVersion( ver ); // Since this is a pointer to a structure, we use "->" to access the members printf( "major: %d\n", ver->major ); printf( "minor: %d\n", ver->minor ); printf( "patch: %d\n", ver->patch ); // Free the memory from the heap otherwise we could cause a memory leak free( ver ); return 0; }
And the naive approach would be to wrap it like this:
-- SDL_version.e include std/ffi.e constant xSDL_GetVersion = define_c_proc( libsdl, "+SDL_GetVersion", {C_POINTER} ) public constant SDL_Version = define_c_struct({ C_UINT8, -- major C_UINT8, -- minor C_UINT8 -- patch }) public procedure SDL_GetVersion( atom ver ) c_proc( xSDL_GetVersion, {ver} ) end procedure
Which would require the user to allocate the structure, call the function, peek the result, and then free the memory, like this:
include std/ffi.e include SDL.e atom ver = allocate_struct( SDL_Version ) SDL_GetVersion( ver ) printf( 1, "major: %d\n", ver[1] ) printf( 1, "minor: %d\n", ver[2] ) printf( 1, "patch: %d\n", ver[3] ) free( ver )
Sure, that code works but it's messy and error-prone and I doubt anyone wants to spend much time writing such tedious boilerplate code.
Instead, we wrap these functions in such a way that we're doing all the boilerplate and heavy lifting behind the scenes. This is where FFI works best.
-- SDL_version.e include std/ffi.e constant xSDL_GetVersion = define_c_proc( libsdl, "+SDL_GetVersion", {C_POINTER} ) -- notice I made this this all-caps and removed the scope, since only -- the wrapper is concerned with dealing with the structure internally. constant SDL_VERSION = define_c_struct({ C_UINT8, -- major C_UINT8, -- minor C_UINT8 -- patch }) public function SDL_GetVersion() atom ver = allocate_struct( SDL_VERSION ) c_proc( xSDL_GetVersion, {ver} ) sequence result = peek_struct( ver, SDL_VERSION ) free( ver ) return result end function
And now the end-user code is much cleaner and "feels" more like Euphoria code.
include SDL.e sequence ver = SDL_GetVersion() printf( 1, "major: %d\n", ver[1] ) printf( 1, "minor: %d\n", ver[2] ) printf( 1, "patch: %d\n", ver[3] )
This is the Way.
-Greg
P.S. Here's a good quick write-up on "stack" vs "heap" in C: https://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap#80113