In an ideal world you would be able to solve any computing problem without leaving the confines of Revolution. Unfortunately, though, there will always be things that very-high level programming is not suited for. For example, an implementation of an algorithm in Revolution may be too slow, you may need a specific OS feature that isn’t wrapped by a command or function in Revolution, or you may just want to access a pre-existing library in Revolution.
To support such cases, Revolution has what we call the ‘externals interface’. An external is simply a shared library (dll on Windows, bundle on Mac OS X) written in a lower-level language that can be loaded (at runtime) into the Revolution environment. Once loaded, your scripting environment is augmented with the commands and functions exported by the external.
In this, the first of a series of articles, I will cover the basics of writing a (cross-platform) external for Windows and Mac OS X in C/C++.
Before we begin
Before going any further you will need:
Note: The need for Revolution 2.7.x is just because the External Creator assumes the new installation structure is 2.7 when setting up projects – the externals you build will operate in any Revolution version and edition.
Setting up the environment
Before you can begin doing anything in a lower-level language, it is important that you set up your ‘build environment’. Modern IDEs such as Visual C++ and XCode make this straightforward when targetting a single platform – but it can be trickier if you want to use the same code base to compile on multiple platforms (or even multiple variants of the same platform such as exist with Mac OS X).
To help with this, a skeleton environment similar to the system we use internally is included. This skeleton has the basic structure allowing you to write externals using both Visual C++ 2005 and XCode 2.4.x.
After unpacking ‘ExternalsEnvironmentV1.zip’ onto a suitable place on your hard-drive, it is worth spending a few moments familiarising yourself with its contents:
The contents you see here serve the following purposes:
External Creator V1.rev – a small Revolution utility to help you setup an external project
libexternal – this project folder contains the special glue-code
that all externals need to be linked with in order to bind to Revolution. (It is a project in its own right that builds as a static library).
templates – this folder contains template project files used by the External
Creator to setup new external projects.
configurations – this folder contains two kinds of file vsprops
and xcconfig. These contain settings for different variants of builds on different platforms.
In later sections we will refer to this folder as the environment – in addition to what it currently contains, it will also hold our external projects themselves.
Note: Although the environment folder will unpack to ExternalsEnvironmentV1,
you are free to rename it to anything.s
An aside on projects
Both Visual Studio and XCode rely on the idea of projects to organize development. A project defines the type of entity to be built along with all the build parameters and source-files needed to do the build.
In Visual Studio, projects are organized into solutions - a collection of projects that can reference each other to help organize dependancies. In our case, our solution will contain one project for each different external, all which depend on the project libexternal to ensure that the special glue-code
that makes an external an external is available.
In XCode, on the other hand, there is no need for a solution. Any XCode project can reference another XCode project and depend on what it produces. However, in the end this amounts to the same thing as Visual Studio – although the XCode model is sligthly more flexible.
An aside on build configurations
One concept that will be new to you if you haven’t built projects in lower-level languages before will be that of build configuration. A build configuration is a collection of settings that define both the type of build and also the variant of the current platform to build for.
For example, lower-level languages typically require you to compile in a different way if you wish to debug your code (this is the Debug configuration in our case) or release your project (this is one of the Release configurations in our case).
Furthermore, the existence of single OSes targetting different architectures means that there are generally a multiplicity of Release builds needed. For example, for Mac OS X, we need ones for both PowerPC and Intel, as well as one for both (i.e.
However, you needn’t worry about all the details here as the skeleton environment comes with a collection of configurations that are completely compatible with Revolution (i.e. they should just work!).
Our first external – working with text: a take on Hello, World!
Setting up the project
The first thing to do is to create a skeleton project for our first external. Included with the skeleton environment is a Revolution stack called External Creator.
This little tool helps to start off creating an external by setting up the project (potentially for multiple platforms) including directory structure and build configurations. Furthermore, it creates an empty test stack to load the new external.
So, with the External Creator loaded into Revolution do the following:
fill in the Name field with rnahello (we use the three-letter prefix rna to stand for Revolution Newsletter Article)
check the appropriate platforms
choose C++ (no exceptions, no rtti) as the language
ensure the Installation paths are filled in correctly.
You should end up with something like:
When you have configured all your settings, just click Generate.
Exploring our new project
Having generated the project, you should now find an rnahello folder within the environment. It is worth spending a few moments exploring this folder to see what has been created:
Here we have the following:
rnahello.vsproj – the Visual Studio project file (present if you checked
rnahello.xcodeproj – the XCode project folder (present if you checks
Mac OS X)
test_rnahello.rev – an empty stack that will load the external (used for testing and debugging)
src/rnahello.cpp – the outline of our main C++ file that will contain the implementation of our commands and functions
So, now we have the structure in place we can actually add some functionality...
Getting started with the IDEs
Open up Visual C++ or XCode depending on the platform, and load either externals.sln
(into Visual Studio), or rnahello.xcodeproj into XCode. In both cases you should be presented with (what will, at least, become) a familiar interface.
Both IDEs use a hierarchical approach to manage projects – in the case of XCode, the project itself will be the root node, whereas in Visual Studio the root node will be the solution:
You may notice in Visual Studio that one of the projects is highlighted in bold. This signifies which is the current Startup Project - the project that is built and run whenever you click the Run button. You can change the startup project by right-clicking on a project node and choosing Set as Startup Project
- this will becomed particularly important for you if you add new externals to the
My personal preference is to use XCode in all-in-one mode where you switch between Edit/Build/Debug by the little tab control in the top-left – this article reflects this preference. By default, however, XCode is not configured in this mode – to change this select the General pane in Preferences and choose
All-in-one from the Layout option menu.
You may wish to spend some time looking around the IDEs and the project/solution structure we have created to familiarise yourself with them before going any further.
Adding functionality – our first function
At this stage, you should be looking at the rnahello project created with the External Creator. This project has all the appropriate build configurations configured as well as the necessary setup for launching the test stack on run so you can debug and/or see your external running – basically we have a skeleton external that will work, but not actually provide any functionality to the development environment.
In this section we will add an external function to our external. An external
function is exactly the same as a function handler (defined by the function
keyword) you will be familiar with in Revolution, except that it’s implementation is in native code defined in an external. As with a function handler, an external function takes a number of parameters as input and returns a value as a result.
The External Creator will have created an outline main file rnahello.cpp
which you may want to familiarise yourself with before continuing.
We will now add our first external function to our project – called rnahellouser.
This will take one parameter (a name) and return a friendly message.
The external declarations block – describing the functionality the external provides
The external glue code provided in libexternal provides a number of C/C++-macros that help in the construction of the external declarations block that tells
Revolution what the external exports to it.
A block starts with:
This is followed by a sequence of command or functions declarations:
Then the block finishes with:
In our case we wish to declare the existence of an external function called rnahellouser.
So, scroll to where the (empty) external declarations block is in the rnahello.cpp
file and insert the line:
Between the comments '// BEGIN USER DECLARATIONS' and '// END USER DECLARATIONS'. This will cause a call to a function rnahellouser in Revolution to be mapped to a call to the C++ function rnaHelloUser which we will implement next.
The function definition – implementing an external function
Now we have declared the existence of our external function, we now need to implement it. For any external handler (command or function), the C/C++ prototype is the same:
void handler(char *p_arguments, int p_argument_count,
char **r_result, Bool *r_pass, Bool *r_err)
p_arguments is an array of C-strings of length p_argument_count – each element of the array is an argument passed to the external handler.
r_result is a pointer to a C-string variable that should be set to a pointer to the result of the external handler. (The C-string returned in this way must be allocated using malloc/calloc/realloc and ownership of the memory will pass to Revolution.)
r_pass is a pointer to a Bool variable that should be set to True if the handler invocation should be passed in the message path.
r_err is a pointer to a Bool variable that should be set to True if the invocation of this handler should raise a runtime error in Revolution.
Add the following function to the rnahello.cpp file between the // BEGIN USER DEFINITIONS
and // END USER DEFINITIONS:
**r_result, Bool *r_pass, Bool *r_err)
(p_argument_count != 1)
*r_result = strdup("wrong number of parameters");
*r_err = True;
*r_pass = False;
t_buffer_length = strlen(p_arguments) + 8 + 1;
t_buffer = (char
(t_buffer == NULL)
*r_result = strdup("out of memory");
*r_err = True;
*r_pass = False;
sprintf(t_buffer, "Hello, %s!"
*r_result = t_buffer;
*r_err = False;
*r_pass = False;
In this function we have used a number of standard library functions which we will need to include in our environment. In C/C++ this is done via #include
directives, so add the following to the rnahello.cpp file before
the #include <revolution/external.h> directive.
This imports memory management (cstdlib), i/o (cstdio) and string manipulation (cstring) functions into our file.
All being well, you should now be able to compile your external and launch it within Revolution...
In Visual Studio:
Ensure rnahello is the Startup Project
Make sure Debug is chosen in the Solution Configurations drop-down list
(on the toolbar)
Click the green Run button
Note: Due to the relaunch feature added to recent versions of Revolution, you need
to make sure the version of Revolution you are debugging externals in is not already
running when you launch a debug session from Visual Studio
Note: You will likely get a message saying 'Unable to find debug symbols for Revolution.exe'
the first time you run your application - do not worry about this, just click to
continue and choose not to show the dialog again.
Choose Build mode (either by choosing Build Results from the Build
menu, or by clicking on the Build icon in the Page tab in the in the top-left).
Choose rnaHello from the Target drop-down
Choose Release from the Configuration drop-down
Now choose the Debug configuration and click Build and when it
has finished click Debug.
(The previous two steps shouldn't be necessary, but there seems to be a glitch in
XCode that prevents it picking up the correct settings for launching the debug executable
if you have only ever built a Debug variant of a project - from now on you won't
need to build a release variant before a debug one in this project).
Assuming the above steps were followed correctly, after a short while an empty stack called rnaHelloTest should pop-up inside the development environment.
Testing our function – hooking into our external
At this point you should be looking at the Revolution IDE with a stack opened up. This stack should have already loaded the external into the Revolution environment – so now we just need to hook into it.
So do the following:
answer rnaHelloUser(field "Name")
Now enter any string into the field and click the button – you should get an answer dialog popping up with a friendly message.
Before continuing, make sure you save your test stack (we will be building on it in the next section) and then quit Revolution.
Adding functionality – our first command
In the previous section, we looked at adding an external function. In this section we will look at adding an external command instead. Just with external functions, external commands are effectively the same as command handlers (defined with the on keyword) in Revolution except that their implementation resides in a external.
In reality, there is very little difference between external commands and functions (just like there is little difference between command and function handlers in Revolution). Indeed, the difference is mainly syntactic, and the fact that you have to use the
result to get the return value of a previously executed command. Therefore, we will use this opportunity to introduce how to set and get local variables in an external handler.
The command we will implement will be called rnaHelloUserIndirect and will have syntax:
rnaHelloUserIndirect pUserVariableName, pOutputVariableName
Here pUserVariableName will be the name of a (handler) local variable from which to fetch the user’s name, and pOutputVariableName
will be the name of a (handler) local variable in which to put the result.
The Revolution externals API – getting and setting string variables
In addition to providing the necessary ‘glue’ to allow a shared library to be loaded as a Revolution external, ‘libexternal’ also exports a number of functions that allow some access to the internals of the currently running Revolution application.
In this section will make use of two of these functions GetVariable and SetVariable. These functions give you access to the contents of the local variables present in the context that called the external handler – as long as the value of those variables are text strings (i.e. not binary data).
The prototypes of these functions are:
char *GetVariable(const char *p_variable_name, int *r_success)
void SetVariable(const char *p_variable, const char *p_value, int *r_success)
The GetVariable call takes a name of a variable (as a C-string) and a pointer to a return variable. If the call succeeds, *r_success will be EXTERNAL_SUCCESS
and a copy of the value of the variable as a C-string will be returned. If the call fails, r_success will contain EXTERNAL_FAILURE and
NULL will be returned.
NB: The ownership of the memory returned passes to you – i.e. you must free it when you are finished with it.
The SetVariable call takes a name of a variable, the value you wish to set the variable to and a return variable. If the call succeeds *r_success will contain EXTERNAL_SUCCESS and the specified variable will be set to the required value. If the call fails *r_success will contain EXTERNAL_FAILURE and no other change will have occured.
NB: Revolution copies the string you pass to it in p_value so there is no concern about ownership - i.e. if you've allocated memory to store it, you still
have to free it.
Implementing our new command
As before, we first have to declare the existence our new command by adding an appropriate entry in the external declarations block. Therefore, add the following line in the ‘USER DECLARATIONS’ as before:
Next we have to provide an implementation of this command which we do by adding the following to the 'USER HANDLER DEFINITIONS' section of the file:
*r_pass, Bool *r_err)
t_error = NULL;
(t_error == NULL && p_argument_count != 2)
t_error = "wrong number of parameters";
t_name = NULL;
(t_error == NULL)
t_name = GetVariable(p_arguments, &t_success);
(t_success == EXTERNAL_FAILURE)
t_error = "unable to get value of name variable";
t_message = NULL;
(t_error == NULL)
t_message = (char
*)malloc(strlen(t_name) + 8 + 1);
(t_message == NULL)
t_error = "out of memory";
(t_error == NULL)
sprintf(t_message, "Hello, %s!", t_name);
(t_error == NULL)
SetVariable(p_arguments, t_message, &t_success);
(t_success == EXTERNAL_FAILURE)
t_error = "unable to set output variable";
(t_message != NULL)
(t_name != NULL)
(t_error == NULL)
*r_result = strdup("");
*r_pass = False;
*r_err = False;
*r_result = strdup(t_error);
*r_pass = False;
*r_err = True;
Again, you should now be able to Run (Visual Studio), or Build and Debug
(XCode) your project and after a few moments, the test stack will appear again.
Testing our command
You should again find yourself staring at our test stack, loaded into the Revolution IDE. Add a new button to the stack called rnaHelloUserIndirect and place the following in its script:
local tName, tOutput
put field "Name" into tName
rnaHelloUserIndirect "tName", "tOutput"
Notice here that we are passing strings to rnaHelloUserIndirect that contain the names of the variables that we wish to access in the external, rather than their values.
Clicking on rnaHelloUserIndirect should now give you exactly the same message
as clicking rnaHelloUser - our new command works!
Integrating an external into the IDE
The External Creator helpfully builds us a stack that automatically loads our external when it is opened through either XCode or Visual C++. However, when you get to a point when you wish to start testing any externals you create in the context of actual Revolution projects, you will need to be able to integrate them with the IDE.
To do this, you will first want to build Release builds of your externals.
In Visual Studio, simply choose the Release configuration from the drop-down list on the toolbar, while in XCode you need to switch to the build pane and choose one of Release, Release x86-32 or Release PowerPC-32.
When you have done this, all you need to do then is copy them into appropraite places in your Documents folder for Revolution to pick up next time it is launched. This can be done by creating the following hierarchy inside the standard Documents folder:
My Revolution <edition>/
Mac OS X/
Then inside each Externals folder, put the appropriate build of the external along with a text file called Externals.txt. The externals.txt file is return-delimited list of pairs:
<external label>,<external library>
For example for rnahello on Windows the file should contains:
Whereas on Mac OS X it should contain:
Note: You only need the Runtime hierarchy for building Standalones containing your externals. Furthermore, you need only create the architecture folders within each platform folder for the architectures that you need.
Note: In general, you should always build a Release (Universal) external on Mac OS X to place in the non-Runtime Externals folder - as this is the folder
the IDE uses.
In this article we have covered the basics of writing externals – with particular emphasis on doing so cross-platform for both Mac OS X and Windows. Although we have only covered two API calls (GetVariable and SetVariable) there are numerous other ones – these are all documented in external.h present inside the
In the next part of the series on external writing we will explore using more of these APIs – in particular ones allowing you to access binary data and arrays from Revolution. We will then put this to good use in producing an external that does something native code is very good at – image processing...
Note: At the time of 'going to press' - the documentation to be
included in external.h had not been finalized. We will upload ExternalsEnvironmentV2.zip
in a few days with this documentation included.
Where can I get a copy of Visual C++ 2005?
Visual C++ 2005 is available in several editions as part of the Visual Studio 2005 software suite. Microsoft makes Visual C++ 2005 Express Edition available free of charge, and is available from:
Where can I get a copy of XCode 2.4.x?
The XCode tools are freely available from Apple’s developer site after registering for Apple’s Developer Connection (also free). The landing page is here:
Do I really have to wait for the IDE to load each time I want to test my external?
No. If you are on a slow machine, you may want to setup your test stack and build it as a standalone. You can then change the debugger parameters in either XCode or Visual Studio to run this executable rather than the IDE.
For Mac OS X:
Navigate to the Executables branch of the project tree and Get Infoon
the Test node.
Change Executable Path to reference your standalone application.
Go to the Arguments tab and remove all the arguments.
Right-click on the appropriate project in the Solution tree-view and select Properties.
Choose All Configurations from the drop-down list
Expand Configuration Properties and choose Debugging
Select Command, choose Browse and select your test standalone executable
Clear the command arguments and working directory fields
Can I reuse the source-code provided with this article in my own projects?
Yes. You are free to use all source-code and project files included with this article (or generated by External Creator V1) for the purposes of producing Revolution compatible externals.
libexternal seems to be different from the old XCmdGlue implementation I have...
Yes – this is correct. libexternal is an updated version of the glue-code which is cleaner. It uses exactly the same Revolution interface as the previous implementation and so will continue to produce externals that will be usable in any Revolution version.
What’s the difference between C++ (no exceptions, no rtti) and C++?
Many people use C++ as ‘just a better C’ - and don’t take advantage of exception handling or runtime-type information. Used in this form, there is no need for the external glue-code to do anything special. However, if you wish to use C++ exceptions, it is important the glue code automatically handles any before control is returned to the engine and this option does this automatically for you.
Can I use the standard C++ library?
Yes – but make sure you choose C++ when creating the skeleton for your external. Most recent implementations of the standard C++ library require exception handling (Visual Studio for example) and are not guaranteed to work correctly if it is disabled. Creating your external with initial support for C++ will ensure that unhandled exceptions in your external code are caught before returning to Revolution.
Can I write externals in other languages?
In theory any language that can build shared libraries and allows you to link with the provided glue code could be used. However, in practice, some languages will require extra glue-code in order to provide an environment in which their code can run. For example, to access Objective-C you will need to ensure you set up its memory/exception environment in C wrapper functions before calling any Objective-C code.
Can I build externals that run on Windows 98SE/ME?
Yes. Any external that does not depend on any OS APIs should run fine on the Windows 98/ME family of OSes. If you do you use any OS APIs you just need to make sure they are available on that platform by checking in the Platform SDK documentation (since you can’t explicity target a given Windows version, you won’t find out at compile-time if you’ve accidentally used an API that isn’t available).
Can I build externals with other versions of Visual C++?
Yes – but the External Creator only generates project and solution files compatible with the Visual Studio 2005 family.
Can I build externals with other Windows C/C++ development environments?
Yes. Externals are just DLLs which export a specific function and so any compiler should be persuadable to produce externals compatible with Revolution. Of course, you will have to set up your own environment in this case.
Note: the only requirement that Revolution puts on a DLL it trys to load as an external is that it exports one symbol whose resulting name is _getXtable. Some compilers don’t automatically prepend an ‘_’ but with appropriate options can be made to do so. (getXtable is defined in libexternal).
Can I build externals with other versions of XCode?
In theory, yes. In practice, no. With the advent of Universal Binary you really need to be producing externals that compile to both PowerPC and Intel – this requires a minimum of XCode 2.2 (if I recall correctly). Also, if you wish to support the same range of OS versions that Revolution supports you need to build them in a specific way:
We use XCode 2.4.1 internally and know these configurations work without problems and so we recommend that you do the same.
Of course, if you are targetting a specific OS version or architecture, then you can choose your version of XCode appropriately, but you may have to setup your environment from scratch.
Can I build externals with other Mac OS X C/C++ development environments?
This depends on the OS versions and architectures that you are targetting. Any environment that is based on the Apple GNU toolchains should be able to produce compatible shared libraries. However, if they are not based on these toolchains, your mileage will vary.
In all these cases, though, you will have to configure your build environment yourself from scratch.
Can I use the same environment folder to build on both Windows and Mac OS X?
Yes. As long as you checked both platform boxes in the ‘External Creator’ when you initially setup your external a project will have been created for both platforms. To save constantly moving the folder between the two platforms you could (for example) put the environment on a network share. However, I recommend that you look into setting up a version control system such as Subversion to manage your external projects – this isn’t at all hard to do and numerous tutorials and GUIs exist on the web to help.
I’ve setup a version control system, what files should I include?
As a general rule, you should only include ‘source’ files in the version control system - these are the files that cannot be derived from other files. So, in our case you will need:
The top-level folder
Each project folder
The configurations folder and its contents
The externals.sln file (if you are targetting Windows)
The src folders and their contents within each project folder
The vcproj file within each project folder
The vcproj.user file within each project folder
The xcodeproj folder and the project.pbxproj file within it (Mac OS X build only)
The plist file within each project folder (Mac OS X build only)
The rev test stack within each project folder
Any other files that you have explicitly added
You should not add the _build or _cache folders, nor any ‘hidden’ files or .ncb files on Windows. Furthermore, all the files within the xcodeproj folder apart from project.pbxproj should be ignored.
The general principle is this – if a file or folder was not created by you, or the External Creator it shouldn’t go under version control!
What about UNIX, Linux and Classic?
To begin with, we decided to focus on the two main desktop platforms. However, we hope to look at the other platforms in future articles.
Revision 1 - 2006/11/10