Sunday, September 06, 2015

Accessing the Foundation library with LiveCode Builder

This blog post is part of an ongoing series about writing LiveCode Builder applications without the LiveCode engine.

The LiveCode Foundation library

LiveCode includes a "foundation" library (called, unsurprisingly, libfoundation) which provides a lot of useful functions that work on all the platforms that LiveCode supports. This is used to make sure that LiveCode works in the same way no matter which operating system or processor you're using. libfoundation is compiled into both the LiveCode engine and LiveCode Builder's lc-run tool, so it's always available.

libfoundation is written in C and C++. The functions available in the library are declared in the foundation.h header file.

Among other capabilities, libfoundation handles encoding and decoding text. This provides an opportunity to fix one of the problems with the "hello world" program I described in a previous post.

Foreign function access to libfoundation

The "hello world" program read in a file and wrote it out to the standard output stream. Unlike "hello world" programs seen elsewhere, it *didn't* write out a string, e.g.:

write "Hello World!" to the output stream

This doesn't work because write needs to receive Data, and converting a String to Data requires encoding (using a suitable string encoding). And unfortunately, the LiveCode Builder library doesn't supply any text encoding/decoding syntax, although I'm working on it.

However, and fortunately for this blog post, libfoundation supplies a suitable function, MCStringEncode. Its C++ declaration looks like:

bool MCStringEncode(MCStringRef string, MCStringEncoding encoding, bool is_external_rep, MCDataRef& r_data);

You can use it in a LiveCode Builder program by declaring it as a foreign handler. The com.livecode.foreign module provides some helpful declarations for C and C++ types.

use com.livecode.foreign

foreign handler MCStringEncode(in Source as String, \
      in Encoding as CInt, in IsExternalRep as CBool, \
      out Encoded as Data) returns CBool binds to "<builtin>"

CInt and CBool are C & C++'s int and bool types, respectively.

Encoding a string with UTF-8

Next, you can write a LiveCode Builder handler that encodes a string using UTF-8 (an 8-bit Unicode encoding). Almost every operating system will Do The Right Thing if you write UTF-8 encoded text to standard output; the only ones that might complain are some versions of Windows and some weirdly-configured Linux systems.

handler EncodeUTF8(in pString as String) returns Data
   variable tEncoded as Data
   MCStringEncode(pString, 4 /* UTF-8 */, false, tEncoded)
   return tEncoded
end handler

The "4" in there is a magic number that comes from libfoundation's kMCStringEncodingUTF8 constant. Also, you should always pass false to the IsExternalRep argument (for historical reasons).

A better "hello world" program

Putting this all together, you can now write an improved "hello world" program that doesn't get its text from an external file.

module org.example.helloworld2

use com.livecode.foreign

foreign handler MCStringEncode(in Source as String, \
      in Encoding as CInt, in IsExternalRep as CBool, \
      out Encoded as Data) returns CBool binds to "<builtin>"

handler EncodeUTF8(in pString as String) returns Data
   variable tEncoded as Data
   MCStringEncode(pString, 4 /* UTF-8 */, false, tEncoded)
   return tEncoded
end handler

public handler Main()
   write EncodeUTF8("Hello World!\n") to the output stream
end handler

end module

If you compile and run this program, you'll now get the same "Hello World!" message -- but this time, it's taking some text, turning it into Data by encoding it, and writing it out, rather than just regurgitating some previously-encoded data.

Other neat stuff

There's other cool (and, often, terribly unsafe) stuff you can do with direct access to libfoundation functions, like allocate Pointers to new memory buffers and directly manipulate LiveCode types & values. However, most of libfoundation's capabilities are already available using normal LiveCode Builder syntax.

The real power of foreign handler declarations becomes apparent when accessing functions that aren't in libfoundation — and this may be the subject of a future blog post!

1 comment:

Monte said...

Very interesting Peter, thanks

Looking forward to the post about accessing functions that aren’t in libfoundation.