Embedded scripting is increasingly gaining popularity in design of software systems and applications. In the programming paradigm of embedded scripting, an interpreter or scripting engine is embedded into a binary application program. The application can execute script code through the embedded interpreter. The script code can also invoke binary code. Although a powerful embeddable interpreter can speed-up software development and deployment significantly, the power and capabilities of this programming paradigm are yet to be explored and fully utilized.
For example, if a C/C++ interpreter is embedded in an automated program for testing hardware and software, quality assurance engineers are able to access binary C functions and C++ member functions from C/C++ testing scripts. A binary application program can be used to test different products by just invoking different scripts that can be entered from the GUI or loaded from files. As another example, an embedded interpreter can be used to customize a product for different customers and applications, and extra functionality can be added to applications specific to a particular customer's needs without changing the standard product. By using scripts, executed at the defined points from the application, customer-specific behaviors can be implemented.
Many embeddable interpreters are available for embedded scripting Tcl/TK, Python, Ch, and Lua, among others. However, it's most desirable if the same language is used for both native binary code and scripting code. Otherwise, there is overhead and complexity in sharing the data between binary space and script space.
C and C++ are most commonly used to develop binary applications. For application programs written in C/C++, embedding a C/C++ interpreter is the most logical choice. The memory, functions, and header files can be seamlessly shared by both application program written in C/C++ and C/C++ scripts.
Listing One shows how a C/C++ interpreter can be easily embedded in an application program as a scripting engine. In this example, Ch (an embeddable C/C++ interpreter from SoftIntegraton, www.softintegration.com) is used. The program includes the header file embedch.h, which defines some macros, data types, and function prototypes for embedded Ch. The program first initializes an embedded Ch interpreter by the API:
Ch_Initialize(&interp, NULL);
The function func() is loaded in the interpreter by:
Ch_AppendRunScript(interp, code);
This script function is invoked by:
Ch_CallFuncByName(interp,"func", &retval, x, a);
The variable retval contains the returned value. The variable x is passed to function func(). The memory for array a is shared in both binary and script space. Finally, the interpreter is closed by function call:
Ch_End(interp);
The output from execution of the compiled and linked binary program embedch.exe is as follows:
x = 10.000000 a[1] in func=2 a[1] in main=20 retval = 30
When an interpreter is embedded into an application, it becomes a part of the application. If the interpreter has a memory leak for running each script code, this memory leak is reflected in the application. Therefore, the interpreter must be reliable and robust. When an error occurs, the interpreter should not crash and all the dynamically allocated memory for the offending code should be released. Thus, it is more challenging in error handling in an interpreter than in a compiler. A robust interpreter should recover from errors smoothly.
/* File: embedch.c */ #include <stdio.h> #include <embedch.h> char *code = "\ int func(double x, int *a) { \ printf(\"x = %f\\n\", x); \ printf(\"a[1] in func=%d\\n\", a[1]);\ a[1] = 20; \ return 30; \ }"; int main () { ChInterp_t interp; double x = 10; int a[] = {1, 2, 3, 4, 5}, retval; Ch_Initialize(&interp, NULL); Ch_AppendRunScript(interp,code); Ch_CallFuncByName(interp, "func", &retval, x, a); printf("a[1] in main=%d\n", a[1]); printf("retval = %d\n", retval); Ch_End(interp); } }