OSX, SBCL and FFI, Part 2.
(You may want to read part one if you have not already done so.)
In the last post we created a small shared library for calling from SBCL/FFI but first I thought we could prove it works by writing a small test in C, here it is :-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | #include <stdio.h> #include <dlfcn.h> // Declare signatures for our functions typedef int (*add1fn_sig)(int); typedef int (*add2fn_sig)(int,int); int main( int argc, char **argv ) { add1fn_sig add1fn; add2fn_sig add2fn; void *libH = dlopen ( "./libtest.dylib", RTLD_NOW ); if ( !libH ) printf("Library load FAILED, error: %s\n", dlerror()); else { add1fn = (add1fn_sig)dlsym( libH, "add1" ); add1fn ? printf("add1( 10 ) => %i\n", add1fn( 10 )) : printf("Failed to locate add1: %s\n", dlerror()); add2fn = (add2fn_sig)dlsym( libH, "add2" ); add2fn ? printf("add2( 10, 32 ) => %i\n", add2fn( 10, 32 )) : printf("Failed to locate add2: %s\n", dlerror()); } return 0; } |
Save this in a file called loadtest.c in the same place where you saved the code from part one, the reason for this is to ensure that the dlopen() call succeeds as you will see we use the path “./libtest.dylib” in the code.
To build this into the executable issue this command :-
gcc -o loadtest loadtest.c
and hit Return, then just run it like this and if all is well you will see the following output :-
[scharles]cffi $ ./loadtest add1( 10 ) => 11 add2( 10, 32 ) => 42
You can see that we have managed to call our two test functions from the library and lo!, the results are as expected. So how did that all work…? Let’s do a break-down of what just happened.
Line 1:Includes stdio.h for the printf() function.
Line 2:Includes dlfcn.h for the dynamic library functions.
Lines 5,6: These declare function prototypes for out two functions in the library, i C speak you read this as, “add1fn_sig is a pointer to a function that takes one integer as an argument and returns an integer” and similarly for add2fn we have “add2fn_sig is a pointer to a function that takes two integer arguments and returns an integer”. These are used further down to cast the value from dlsym(0 which is “void*” into a form that can be used to actually call the function.
Line 9: This is the declaration of the main() function, this is pretty standard and instantly recognisable to any C hacker on the planet.
Lines 11, 12: These lines declare two local stack variables of the types defined in lines 5 and 6. This is where we save the returned void* values from the dlsym() call. Once these values are set we can use the function call “()” syntax to call the actual routines just as though we had statically linked them in.
Line 14: This performs the dynamic loading if the library from part 1, it assigns an internal handle into “libH” so we can then use the library. If the call fails then we print a message otherwise we try to attach to the two functions. If you *do* experience failure, check the library is where it is supposed to be!
Lines 16,17: We display an error message here if the value from line 14 contains a null pointer i.e. the load failed for some reason.
Line 19: The start of trickery! This is the line that asks the OS to look inside the library to locate a function called “add1″ and create an address that we can use to call it at run-time. The value is saved in “add1fn” which is of a type “add1fn_sig” which means that we can use standard calling syntax to invoke it.
Lines 20-22: Sometimes the ternary operator can be useful! This says “if add1fn is not null, print the result of calling the function with the value ten as the integer argument, otherwise print the reason why we failed to find the function.” You can deliberately change the name in line 19 to break it and see what happens if you like, that is how you learn!
Lines 24-27: These do exactly the same as 20-22 but this time we are using “add2″ and passing in 10 and 32 as the arguments. This function should return the sum of those numbers and we have shown that it does.
Line 29: Exit to the command prompt with a return value of zero to say everything was fine. Even if it wasn’t!
Summary
So there you have it, a working shared library that we can load and call from C. With this we can now attempt to create an SBCL/FFI binding to our library and in the process de-mystify the magic that is FFI!
Onwards to part 3… (coming soon!)