|
Wrapper
Functions
By
Rich Robinson, Nombas, Inc.
Once
you begin to understand what is going on, ScriptEase
is pretty easy to work with. However, before everything
'clicks', it can seem confusing and daunting. One of
the most basic concepts of ScriptEase is the wrapper
function. This document will explain this concept, and
move you closer to that 'click.'
The
first step is to understand integrating the ISDK with
your application. This is fully described in the manual.
Once you have done so, you can run scripts. The API
function 'jseInterpret()' is most often used to do so.
This part is easy to understand.
However,
at that point, those scripts are generic JavaScript
and they cannot communicate with your application. You
can compute, for instance, the value of '10+3', but
you can't do anything with it.
You
need to write wrapper functions to do this communication.
What are wrapper functions and why are they needed?
Wrapper functions are the pieces of code where JavaScript
and your application communicate. Let's start with a
blank-slate application and add some wrapper functions
to make it do something. Work through this example,
and hopefully by the end everything will 'click' for
you. This example is for the ScriptEase:ISDK for C version.
First,
here is a sample application. Its only purpose is to
interpret a single script and exit. Of course, your
application will be much more complex. It will call
scripts as one of the many actions it needs to do and
will do it probably more than once. That is fine.
To
compile this, you will need to make a project for your
compiler that includes the file 'samp.c' (found below).
In addition, add the files 'srcmisc/globldat.c', 'srcmisc/utilstr.c',
'srcmisc/dirparts.c', and 'srcmisc/jsemem.c'. These
last files are part of the ScriptEase distribution.
Also found below is the file 'jseopt.h'. Put that in
the same directory as the 'samp.c' file.
You
will need to add the following ScriptEase directories
(as well as the directory containing your source files)
to the include path for this application:
incjse
srclib srcmisc srcapp
You
will need to link with static library version of the
ScriptEase runtime engine (named serte40.lib). There
are other options available, including the ability to
link with a .dll version, but for this simple example
we will use the static version. Below is the text of
the two basic files, 'jseopt.h' and 'samp.c'. Comments
and extraneous text are not included - the file you
are reading is the explanation you need!
Note
that in jseopt.h, we have defined '__JSE_CON32__'. That
is because I am building Windows console apps to test
my examples. However, ScriptEase is portable. Chapter
2 of the manual in the section on JSEOPT.H has a table
listing the different defines necessary for different
systems. Replace this define if necessary with the one
in the table for your system.
The
only other caveat is that for simplicity, we are using
printf. This is not available on all systems. You will
need to modify it for your system if you cannot printf.
/* file: jseopt.h */
#ifndef _JSEOPT_H
#define _JSEOPT_H
#define JSETOOLKIT_APP
#define __JSE_LIB__
#define __JSE_CON32__ // see comments above
#define NDEBUG
#include "seall.h"
#endif
----------------------------------------------------------------------
/* file: samp.c */
#include "jseopt.h"
void JSE_CFUNC FAR_CALL myerrfunc(jseContext jsecontext,
const jsechar _FAR_ *ErrorString)
{
UNUSED_PARAMETER(jsecontext);
printf("ScriptEase error: %s\n", ErrorString);
}
#define text UNISTR("var a = 10 * 5;\n")
int main(int argc,char *argv[])
{
jseContext jsecontext;
struct jseExternalLinkParameters LinkParms;
long ver;
printf("samp.c version 1.\n");
#if defined(JSE_MEM_DEBUG) && (0!=JSE_MEM_DEBUG)
jseInitializeMallocDebugging();
#endif
ver = jseInitializeEngine();
if( JSE_ENGINE_VERSION_ID != ver )
{
printf("Failed to initialize interpreter engine.\n");
exit(0);
}
memset(&LinkParms,0,sizeof(struct jseExternalLinkParameters));
LinkParms.PrintErrorFunc = myerrfunc;
/* Insert here the key your received by email */
jsecontext = jseInitializeExternalLink(NULL,&LinkParms,"", MY_JSE_USER_KEY);
if( jsecontext==NULL )
{
printf("Error initializing context.\n");
exit(0);
}
jseInterpret(jsecontext,NULL,text,NULL,jseNewNone,0,NULL,NULL);
jseTerminateExternalLink(jsecontext);
jseTerminateEngine();
#if defined(JSE_MEM_DEBUG) && (0!=JSE_MEM_DEBUG)
jseTerminateMallocDebugging();
#endif
return 0;
}
----------------------------------------------------------------------
Compile and link this sample. You will have to replace 'MY_JSE_USER_KEY' with
your actual key, in quotes. You can now interpret a script using the
ScriptEase engine. If you run this, it will interpret the simple script 'var a
= 10 * 5;'. Unfortunately, that script has no way to communicate with your
application. It cannot access your application's data, tell your application
to perform tasks, or do any other real work. This is why you need wrapper
functions. Let's add a single wrapper function. This wrapper function will
simply print out whatever parameter is passed to it as a string. Now we can at
least see that the script is working.
Replace the text in 'samp.c' above, by changing the line:
#define text UNISTR("var a = 10 * 5;\n")
to
#define text UNISTR("var a = 10 * 5; foo(a);\n")
Recompile and run it. You'll get an error! That's because the interpreter does
not know about the function "foo". We need to associate "foo" with a wrapper
function and write that function. Wrapper functions always have the same
format. The only difference is their names and the body of their
function. Obviously, the name is up to you. The body of the wrapper function
can be conceptually broken into three parts, some or all of which can be
ignored if your function doesn't have a need to do that.
1. A wrapper function examines the arguments passed to it. These arguments are
JavaScript arguments of the type 'jseVariable'. The ScriptEase API provides
many functions to work with these arguments. You can find out how many your
function received, what type they are, translate their values to the
equivelent 'C' value so your program can work with them, and so forth.
2. The wrapper function does some work with your application. For instance, a
wrapper function designed to make an audible beep will call whatever
application function you have that will make a beep.
3. The wrapper function returns some value. Of course, whatever value you have
to return will be a C value. You will use the ScriptEase API to translate
that into a JavaScript value which can then be used by the ScriptEase
engine and return that value.
For our example, "foo" needs to examine its single argument and print it out
as a string. Let's write a wrapper function to do this.
static jseLibFunc(FooWrapperFunction)
{
/* get first argument, remember it is numbered from 0 up */
jseVariable arg1 = jseFuncVar(jsecontext,0);
const jsechar *string_val;
/* We want to see the variable as a string, so make a 'string' version of
* it.
*/
jseVariable arg_as_string =
jseCreateConvertedVariable(jsecontext,arg1,jseToString);
/* Get that string value. */
string_val = jseGetString(jsecontext,arg_as_string,NULL);
/* print it to the screen */
printf("Value passed to foo wrapper function: %s\n",string_val);
/* ScriptEase API rule: if a function has the text 'Create' in it, the
* variable returned must later be destroyed!
*/
jseDestroyVariable(jsecontext,arg_as_string);
}
That's the wrapper function. Notice that this function does not use the
ScriptEase API function 'jseReturnVar' to return a value. This is analogous to
a JavaScript function that doesn't have a 'return' statement. Also, there are
different ways to accomplish the same task. I choose the above way to clearly
indicate the steps we are doing. A number of common tasks are simplified in
the ScriptEase API by functions designed to do those tasks.
Now that we've created the wrapper function, we have one final step. We must
tell the ScriptEase interpreter about it. First thing we do is create a table
describing the functions we wish to add. In this case, there is just one
function in the table:
static CONST_DATA(struct jseFunctionDescription) MyFunctionList[] =
{
JSE_LIBMETHOD( "foo", FooWrapperFunction, 1, 1, 0, 0 ),
JSE_FUNC_END
};
The first parameter gives the name that the JavaScript can refer to our
function as, the second is the function that will do the work. The third and
fourth parameters are the minimum and maximum arguments the function can
take. For our example, we say it must always have 1 argument. The last two are
flags about the function, which we can set to 0 since we have no special
requirements.
Finally, we need to register this table with the interpreter. We do this after
we create the context using jseInitializeExternalLink() and before we try to
interpret any scripts. This call does the trick:
jseAddLibrary( jsecontext, NULL, MyFunctionList, NULL, NULL, NULL );
Again there is added behavior this function can do for us, but we don't need
it so many of the parameters are NULL. Refer to the ScriptEase API reference
as well as Chapter 2 of the manual for more information.
We add all of this into our samp.c file, here is the complete new version:
----------------------------------------------------------------------
/* samp.c with a wrapper function */
#include "jseopt.h"
void JSE_CFUNC FAR_CALL myerrfunc(jseContext jsecontext,
const jsechar _FAR_ *ErrorString)
{
UNUSED_PARAMETER(jsecontext);
printf("ScriptEase error: %s\n", ErrorString);
}
static jseLibFunc(FooWrapperFunction)
{
/* get first argument, remember it is numbered from 0 up */
jseVariable arg1 = jseFuncVar(jsecontext,0);
const jsechar *string_val;
/* We want to see the variable as a string,
* so make a 'string' version of it.
*/
jseVariable arg_as_string =
jseCreateConvertedVariable(jsecontext,arg1,jseToString);
/* Get that string value. */
string_val = jseGetString(jsecontext,arg_as_string,NULL);
/* print it to the screen */
printf("Value passed to foo wrapper function: %s\n",string_val);
/* ScriptEase API rule: if a function has the text 'Create' in it,
* the variable returned must later be destroyed!
*/
jseDestroyVariable(jsecontext,arg_as_string);
}
static CONST_DATA(struct jseFunctionDescription) MyFunctionList[] =
{
JSE_LIBMETHOD( "foo", FooWrapperFunction, 1, 1, 0, 0 ),
JSE_FUNC_END
};
#define text UNISTR("var a = 10 * 5; foo(a);\n")
int main(int argc,char *argv[])
{
jseContext jsecontext;
struct jseExternalLinkParameters LinkParms;
long ver;
printf("samp.c version 2.\n");
#if defined(JSE_MEM_DEBUG) && (0!=JSE_MEM_DEBUG)
jseInitializeMallocDebugging();
#endif
ver = jseInitializeEngine();
if( JSE_ENGINE_VERSION_ID != ver )
{
printf("Failed to initialize interpreter engine.\n");
exit(0);
}
memset(&LinkParms,0,sizeof(struct jseExternalLinkParameters));
LinkParms.PrintErrorFunc = myerrfunc;
/* Insert here the key your received by email */
jsecontext = jseInitializeExternalLink(NULL,&LinkParms,"", MY_JSE_USER_KEY);
if( jsecontext==NULL )
{
printf("Error initializing context.\n");
exit(0);
}
jseAddLibrary( jsecontext, NULL, MyFunctionList, NULL, NULL, NULL );
jseInterpret(jsecontext,NULL,text,NULL,jseNewNone,0,NULL,NULL);
jseTerminateExternalLink(jsecontext);
jseTerminateEngine();
#if defined(JSE_MEM_DEBUG) && (0!=JSE_MEM_DEBUG)
jseTerminateMallocDebugging();
#endif
return 0;
}
----------------------------------------------------------------------
Recompile, link, and run it. Now, you can see that your wrapper function is
called. The script is communicating with your application, albeit in a very
simply way.
Wrapper functions can be used to do much more complex things. The example
above shows a clearly procedural approach. You can also use the ScriptEase API
to build classes of objects with member functions that are handled by wrapper
functions as well. Chapter 5 of the manual, under the section on Objects,
describes how you go about building object classes. Just remember that you can
use a wrapper function wherever you might have a JavaScript function. So the
object methods described in that section written in JavaScript can be just as
easily written in C as wrapper functions. You will use the ScriptEase API
function 'jseGetCurrentThisVariable()' to know which object this method should
be acting upon for this call to it.
SOME HELPFUL HINTS
Hopefully now you have a good idea of the basics of wrapper functions: why
they are needed and what they do. What follows are some helpful pointers on
common techniques for using wrapper functions.
LOOK AT THE SAMPLES
The Samples directory of the ScriptEase: ISDK has a number of simple samples
that you should look at. The directory 'srclib/ecma' contains the source code
for the ScriptEase implementation of the standard ECMAScript objects (Number,
String, Math, etc.) You can see that they are implemented using the ScriptEase
API and wrapper functions. Although more complex than the simple samples, you
can learn a lot from them.
RETURNING C POINTERS
In many instances you will want to return a value from a wrapper function
simply so you'll have it available to be passed as a parameter to a different
function. For instance, suppose you want to have a function
'lookupMyThingByName' which takes an ASCII string and uses it to find some
pointer. You then want to take that as a parameter to functions that act on
that object. You don't need the JavaScript variable itself to be
understandable by the script; the only valid thing the script can do with it
is pass it as a parameter to one of these other functions that can understand
it. Your task is simply to pass the value along.
One easy way to do this is to create a JavaScript string to return the value,
as in:
----------------------------------------------------------------------
jseVariable retvar = jseCreateVariable(jsecontext,jseTypeString);
char buffer[100];
sprintf(buffer,"MyThingee%x",);
jsePutString(jsecontext,retvar,buffer);
jseReturnVar(jsecontext,retvar,jseRetTempVar);
/* NOTE: jseReturnVar() with the jseRetTempVar will implicitly destroy
* the variable. Otherwise we would have to explicitly destroy it
* ourself since it was returned via an API call with the text
* 'Create' as part of it.
*/
----------------------------------------------------------------------
Wrapping it up into a string is slower than casting it to a double and
returning it via jseReturnNumber(), true, but it allows you to ensure that the
value is correct in functions that later try to use it. It's easy to pass the
wrong number, but someone would have to purposely try to trick you using this
method. Such a function could extract the value using this snippet:
----------------------------------------------------------------------
/* assume the 1st parameter is the object to be acted upon */
jseVariable object = jseFuncVar(jsecontext,0);
MyPointerType *pointer;
if( jseGetType(jsecontext,object)!=jseTypeString ||
sscanf(jseGetString(jsecontext,object,NULL),"MyThingee%x",&pointer)!=1 )
{
jseLibErrorPrintf(jsecontext,"That is not a valid thingee!\n");
}
else
{
/* pointer is now valid, we can use it to do some real work */
}
----------------------------------------------------------------------
You can do the same kind of thing in an object-oriented way. Instead of
returning a string, we can make the function act like a constructor. You
create an object, and put the string as one of its members. The object has a
number of member functions. Each function will get its 'this' variable using
'jseGetCurrentThisVariable'. Each function will ensure the storage member
exists, and extract the pointer from that string similarly to above. The two
code snippets above are modified to use this method below. First, the
constructor would look something like this:
----------------------------------------------------------------------
jseVariable retvar = jseCreateVariable(jsecontext,jseTypeObject);
jseVariable storevar = jseMember(jsecontext,retvar,"my_storage_member",
jseTypeString);
char buffer[100];
sprintf(buffer,"MyThingee%x",);
jsePutString(jsecontext,storevar,buffer);
jseAssign(jsecontext,jseMember(jsecontext,retvar,"_prototype"),
);
jseReturnVar(jsecontext,retvar,jseRetTempVar);
----------------------------------------------------------------------
and each member function would start off with:
----------------------------------------------------------------------
jseVariable wrapper_object = jseGetCurrentThisVariable(jsecontext);
jseVariable object = jseGetMember(jsecontext,wrapper_object, "my_storage_member");
MyPointerType *pointer;
if( object==NULL ||
jseGetType(jsecontext,object)!=jseTypeString ||
sscanf(jseGetString(jsecontext,object,NULL),"MyThingee%x", &pointer)!=1 )
{
jseLibErrorPrintf(jsecontext,"'this' is not really my type!\n");
}
else
{
/* pointer is now valid, we can use it to do some real work */
}
|