Add Embedded Scripting to Your C++ Application

Scripting tends to come on late in the life of your application. About the time that the feature-set is approaching completeness and customers are starting to get happy, you get hit with a new requirement. Take the scenario when the boss marches in and tells you how many more copies the company could sell if only it could allow users to automate report writing themselves. Or perhaps after rewriting or tweaking reports for the fourth time this week, you have the epiphany yourself. Other times, you find that ordinary macro recording and playback tools fail to meet the needs for rigorous quality assurance testing. What you really want is a test-rig that can run your algorithm 100 or 1,000 times with minor variations to really give it a good shakedown. If you’re a game developer, you may want to expose part of your engine to allow third-party developers or your own in-house crew to design new AI behaviors for the zombies. You get the idea: Nearly every application can benefit from a scriptable interface.

The C Scripting Language (CSL)

The C Scripting Language (CSL) is a well-structured and easy-to-learn script programming language available for Windows 95 thru Windows XP, OS/2, Linux, FreeBSD, and other variants of UNIX. CSL follows the C syntax style very closely so programmers accustomed to C, C++, or Java will be immediately comfortable. CSL can be used as a standalone interpreter or is embeddable into your C/C++ application.

As an interpreter, you write the program with your favorite editor and run it directly like any shell script or run it through your Web server’s CGI-BIN interface. The CSL C interface can be embedded with most C/C++ compilers; the C++ class library is available for use with Visual C++ 5.0 or later, IBM VisualAge, Borland C++ 5.x, and GCC.

Standalone or embedded, CSL scripts have access to built-in libraries for strings, math, file I/O, asynchronous (serial) communications, regular expressions, registry and profiles, window management, and database. The database library (called “dax“) enables high-performance tasks such as data import/export, schema setup scripts, and SQL via ORACLE, DB2, MySQL, and ODBC.

CSL in Standalone Mode and Basic Syntax

Investigating standalone mode is perhaps the quickest way to get your feet wet and convince yourself that the CSL language is both familiar and friendly. Take the following short script as your first exploration into CSL. It computes the average of several numbers passed on the command line:

#loadLibrary 'ZcSysLib'

main()
{
   var sum, count=0;
   for (var i=2; i<sizeof(mainArgVals); i++) {
      sum = sum + mainArgVals[i];
      count++;
      }
   sysLog(sum / count);
}

As you can see, it looks fairly predictable because the CSL language is very close to C. However, it presents the following major differences:

  • All variables are of type var, which can hold numbers or strings.
  • No goto’s.
  • Exception handling by try/catch/throw, fully interoperable with C++. (Throw an exception in C++ and catch it in CSL or vice versa.)
  • Dynamic arrays are managed with a resize statement rather than realloc().

If you want to dig deeper into the language syntax, the CSL language reference is the best place to go.

Running a CSL Script Inside Your App

Take a look at the minimal amount of code needed for your application to embed CSL. By embed, I of course mean loading and executing arbitrary CSL scripts at runtime. You’ll see more advanced usage later in this article, but you have to crawl before you can fly. Whether the script was explicitly loaded by the user choosing File | Open Script in your app, pulled from a database, or dynamically generated is irrelevant to this part of the exercise. The following example uses a static in-memory script just to make it crystal clear how the mechanism works:

 1 #include <stdlib.h>
 2 #include <stdio.h>
 3 #include <ZCslApi.h>
 4
 5 static char* module = "Embed";    /* module name */
 6 static ZCslHandle csl;            /* csl handle */
 7 static long errs;                 /* csl api return code */
 8
 9
10 int main()
11 {
12    char buf[1024];
13    long bufsize=sizeof buf;
14
15    errs = ZCslOpen(&csl,0);
16    ZCslGet(csl, "cslVersion", buf, &bufsize);
17    printf("Using csl version %sn", buf);
18
19    printf("compile a script from memoryn");
20    errs = ZCslLoadScriptMem(
21       csl,                               /* handle */
22       module,                            /* module name */
23       "#loadLibrary 'ZcSysLib'n"        /* the script */
24       ""
25       "foo()n"
26       "{n"
27       "   sysLog('current time is '+sysTime()); n"
28       "   sysSleep(3000);  n"
29       "   sysLog('current dir is '+sysDirectory()); n"
30       "}n"
31    );
32
33    printf("call 'foo' within compiled scriptn");
34    errs = ZCslCall(csl, module, "foo", 0, 0);
35
36    printf("closing csln");
37    errs = ZCslClose(csl);
38
39    return 0;
40 } /* main */

Line #15 opens a handle to the CSL system, which you later close in line #37. This handle is the basis for all your CSL operations. The meat of this example is lines #21-31, which load a particular script into a module called “Embed”. This script contains a single function called foo() that executes three “sys” functions and then exits. Although not terribly useful, the example gets the point across. The output appears below:

D:cslVV>testcsl
Using csl version 4.04
compile a script from memory
call 'foo' within compiled script
current time is 15:31:46
current dir is D:cslVV
closing csl

Calling a C Function from Your App Inside CSL

