Re: sdl2 and ffi

new topic     » goto parent     » topic index » view thread      » older message » newer message
ChrisB said...

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

new topic     » goto parent     » topic index » view thread      » older message » newer message

Search



Quick Links

User menu

Not signed in.

Misc Menu