Previous Section | Next Section | Table of Contents | Glossary | Index |
This tutorial is meant to cover the basics of CCL
for
calling external C functions and passing data back and forth.
These basics will provide the foundation for more advanced
techniques which will allow access to the various external
libraries and toolkits.
The first step is to start with a simple C dynamic library
in order to actually observe what is actually passing between
CCL
and C. So, some C code is in order:
Create the file typetest.c, and put the following code into it:
#include <stdio.h> void void_void_test(void) { printf("Entered %s:\n", __FUNCTION__); printf("Exited %s:\n", __FUNCTION__); fflush(stdout); } signed char sc_sc_test(signed char data) { printf("Entered %s:\n", __FUNCTION__); printf("Data In: %d\n", (signed int)data); printf("Exited %s:\n", __FUNCTION__); fflush(stdout); return data; } unsigned char uc_uc_test(unsigned char data) { printf("Entered %s:\n", __FUNCTION__); printf("Data In: %d\n", (signed int)data); printf("Exited %s:\n", __FUNCTION__); fflush(stdout); return data; }
This defines three functions. If you're familiar with C,
notice that there's no main()
, because we're
just building a library, not an executable.
The function void_void_test()
doesn't
take any parameters, and doesn't return anything, but it prints
two lines to let us know it was called.
sc_sc_test()
takes a signed char as a
parameter, prints it, and returns it.
uc_uc_test()
does the same thing, but with an
unsigned char. Their purpose is just to prove to us that we
really can call C functions, pass them values, and get values
back from them.
This code is compiled into a dynamic library on OS X 10.3.4 with the command:
gcc -dynamiclib -Wall -o libtypetest.dylib typetest.c \ -install_name ./libtypetest.dylib
Users of 64-bit platforms may need to pass options such as "-m64" to gcc, may need to give the output library a different extension (such as ".so"), and may need to user slightly different values for other options in order to create an equivalent test library.
The -dynamiclib tells gcc that we will be compiling this
into a dynamic library and not an executable binary program.
The output filename is "libtypetest.dylib". Notice that we
chose a name which follows the normal OS X convention, being in
the form "libXXXXX.dylib", so that other programs can link to
the library. CCL
doesn't need it to be this way, but it is
a good idea to adhere to existing conventions.
The -install_name flag is primarily used when building OS X "bundles". In this case, we are not using it, so we put a placeholder into it, "./libtypetest.dylib". If we wanted to use typetest in a bundle, the -install_name argument would be a relative path from some "current" directory.
After creating this library, the first step is to tell
CCL
to open the dynamic library. This is done by calling
.
Welcome to CCL
Version (Beta: Darwin) 0.14.2-040506!
? (open-shared-library "/Users/andewl/openmcl/libtypetest.dylib")
#<SHLIB /Users/andewl/openmcl/libtypetest.dylib #x638EF3E>
You should use an absolute path here; using a relative one, such as just "libtypetest.dylib", would appear to work, but there are subtle problems which occur after reloading it. See the Darwin notes on for details. It would be a bad idea anyway, because software should never rely on its starting directory being anything in particular.
This command returns a reference to the opened shared library, and
CCL
also adds one to the global variable
ccl::*shared-libraries*
:
? ccl::*shared-libraries* (#<SHLIB /Users/andewl/openmcl/libtypetest.dylib #x638EF3E> #<SHLIB /usr/lib/libSystem.B.dylib #x606179E>)
Before we call anything, let's check that the individual functions can actually be found by the system. We don't have to do this, but it helps to know how to find out whether this is the problem, when something goes wrong. We use external-call:
? (external "_void_void_test") #<EXTERNAL-ENTRY-POINT "_void_void_test" (#x000CFDF8) /Users/andewl/openmcl/libtypetest.dylib #x638EDF6> ? (external "_sc_sc_test") #<EXTERNAL-ENTRY-POINT "_sc_sc_test" (#x000CFE50) /Users/andewl/openmcl/libtypetest.dylib #x638EB3E> ? (external "_uc_uc_test") #<EXTERNAL-ENTRY-POINT "_uc_uc_test" (#x000CFED4) /Users/andewl/openmcl/libtypetest.dylib #x638E626>
Notice that the actual function names have been "mangled" by the C linker. The first function was named "void_void_test" in typetest.c, but in libtypetest.dylib, it has an underscore (a "_" symbol) before it: "_void_void_test". So, this is the name which you have to use. The mangling - the way the name is changed - may be different for other operating systems or other versions, so you need to "just know" how it's done...
Also, pay particular attention to the fact that a hexadecimal value appears in the EXTERNAL-ENTRY-POINT. (#x000CFDF8, for example - but what it is doesn't matter.) These hex numbers mean that the function can be dereferenced. Functions which aren't found will not have a hex number. For example:
? (external "functiondoesnotexist") #<EXTERNAL-ENTRY-POINT "functiondoesnotexist" {unresolved} #x638E3F6>
The "unresolved" tells us that CCL
wasn't able to find this
function, which means you would get an error, "Can't resolve foreign
symbol," if you tried to call it.
These external function references also are stored in a
hash table which is accessible through a global variable,
ccl::*eeps*
.
At this point, we are ready to try our first external function call:
? (external-call "_void_void_test" :void) Entered void_void_test: Exited void_void_test: NIL
We used , which is is the normal mechanism for accessing externally linked code. The "_void_void_test" is the mangled name of the external function. The :void refers to the return type of the function.
The next step is to try passing a value to C, and getting one back:
? (external-call "_sc_sc_test" :signed-byte -128 :signed-byte) Entered sc_sc_test: Data In: -128 Exited sc_sc_test: -128
The first :signed-byte gives the type of the first argument, and then -128 gives the value to pass for it. The second :signed-byte gives the return type. The return type is always given by the last argument to .
Everything looks good. Now, let's try a number outside the range which fits in one byte:
? (external-call "_sc_sc_test" :signed-byte -567 :signed-byte) Entered sc_sc_test: Data In: -55 Exited sc_sc_test: -55
Hmmmm. A little odd. Let's look at the unsigned stuff to see how it reacts:
? (external-call "_uc_uc_test" :unsigned-byte 255 :unsigned-byte) Entered uc_uc_test: Data In: 255 Exited uc_uc_test: 255
That looks okay. Now, let's go outside the valid range again:
? (external-call "_uc_uc_test" :unsigned-byte 567 :unsigned-byte) Entered uc_uc_test: Data In: 55 Exited uc_uc_test: 55 ? (external-call "_uc_uc_test" :unsigned-byte -567 :unsigned-byte) Entered uc_uc_test: Data In: 201 Exited uc_uc_test: 201
Since a signed byte can only hold values from -128 through 127, and an unsigned one can only hold values from 0 through 255, any number outside that range gets "clipped": only the low eight bits of it are used.
What is important to remember is that external function calls have very few safety checks. Data outside the valid range for its type will silently do very strange things; pointers outside the valid range can very well crash the system.
That's it for our first example library. If you're still following along, let's add some more C code to look at the rest of the primitive types. Then we'll need to recompile the dynamic library, load it again, and then we can see what happens.
Add the following code to typetest.c:
int si_si_test(int data) { printf("Entered %s:\n", __FUNCTION__); printf("Data In: %d\n", data); printf("Exited %s:\n", __FUNCTION__); fflush(stdout); return data; } long sl_sl_test(long data) { printf("Entered %s:\n", __FUNCTION__); printf("Data In: %ld\n", data); printf("Exited %s:\n", __FUNCTION__); fflush(stdout); return data; } long long sll_sll_test(long long data) { printf("Entered %s:\n", __FUNCTION__); printf("Data In: %lld\n", data); printf("Exited %s:\n", __FUNCTION__); fflush(stdout); return data; } float f_f_test(float data) { printf("Entered %s:\n", __FUNCTION__); printf("Data In: %e\n", data); printf("Exited %s:\n", __FUNCTION__); fflush(stdout); return data; } double d_d_test(double data) { printf("Entered %s:\n", __FUNCTION__); printf("Data In: %e\n", data); printf("Exited %s:\n", __FUNCTION__); fflush(stdout); return data; }
The command line to compile the dynamic library is the same as before:
gcc -dynamiclib -Wall -o libtypetest.dylib typetest.c \ -install_name ./libtypetest.dylib
Now, restart CCL
. This step is required because
CCL
cannot close and reload a dynamic library on OS
X.
Have you restarted? Okay, try out the new code:
Welcome to CCL
Version (Beta: Darwin) 0.14.2-040506!
? (open-shared-library "/Users/andewl/openmcl/libtypetest.dylib")
#<SHLIB /Users/andewl/openmcl/libtypetest.dylib #x638EF3E>
? (external-call "_si_si_test" :signed-fullword -178965 :signed-fullword)
Entered si_si_test:
Data In: -178965
Exited si_si_test:
-178965
? ;; long is the same size as int on 32-bit machines.
(external-call "_sl_sl_test" :signed-fullword -178965 :signed-fullword)
Entered sl_sl_test:
Data In: -178965
Exited sl_sl_test:
-178965
? (external-call "_sll_sll_test"
:signed-doubleword -973891578912 :signed-doubleword)
Entered sll_sll_test:
Data In: -973891578912
Exited sll_sll_test:
-973891578912
Okay, everything seems to be acting as expected. However,
just to remind you that most of this stuff has no safety net,
here's what happens if somebody mistakes
sl_sl_test()
for
sll_sll_test()
, thinking that a long is
actually a doubleword:
? (external-call "_sl_sl_test" :signed-doubleword -973891578912 :signed-doubleword) Entered sl_sl_test: Data In: -227 Exited sl_sl_test: -974957576192
Ouch. The C function changes the value with no warning
that something is wrong. Even worse, it manages to pass the
original value back to CCL
, which hides the fact that
something is wrong.
Finally, let's take a look at doing this with floating-point numbers.
Welcome to CCL
Version (Beta: Darwin) 0.14.2-040506!
? (open-shared-library "/Users/andewl/openmcl/libtypetest.dylib")
#<SHLIB /Users/andewl/openmcl/libtypetest.dylib #x638EF3E>
? (external-call "_f_f_test" :single-float -1.256791e+11 :single-float)
Entered f_f_test:
Data In: -1.256791e+11
Exited f_f_test:
-1.256791E+11
? (external-call "_d_d_test" :double-float -1.256791d+290 :double-float)
Entered d_d_test:
Data In: -1.256791e+290
Exited d_d_test:
-1.256791D+290
Notice that the number ends with "...e+11" for the single-float, and "...d+290" for the double-float. Lisp has both of these float types itself, and the d instead of the e is how you specify which to create. If you tried to pass :double-float 1.0e2 to external-call, Lisp would be nice enough to notice and give you a type error. Don't get the :double-float wrong, though, because then there's no protection.
Congratulations! You now know how to call external C functions from
within CCL
, and pass numbers back and forth. Now that the basic
mechanics of calling and passing work, the next step is to examine how
to pass more complex data structures around.
Previous Section | Next Section | Table of Contents | Glossary | Index |