I know what you’re thinking: I haven’t seen anything yet that I couldn’t do with a system() call to a Perl script or something. This time around, the example exposes a C function from within your application, which is called from CSL. The C function is foo_factorial(), which computes N factorial as expected and is called csl_factorial() inside the CSL script (just to prove that it can be an independent namespace if needed):

  1 #include <stdlib.h>
  2 #include <stdio.h>
  3 #include <ZCslApi.h>
  4
  5 static char* module = "Embed";    /* module name */
  6 static ZCslHandle csl;            /* csl handle */
  7 static long errs;                 /* csl api return code */
  8
  9
 10 ZExportAPI(void) foo_factorial(ZCslHandle aCsl)
 11 {
 12    int ii, factorial;
 13    double sum;
 14    char buf[40], name[4];
 15    long bufsiz = sizeof buf;
 16
 17    bufsiz = sizeof(buf);
 18    if ( ZCslGet(aCsl, "p1", buf, &bufsiz) )
 19       return;
 20    if (!atoi(buf)) {
 21       ZCslSetError(aCsl, "foo_factorial: Not a number", -1);
 22       }
 23
 24    factorial = 1;
 25    for (ii=1; ii < atoi(buf); ii++)
 26      factorial *= ii;
 27
 28    sprintf(buf, "%d", factorial);
 29    ZCslSetResult(aCsl, buf, -1); /* (2) */
 30 }
 31
 32
 33 int main()
 34 {
 35    char buf[1024];
 36    long bufsize=sizeof buf;
 37
 38    errs = ZCslOpen(&csl,0);
 39    ZCslGet(csl, "cslVersion", buf, &bufsize);
 40    printf("Using csl version %sn", buf);
 41
 42    errs = ZCslAddFunc(csl, module, "csl_factorial(const p1)",
                          foo_factorial);
 43
 44    printf("compile a script from memoryn");
 45    errs = ZCslLoadScriptMem(csl,     /* handle */
 46       module,                        /* module name */
 47       "#loadLibrary 'ZcSysLib'n"    /* the script */
 48       ""
 49       "foo()n"
 50       "{n"
 51       "   sysLog('current time is '+sysTime()); n"
 52       "   var x;n"
 53       "   x = csl_factorial(5);  n"
 54       "   sysLog('5! is '+ x); n"
 55       "}n"
 56    );
 57
 58    printf("call 'foo' within compiled scriptn");
 59    errs = ZCslCall(csl, module, "foo", 0, 0);
 60
 61    errs = ZCslClose(csl);
 62    return 0;
 63 } /* main */

First, look at the new CSL-callable function in lines #10-30. Every CSL-callable function will have this same signature: ZExportAPI(void) and one argument of type ZcslHandle(). Once inside the function, you can retrieve its actual arguments by name with ZcslGet() on line #18. You do your function execution stuff, which in this case computes the factorial, and then return the value to CSL with ZCslSetResult() in line #29.

Back in the main() function, the new thing to notice is your dynamic installation of the CSL-callable function via ZCslAddFunc() on line #42. Again, you referred to the formal parameter “p1” by name back on line #18. You actually call csl_factorial() right in the middle of your in-memory script, as shown on line #53. Again, this need not have been an in-memory script; it could have been typed into an edit control in your application, loaded from a file, and so forth. When you run the program, you’ll see the output of 5 factorial (!):

Using csl version 4.04
compile a script from memory
call 'foo' within compiled script
current time is 16:29:08
5! is 24

You now also call this function from C using a related API:

char *args[] = {"5"};
ZcslCall(csl, module, "csl_factorial", 1, args);

Other Embedded Interpreters to Consider

It’s possible that this approach is backwards from what you want. If you want to turn your application inside out and make all of its guts callable from other scripting languages, such as Perl, Python, and Tcl/TK, what you want is SWIG (Software Interface Generator).

Ch is an embeddable C/C++ interpreter for cross-platform scripting, shell programming, 2D/3D plotting, numerical computing, and embedded scripting. Ch works on Windows, Solaris, HP-UX, Linux (X86 and PPC), FreeBSD, and QNX. There’s even a free add-in for scripting from Microsoft Excel.

S-Lang library by John E. Davis is another C-like environment with facilities required by interactive applications such as display/screen management, keyboard input, keymaps, and so on. The most exciting feature of the library is the slang interpreter that you can easily embed into a program to make it extensible.

EiC is a freely available C language interpreter in both source and binary form. EiC allows you to write C programs, and then “execute” them as if they were a script (like a Perl script or a shell script). EiC can be run in several different modes: (1) interactively, (2) non-interactively, (3) in scripting mode, and (4) embedded as an interpreter in other systems.

Everyone Could Use Scripting

CSL provides all the required functionality for plugging a scripting language into your app with minimal hassle. You can export functions from your application with relative ease to make them callable within CSL scripts. The CSL language will be instantly usable by any end-user with C or Java experience. It also contains a rich set of libraries—including database access—that this article hasn’t even described.

Once you try embedded scripting in your app, you and your end-users (or zombies) will wonder how you ever got along without it.

About the Author

Victor Volkman has been writing for C/C++ Users Journal and other programming journals since the late 1980s. He is a graduate of Michigan Tech and a faculty advisor board member for Washtenaw Community College CIS department. Volkman is the editor of numerous books, including C/C++ Treasure Chest and is the owner of Loving Healing Press. He can help you in your quest for open source tools and libraries, just drop an e-mail to sysop@HAL9K.com.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read