Introduction
One of the most important parts of the upcoming 2.9 release is that it contains
an updated Linux engine full to the brim with features that were previously only
available on Windows and Mac OS X. In particular, the old UNIX external mechanism
has been replaced by one using shared libraries - thus gaining the same performance
and implementation ease as the other platforms.
In this article, we will introduce an updated version of the External Creator
and show how to use it to produce a simple external that can be used on Linux.
Before we begin
Before going any further you will need:
Setting up the environment
As with the other platforms, before doing anything in a lower-level language on
Linux you need
to set up your build environment appropriately. On Windows and Mac OS X there are
obvious choices for an IDE to build your external in, however on Linux there is
no clear winner in this regard and so we have opted for a simple Makefile system.
To help with this, the new version of the Externals Environment and External Creator
is able to generate Makefiles in a simple structure for use on Linux, and with a
few simple terminal commands you can be up and running with Linux external development
in no time.
So, before continuing take some time to unpack the ExternalsEnvironmentV3.zip file
into a suitable place on your hard-drive and take a quick look through its contents.
Also feel free to rename the unpacked folder ExternalsEnvironmentV3 to something
more to your liking - but make a note of its location as this will be needed later
when compiling and running your externals. (In future, we will refer to this folder
as the environment folder).
A simple external - wrapping iconv
Setting up the project
As an introduction to external development for Linux, we are going to wrap a simple
system library call (iconv) that is used for text conversions allowing access to
its features inside Revolution.
Having unpacked the environment and put it in a suitable place, we now need to generate
the skeleton files for our external. So, without further ado, load up your
copy of Revolution on Linux and open the 'External Creator V3.rev' stack inside
your environment folder.
With this stack loaded, do the following:
fill in the Name field with rnaiconv (as in other external
articles, we using the three-letter prefix rna to stand for Revolution
Newsletter Article)
-
choose Linux as the target platform
-
choose C++ (no exceptions, no rtti) as the language
-
ensure that the Linux installation path is correct
Once you are happy with your settings, just click Generate and
the creator will chug away for a moment producing the necessary files.
Exploring our new project
Having generated the project, you should now find an rnaiconv folder
within the environment, as well as two files in the root of the environment folder
- Makefile and run (more on these later).
Taking a look in the rnaiconv folder should reveal a number of files and folders:
- rnaiconvtest.rev - an empty stack that will load the external (used for
testing and debugging)
- Makefile - the external's Makefile which will be read by the make
command to build the external
- src/rnaiconv.cpp - the outline of the main C++ file that will contain the
implementation of our functions
Now we have this basic structure we can go onto actually implementing some functionality...
All about iconv
The iconv command is ubiquitous on Linux systems. This flexible tool allows you
to convert text files between different encodings on the command line (to find out
more about this type man iconv at a terminal prompt).
As it turns out, this tool simply wraps a system library call that provides this
ability - and our external will give us direct access to this ability without need
to resort to the shell or open process Revolution syntax.
The iconv library consists of three functions:
- iconv_open - create an iconv_t converter object between the source
and target encodings
- iconv_close - destroy a previously created iconv_t converter object
- iconv - the function that actually does the conversion
To use it is simple, you first open the converter with the appropriate source and
target encodings, do your conversion, then close the converter.
We will wrap this sequence of calls using an external command with the following prototype:
command rnaIconv pSrcEncoding, pDstEncoding, pInTextVar, pOutTextVar
Something to note here is that we have to pass a variable names to the function
for both input and output - this is because we cannot pass arbitrary binary data
to external commands. To get around this, we pass variable names and then use the
GetVariableEx and SetVariableEx calls to get and set their values (for more details
on these two calls see the previous external writing article: External Writing for the Unititiated - Part 2)
Before going any further, make sure you have your favourite linux text editor loaded
(I tend to use gedit) and open the rnaiconv.cpp file.
Sorting out the declarations
Before actually implementing our function, we first need to sort out a couple of
declarations - we need to import the declarations of the system functions we wish
to use, as well as declare to Revolution that we are implementing an external function.
To do the former, we just need to include the relevent header files. To do this we
use #include directives. The one to use to gain access to iconv is <iconv.h>.
So, add the following lines just on the line after the BEGIN USER DEFINITIONS comment:
To actually declare our external function to Revolution we need to add a line to the externals
export table. In our environment this is easy, there are a number of macros we use
to do this declared in <revolution/external.h>. In this case, we use the EXTERNAL_DECLARE_COMMAND
one. Place the following line on the line after the BEGIN USER DECLARATIONS comment:
EXTERNAL_DECLARE_COMMAND("rnaiconv", rnaIconv)
Now we have the declarations sorted out, we can actually implement something!
Implementing the function
Although the method used to build externals differs between platforms, the actual
Externals API is identical - a Linux external function or command is defined in
exactly the same was as on the other two platforms, and such functions and commands
have access to the same set of engine calls. As these details have been covered
in previous articles, we'll instead go straight onto the function implementation.
Add the following function to the rnaiconv.cpp file after the #include <iconv.h>
directive we've already added:
void rnaIconv( char *p_arguments[], int p_argument_count,
char **r_result, Bool *r_pass, Bool *r_err)
{
int t_success;
if (p_argument_count != 4)
{
*r_result = strdup("wrong number of parameters");
*r_err = True;
*r_pass = False;
return;
}
ExternalString t_src_text;
GetVariableEx(p_arguments[2], "", &t_src_text, &t_success);
if (t_success == EXTERNAL_FAILURE)
{
*r_result = strdup("unknown input variable");
*r_err = True;
*r_pass = False;
return;
}
iconv_t t_converter;
t_converter = iconv_open(p_arguments[1], p_arguments[0]);
if (t_converter == NULL)
{
*r_result = strdup("unable to create converter");
*r_err = True;
*r_pass = False;
return;
}
char *t_in_text_ptr;
size_t t_in_text_left;
t_in_text_ptr = (char *)t_src_text . buffer;
t_in_text_left = t_src_text . length;
char *t_out_text_base;
size_t t_out_text_frontier;
size_t t_out_text_limit;
t_out_text_base = NULL;
t_out_text_frontier = 0;
t_out_text_limit = 0;
bool t_memory_error;
t_memory_error = false;
bool t_conversion_error;
t_conversion_error = false;
while(t_in_text_left != 0)
{
char *t_new_out_text_base;
t_new_out_text_base = ( char *)realloc(t_out_text_base, t_out_text_limit + t_in_text_left * 2);
if (t_new_out_text_base == NULL)
{
t_memory_error = true;
break;
}
t_out_text_limit += t_in_text_left * 2;
t_out_text_base = t_new_out_text_base;
char *t_out_text_ptr;
t_out_text_ptr = t_out_text_base + t_out_text_frontier;
size_t t_out_text_left;
t_out_text_left = t_out_text_limit - t_out_text_frontier;
size_t t_iconv_result;
t_iconv_result = iconv(t_converter, &t_in_text_ptr, &t_in_text_left, &t_out_text_ptr, &t_out_text_left);
if (t_iconv_result == (size_t)(-1) && errno != E2BIG)
{
t_conversion_error = true;
break;
}
t_out_text_frontier += (t_out_text_limit - t_out_text_frontier) - t_out_text_left;
}
if (t_memory_error)
{
*r_result = strdup("out of memory");
*r_pass = False;
*r_err = True;
}
else if (t_conversion_error)
{
*r_result = strdup("conversion error");
*r_pass = False;
*r_err = True;
}
else
{
ExternalString t_out_text;
t_out_text . buffer = t_out_text_base;
t_out_text . length = t_out_text_frontier;
SetVariableEx(p_arguments[3], "", &t_out_text, &t_success);
if (t_success == EXTERNAL_FAILURE)
{
*r_result = strdup("unknown output variable");
*r_pass = False;
*r_err = True;
}
else
{
*r_result = strdup("");
*r_pass = False;
*r_err = False;
}
}
free(t_out_text_base);
iconv_close(t_converter);
}
This function is basically an implementation of what we have outlined above - it opens an iconv converter, processes all the input, then closes the converter.
Building our external
Now that we've coded our function, we need to build and test it. As already mentioned,
the External Creator sets up a simple Makefiles for building on Linux. A Makefile
is simply a text file that describes the commands required to convert the source
files of a project into an actual executable or shared library.
The ones the External Creator generates are predefined to make it easy for you to
get started with external writing. Two Makefiles will have been generated, one in
the environment and one in the rnaiconv folder. If you take a
look at the latter, you should see the following:
NAME=rnaiconv
TYPE=library
SOURCES=rnaiconv.cpp
# To add your own source files put them into this list.
# Note that source files are searched for in src/
#
CUSTOM_DEFINES=
# Add any custom include directions needed by your
# external to this list
#
CUSTOM_INCLUDES=./src
# Add any custom static libraries needed by your external
# to this list
#
CUSTOM_STATIC_LIBS=external
# Add any custom dynamic libraries needed by your external
# to this list
#
CUSTOM_DYNAMIC_LIBS=
# Add any specific compiler options you need here
#
CUSTOM_CCFLAGS=-fno-exceptions -fno-rtti
# Add any specific link options you need here
#
CUSTOM_LDFLAGS=
include ../configurations/library.linux.makefile
As you can see from the comments, this file is ready to be added to as your project
grows. The most important line here is the 'SOURCES' definition. To add more source
files to your external, simply append the name of the file to this line (making
sure they are separated by spaces). (Note that due to the design of the system,
you need to make sure that your source files are all located in the src
sub-folder).
The project Makefile is not designed to be invoked directly, instead it is called
by the Makefile in the environment folder when all the project's dependencies
are also built - this allows more complicated projects to be built up by decomposing
them into smaller sub-projects present in separate folders with their own Makefiles.
In this case, your external project will currently depend on libexternal
- the standard glue-code required for a Revolution external.
Additionally, the environment Makefile offers three
targets for each project
- one to build in a debug configuration, one to build
in a release configuration and one to clean the intermediate files thus enabling
a full rebuild. To use these targets you would use the commands:
make rnaiconv.debug
make rnaiconv.release
make rnaiconv.clean
To actually go ahead and build your project is simple:
Open up a terminal window
Execute cd <environment folder path> (where <environment folder
path> is the full path to the unpacked ExternalsEnvironmentV3 folder)
Execute make
rnaiconv.debug
After a few moments, control should return to you without any error messages having
been reported.
(If you do have problems and things don't seem to compile correctly, the NewsletterArticle3.zip
archive you will have downloaded contains a pre-prepared set of files that should
work straight out of the box. Simply unpack this archive, and copy the folder and
other files into your environment folder. Compiling as above should then
result in everything working as expected.)
Testing our external
Now that we've built our external, we need to test it. The External Creator will
have already set up an empty test stack for us to use. To make it even easier, it
will also have created a simple run script in the environment folder.
This run script combines both building and launching into a single command. To use
it just type the comand ./run rnaiconv debug at your terminal and you
should find Revolution popping up in due course with a blank stack.
With this on the screen, its easy to see our external in action:
Create a button called "Convert"
Create one field called "Input"
Create another field called "Output"
Edit the script of the button and put in this mouseUp handler:
on mouseUp
local tInput, tOutput
put field "Input" into tInput
rnaIconv "ISO_8859-1", "UTF-16LE", "tInput", "tOutput"
put tOutput into field "Output"
end mouseUp
Once this is done, put some text in the input field (I always find "Hello World!"
a useful phrase) and just click the button. What you should see is "Hello World!"
in the output field, except spaced out - this is because the output encoding we've
chosen has two bytes per character, which for Roman script text will have one NUL
byte per character.
To see something more interesting, try entering some input text with accented characters
and use "ISO_8859-1" for the source encoding and "MACINTOSH" for the output encoding.
In this case you should changes which comes from the fact that accented characters
are in different places for these encodings.
To get a full list of the encodings that iconv supports you can use the
command iconv --list at the terminal prompt.
Moving forward
This article has hopefully demonstrated how straightforward it can be to produce
an external for Linux. You can use the External Creator as many times as you want
inside a given environment folder to create additional externals, all of which can
be built and run using the run script in a similar manner to that described
above the rnaiconv project.
When you are happy with your external and want to install it permanently in your
distribution, you just copy the shared library into the appropriate place inside
your My Revolution <Edition> folder. Full details of this can be
found in a previous article External Writing for the Uninitiated - Part 1.
You can find the actual shared library file you need for this inside the _build/release/
or _build/debug/ folders in your environment folder.
Happy external writing!
Revision 1 - 2007/10/04
|