Java脚本是一门解释性的语言,主要用于编写操作html页面对象的客户端脚本,执行数据的初步校验等功能。当然,Microsoft 的ASP技术还允许使用Java脚本来编写服务器端的脚本。本文所探讨的
“使用Java脚本驱动主应用程序对象”的意义在于:用户自行定制应用程序,通过Java脚本,用户可以使用熟悉的语言,编写脚本,操作主程序暴露给Java 脚本引擎的对象树,执行例行性的功能。这样,就使得应用程序具有很大的灵活性,用户可以通过脚本的编写,在主程序和Java 脚本引擎提供的基本函数的基础上,根据实际应用的需要,组织更为高层的功能,适应用户的行业需求。而这一点,对于软件商来讲,也具有深远的意义,也就是说,软件开发商在开发通用软件产品的时候,它不可能对各行各业的需求都有深入细致的了解,但是由于可以通过Java脚本这种扩展机制,使得软件具有了更大的通用性。
要实现用Java 脚本驱动主程序的功能, 必须实现如下的三个方面:
Java 脚本引擎的建立. Mozilla 实现了一个跨平台的开放源码Java 脚本引擎,我们在实现报表系统软件的数据审核和计算脚本引擎时,使用了该开放源码。
通过适当的接口,以对象树的方式把主程序的功能暴露给Java 脚本引擎。
编写主程序和Java 脚本引擎的数据交换辅助类。实现宿主语言的变量、对象和Java 脚本引擎变量和对象的变换。
主程序实现策略
为了讨论的完整性,下面使用一个简单的例子来进行讨论。主程序使用C++来编写,在文档对象(CDocument)内部维护一个点(CPoint)对象列表,通过Java 脚本脚本可以操作文档对象(CDocument),点列表对象里的各个点对象(Cpoint)。
CDocument类继承于Idocument接口和IpointCollection接口,Idocument接口是操作CDocument文档类的主要接口,而IpointCollection接口是对文档对象所管理的点(CPoint)对象列表操作的接口,这两个类的定义简单列表如下:
class IDocument
{ public:
virtual std::string getName()=0; //获取文档的名称
virtual void setName(std::string tempName)=0; //设置文档的名称
………
};
class IPointCollection
{ public:
virtual IPointCollection* Points()=0; //或者点列表对象指针
virtual IPoint* firstPoint()=0; //获取第1个点对象指针
virtual IPoint* nextPoint()=0; //获取下一个点对象指针
………
};
Ipoint接口是Cpoint类的接口定义,通过它,Java 脚本引擎可以操作每个点(CPoint)对象。其定义如下:
class IPoint
{public:
virtual IDoc* getParent()=0; //获取父对象(点列表对象)
virtual int getX()=0; //获取X坐标
virtual int setX(int tempX)=0; //设置X坐标
virtual int getY()=0; //获取Y坐标
virtual int setY(int tempY)=0; //设置Y坐标
………
};
为了演示主程序和Java 脚本引擎的关系,CDocument类和Cpoint类的实现比较简单,在本文就不列出来了。
数据交换实现策略
要实现主程序和Java 脚本引擎的数据交换,核心的思想,是在引擎启动的时候,把主程序的根对象在Java 脚本引擎的全局对象表里面进行注册,而对于Java 脚本引擎可以操作的主程序对象,都需要在主程序指针和Java 脚本对象之间进行变换。在本实例里,由于我们希望Java脚本可以操作CDocument对象,CDocument包含的点列表point list 对象和每个Cpoint对象,所以对于这三个对象类,都需要编写一个可以供Java 脚本引擎操作的辅助类。现在以Cpoint类为例,详细叙述如何实现该辅助类。
我们可以使用Java 脚本对主程序对象树中的某个对象的属性进行提取和设置,以及对其方法进行调用。对属性的存取和对方法的调用在实现上很类似。
在本实例中,我们希望脚本可以提取和设置每个Cpoint对象的X 和Y(坐标)属性。所以我们定义了如下的类,并且实现如下:
class CJavaScriptPoint : public JavaScriptSkeleton<IPoint, CJavaScriptPoint>
{public:
static JSBool getDocument(JSContext* Context, JSObject* Object, jsval ID, jsval* Value);
static JSBool getX(JSContext* Context, JSObject* Object, jsval ID, jsval* Value);
static JSBool setX(JSContext* Context, JSObject* Object, jsval ID, jsval* Value);
static JSBool getY(JSContext* Context, JSObject* Object, jsval ID, jsval* Value);
static JSBool setY(JSContext* Context, JSObject* Object, jsval ID, jsval* Value);
// Methods
};
由于各个方法实现都类似,所以在这里只展示一个方法。
JSBool CJavaScriptPoint::getX(JSContext* Context, JSObject* Object, jsval ID, jsval* Value)
{ int tempInt = Interface(Context, Object)->getX(); //从Java 脚本引擎获取主程序的点对象指针,并且获取X
*Value = ToJS(Context, tempInt); //把主程序的X坐标(int 类型),转换成Java 脚本引擎的变量
return JS_TRUE;
};
我们实现了CjavaScriptDocument、CJavaScriptPointCollection、CjavaScriptPoint,通过这些对象和相应方法的实现,我们可以在脚本中使用熟悉的语言来操作对象树中的对象。脚本示例如下:
var pointList = doc.points; //从全局文档对象实例(doc)获得点列表对象
var pointCount = points.length; //获取点列表对象的点对象个数
var i;
for( i =0; i<pointCount; i ++) //遍历点列表对象
{
var x = points[i].X; //获取第i个点对象的X坐标
var y= points[i].Y; //获取第i个点对象的Y坐标
};
通过这样的方式,用户可以在Java脚本中对主程序进行操作,实现标准的软件没有实现的某些功能,实现通用软件的用户化(customization)。
Java 脚本引擎为了能够操作主程序的对象类和对象,需要在引擎里面登记主程序的对象类。在这里我们使用了C++的强大功能之一——模版类来实现,这个模版类命名为JavaScriptSkeleton,代码实现相当简单,列表如下:
template<class InterfaceType, class BaseType>
class JavaScriptSkeleton
{public:
typedef JavaScriptSkeleton<InterfaceType, BaseType> Skeleton;
typedef BaseType Owner;
static JSObject* Create(
InterfaceType* const Interface, JSContext* const Context, JSObject* const Parent)
{
JSObject* const result = JS_NewObject(Context, &Class, 0, Parent); //创建新的Java 脚本全局对象
JS_SetPrivate(Context, result, Interface); //把主程序的相关接口进行登记
JS_DefineProperties(Context, result, Properties); //登记主程序对象的属性列表
JS_DefineFunctions(Context, result, Methods); //登记主程序的方法列表
return result;
};
static InterfaceType* Interface(JSContext* const Context, JSObject* const Object)
{ //该方法实现Java脚本对象到主程序接口的转换
if(JS_GetClass(Object) == &Class)
return reinterpret_ cast<InterfaceType*>(JS_GetPrivate(Context, Object));
else
return 0;
};
template<class NewInterfaceType, class NewActualType>
static JSBool DynamicCast(
JSContext* const Context, JSObject* const Object, const jsval ID, jsval* const Value)
{ //该方法实现Java脚本对象到不同的主程序接口的转换(主程序的对象采用C++的多重继承来实现)
NewInterfaceType* const i =
dynamic_cast<NewInterfaceType*>(Interface(Context, Object));
if (i==NULL) return JS_FALSE;
*Value = OBJECT_TO_JSVAL(NewActualType::Create(i, Context, Object));
return JS_TRUE;
};
private:
static JSClass Class;
static JSProperty Spec Properties[];
static JSFunctionSpec Methods[];
}。
来源:CCW
|