This blog post is part of an ongoing series about writing LiveCode Builder applications without the LiveCode engine.
Currently, the LiveCode Builder (LCB) standard library is fairly minimal. This means that there are some types of task for which you'll want to go beyond the standard library.
In a previous post, I described how to use LiveCode's foundation library. This lets you access plenty of built-in LiveCode functionality that isn't directly exposed to LCB code yet.
Someone else's problem
Often someone's already wrapped the functions that you need in another program, especially on Linux. You can run that program as a subprocess to access it. In LiveCode Script, you could use the shell function to run an external program. Unfortunately, the LCB standard library doesn't have an equivalent feature yet!
On the other hand, the standard C library's system(3) function can be used to run a shell command. Its prototype is:
int system(const char *command);
In this post, I'll describe how LCB's foreign function interface lets you call it.
Declaring a foreign handler
As last time, you can use the foreign handler syntax to declare the C library function. The com.livecode.foreign provides some important C types.
use com.livecode.foreign foreign handler _system(in pCommand as ZStringNative) \ returns CInt binds to "system"
Some things to bear in mind here:
- I've named the foreign handler _system because the all-lowercase identifier system is reserved for syntax tokens
- The ZStringNative type automatically converts a LCB string into a null-terminated string into whatever encoding LiveCode thinks is the system's "native" encoding.
- Because the C library is always linked into the LiveCode program when it's started, you don't need to specify a library name in the binds to clause; you can just use the name of the system(3) function.
Understanding the results
So, now you've declared the foreign handler, that's it! You can now just _system("rm -rf /opt/runrev") (or some other helpful operation). Right?
Well, not quite. If you want to know whether the shell command succeeded, you'll need to interpret the return value of the _system handler, and unfortunately, this isn't just the exit status of the command. From the system(3) man page:
The value returned is -1 on error (e.g., fork(2) failed), and the return status of the command otherwise. This latter return status is in the format specified in wait(2). Thus, the exit code of the command will be WEXITSTATUS(status). In case /bin/sh could not be executed, the exit status will be that of a command that does exit(127).
So if the _system handler returns -1, then an error occurred. Otherwise, it's necessary to do something equivalent to the WIFEXITED C macro to check if the command ran normally. If it didn't, then some sort of abnormal condition occurred in the command (e.g. it was killed). Finally, the actual exit status is extracted by doing something equivalent to the WEXITSTATUS C macro.
On Linux, these two macros are defined as follows:
#define WIFEXITED(status) __WIFEXITED (__WAIT_INT (status)) #define WEXITSTATUS(status) __WEXITSTATUS (__WAIT_INT (status)) #define __WIFEXITED(status) (__WTERMSIG(status) == 0) #define __WEXITSTATUS(status) (((status) & 0xff00) >> 8) #define __WTERMSIG(status) ((status) & 0x7f) #define __WAIT_INT(status) (status)
Or, more succinctly:
#define WIFEXITED(status) (((status) & 0x7f) == 0) #define WEXITSTATUS(status) (((status) & 0xff00) >> 8)
This is enough to be able to fully define a function that runs a shell command and returns its exit status.
module org.example.system use com.livecode.foreign private foreign handler _system(in pCommand as ZStringNative) \ returns CInt binds to "system" /* Run the shell commandand wait for it to finish. Returns the exit status of if the command completed, and nothing if an error occurred or the command exited abnormally. */ public handler System(in pCommand as String) \ returns optional Number variable tStatus as Number put _system(pCommand) into tStatus -- Check for error if tStatus is -1 then return nothing end if -- Check for abnormal exit if (127 bitwise and tStatus) is not 0 then return nothing end if -- Return exit status return 255 bitwise and (tStatus shifted right by 8 bitwise) end module
Tip of the iceberg
This post has hopefully demonstrated the potential of LiveCode Builder's FFI. Even if you use only the C standard library's functions, you gain access to almost everything that the operating system is capable of!
Using a C function from LCB involves reading the manual pages to find out how the function should be used, and how best to map its arguments and return values onto LCB types; often, reading C library header files to understand how particular values should be encoded or decoded; and finally, binding the library function and providing a wrapper that makes it comfortable use from LCB programs.
LiveCode Builder can do a lot more than just making widgets and — as I hope I've demonstrated — can do useful things without the rest of the LiveCode engine. Download LiveCode 8 and try some things out!