7.6 CLR/Javascript(SpiderMonkey)
Limax提供一个clrjs项目,粘合C#与Javascript,支持C#与Javascript代码之间的互操作,用于实现C#脚本模式框架下的JavaScriptHandle。这一章介绍clrjs提供的功能。
-
7.6.2 C#与Javascript的互操作举例
首先定义Js js = new Js();
-
Javascript操纵C#
首先定义一个C#实验类。
public class TestJs { public TestJs() { Console.WriteLine("Construct TestJs"); acc = add; } public TestJs(int x) { Console.WriteLine("Construct TestJs with " + x); acc = add; } public int add(int a, int b) { return a + b; } public string mystr; public int Prop { get; set; } public int this[int x] { get { return x + 1; } set { Console.WriteLine("Indexed Property set key = " + x + " value = " + value); } } public delegate int ACC(int a, int b); public ACC acc; }
这里应该看到,需要通过Javascript访问的对象成员必须声明为public。
-
构造对象
js.eval("var t0 = new<0>()", typeof(TestJs)); js.eval("var t1 = new<0>(100)", typeof(TestJs)); Console.WriteLine(js.eval("t0") is TestJs);
输出:
Construct TestJs
Construct TestJs with 100
True
在这里,类TestJs被传递进Javascript虚拟机,在类上执行new操作,也就创建了对象,通过参数个数的控制可以选择不同的构造函数进行调用。第三行把Javascript中创建的变量t0传回C#进行验证,果然是TestJs对象。
-
instanceof测试
js.eval("print(t0 instanceof <0>)", typeof(object)); js.eval("print(t0 instanceof <0>)", typeof(TestJs));
输出:
true
true
在这里,t0是通过C#类TestJs构造的对象,所以instanceof操作转发给C#执行,两行代码都输出true。
继续执行:
js.eval("print('abc' instanceof <0>)", typeof(string)); js.eval("print(<0> instanceof <1>)", 'a', typeof(string)); js.eval("print(100 instanceof <0>)", typeof(int)); js.eval("print(<0> instanceof <1>)", 100, typeof(int)); js.eval("print(<0> instanceof <1>)", 100L, typeof(int)); js.eval("print(<0> instanceof <1>)", 100L, typeof(double)); js.eval("print(<0> instanceof <1>)", UInt32.MaxValue, typeof(uint)); js.eval("print(<0> instanceof <1>)", UInt32.MaxValue, typeof(double)); js.eval("print(<0> instanceof <1>)", (uint)10, typeof(uint));
输出:
true
true
true
true
false
true
false
true
false
实际上instanceof执行时,用第二个参数作为类型来测试第一个参数,所以第一条语句输出true;第二条语句,字符型进入javascript转换为串类型,所以测试为true;第三,第四条语句输出true显而易见;第五,第六条语句,javascript整数仅支持int,所以long被转换为double;第七,第八条语,既然uint为无符号类型,那么大于0x7FFFFFFF的uint只能转换为double,转换为int将变为负数,完全损失了无符号的意义;第九条语句,尽管第一个参数类型为uint, 但是javascript内部并不存在这个类型, 所以输出false,实际上,javascript的数值只有int,double两种类型,用之外的C#类型进行测试永远false
-
方法调用
js.eval("print (t0.add(1,2))");
输出:
3
如果是类的静态方法,同样可以调用。
-
字段访问
js.eval("print(t0.mystr)"); js.eval("t0.mystr='hello world'"); Console.WriteLine(js.eval("t0.mystr")); Console.WriteLine(((TestJs)js.eval("t0")).mystr);
输出:
null
helloworld
helloworld
第一行代码,输出mystr中的内容,对象构造时没有初始化该字段,C#中为null,对应了Javascript中的null,第二行设置mystr的字段内容,所以第三第四行访问均正确返回了设置内容。
-
普通属性访问
js.eval("print(t0.Prop)"); js.eval("t0.Prop = t0.Prop + 10"); Console.WriteLine(js.eval("t0.Prop"));
输出:
0
10
第一行代码输出Prop的默认初始化值0,第二行相当于读取该属性,再设置该属性,属性上加上10,所以第三行输出10。
-
索引化属性访问
js.eval("print(t0[100])"); js.eval("t0[200] = 300");
输出:
101
Indexed Property set key = 200 value = 300
这里可以看到索引化属性的两个方法均被正确调用。
-
委托访问
js.eval("print(t0.acc(100,200))");
输出:
300
对照前面的C#代码,这里的acc实际上是一个委托,指向了add方法,所以实际上执行的是add。
-
Javascript访问C#的具体细节参见章节《脚本语言访问C#规范》
-
-
复杂交互
首先定义委托FN
public delegate int FN(object f, int a);
然后执行下面的代码
FN fn = (object f, int a) => (int)js.eval("<1><2?<1>:<0>(<0>,<1>-1)+<0>(<0>,<1>-2);", f, a); js.eval("print(<0>(<0>,<1>))", fn, 9);
输出:
34
事实上,这段程序在C#和Javascript之间间接递归,计算了斐波那契数列的第9项。说明了C#和Javascript进行复杂交互的可能性。
-