几个月前在翻MSDN时发现Microsoft已经允许在Windows Store Apps(即UWP)里使用Chakra的API了。这意味着大家终于可以光明正大地在app中调用Javascript。//另外UWP允许JIT了所以你自己移植个V8上去其实也行在8.x时代,Chakra是被标记为Desktop only的API,想要在Store apps里使用js,要么整个App使用HTML/js编写,要么使用WebView调用。前者显然不符合主要使用C#/XAML编写UI的前提,后者
不好用。
UWP写起来真舒服
使用Chakra之前需要较为深入地了解Chakra API,COM和JavaScript。
UWP是可以直接使用chakra.dll大部分函数的,除去 JsStartProfiling
JsStopProfiling
JsEnumerateHeap
和 JsIsEnumeratingHeap
四个。
然而Microsoft并没有在SDK里提供C#/WinRT API,所以需要用P/Invoke进行基本的封装。这里以 Microsoft官方示例 为准。
将上述的Native.cs以及该目录下所有文件都加入项目。
主要步骤:
1.使用 JsCreateRuntime
创建一个Javascript运行时(runtime)
JavaScriptRuntime runtime; Native.ThrowIfError(Native.JsCreateRuntime(JavaScriptRuntimeAttributes.None, null, out runtime));
2.使用 JsCreateContext
在这个运行时内创建一个上下文(context)
JavaScriptContext context; Native.ThrowIfError(Native.JsCreateContext(runtime, out context));
3.使用 JsSetCurrentContext
将该上下文设置到当前线程
Native.ThrowIfError(Native.JsSetCurrentContext(context));
4.(可选)使用 JsStartDebugging
开启调试
Native.ThrowIfError(Native.JsStartDebugging());
5.使用 JsRunScript
运行Javascript
JavaScriptValue result; JavaScriptSourceContext currentSourceContext = JavaScriptSourceContext.FromIntPtr(IntPtr.Zero); if (Native.JsRunScript(script, currentSourceContext, ""/*如果需要调试,需要在此处指定源码绝对路径*/, out result) != JavaScriptErrorCode.NoError) { JavaScriptException exception; Native.ThrowIfError(Native.JsGetAndClearException(out exception)); //在此处理异常 } JavaScriptValue stringResult; UIntPtr stringLength; Native.ThrowIfError(Native.JsConvertValueToString(result, out stringResult)); Native.ThrowIfError(Native.JsStringToPointer(stringResult, out returnValue, out stringLength)); var ret = Marshal.PtrToStringUni(returnValue);//处理返回值
6.其它用途(如 JsCallFunction
等)
需要注意的是,如果当前使用的上下文已经被设定到一个线程(第三步),那么该上下文仅能用于这个线程。当这个线程不再需要这个上下文时,需要将其设置为NULL(第七步)。
7.将当前线程的jsrt上下文设置为NULL
Native.ThrowIfError(Native.JsSetCurrentContext(new JavaScriptContext()));
8.(可选)在其它线程使用这个jsrt上下文(从第三步开始重复)9.使用完成后销毁这个runtime
Native.ThrowIfError(Native.JsDisposeRuntime(runtime));
JavaScript的类型与C#是不同的,而在Chakra API中使用JsValueRef(即C#中封装的JavaScriptValue)来表示一个值。
常用的类型主要有Undefined, Null, Number, String, Boolean, Object, Function, Array等。C#与Javascript交互时,需要将JavaScriptValue与 .NET 的类型互相转换。
JavaScriptValue本身封装了集中简单类型的转换,例如Number与System.Double: JavaScriptValue.FromDouble()
与 JavaScriptValue.ToDouble()
判断JavaScriptValue的类型使用 JsGetValueType
函数
JavaScriptValueType type; Native.ThrowIfError(Native.JsGetValueType(val, out type)); switch (type) { //对特定类型进行处理 }
WinRT类型的转换
任何 WinRT类型 (写C#时可粗略理解为放在winmd里的类型)均继承自IInspectable,可以直接将其对象使用 JsInspectableToObject
转换成JavaScriptValue使用。
同样如果 确定一个JavaScriptValue代表的对象继承自IInspectable ,也可以使用 JsObjectToInspectable
将其转换为System.Object。
数组类型的转换
JavaScript的数组并没有实现IInspectable接口,因此它不能直接使用COM交互,需要手动读取其值并且进行转换。
举例:转换为.NET的List
List<T> JsArrayToList<T>(JavaScriptValue arrayval) { var _retList = new List<T>(); JavaScriptValueType type; Native.ThrowIfError(Native.JsGetValueType(arrayval, out type)); if (type != JavaScriptValueType.Array) return null; JavaScriptValue lengthvalue; Native.ThrowIfError(Native.JsGetProperty( arrayval, JavaScriptPropertyId.FromString("length"), out lengthvalue)); int length; Native.ThrowIfError(Native.JsNumberToInt(lengthvalue, out length)); for (int i = 0; i < length; i++) { JavaScriptValue elem; Native.ThrowIfError(Native.JsGetIndexedProperty( arrayval, JavaScriptValue.FromInt32(i), out elem)); JavaScriptValueType elemtype; Native.ThrowIfError(Native.JsGetValueType(elem, out elemtype)); if (elemtype == JavaScriptValueType.Object) { object insp; var err = Native.JsObjectToInspectable(elem, out insp); if (err == JavaScriptErrorCode.NoError && insp.GetType() == typeof(T)) _retList.Add((T)insp); } } return _retList; }
首先调用 JsGetGlobalObject
获取当前上下文的全局对象,使用 JsGetProperty
获得函数的对象,再使用 JsCallFunction
调用函数。函数的参数需要全部转换成JavaScriptValue,同时将返回值从JavaScriptValue转换成所需要的类型。
JavaScriptValue CallFunction(string functionName, params JavaScriptValue[] parameters) { JavaScriptValue _globalObject; Native.ThrowIfError(Native.JsGetGlobalObject(out _globalObject)); var functionId = JavaScriptPropertyId.FromString(functionName); var function = _globalObject.GetProperty(functionId); return function.CallFunction(parameters); }
在UWP中使用Javascript而不是Python等别的脚本语言做扩展,原因之一就是JavaScript调用Windows Runtime Component(WinRT组件)非常方便。无论是对于系统API还是自己创建的WinRT组件,都可以用 JsProjectWinRTNamespace
轻松地映射到Javascript中。映射后的使用方式,与直接使用HTML/js编写UWP时调用WinRT API相同。需要注意的是,有WebHostHiddenAttribute的WinRT类仍然无法被js使用。
如果想将WinRT对象映射为js的全局对象,也可以先使用 JsInspectableToObject
将其转换为JavaScriptValue,使用 JsGetGlobalObject
获得全局对象, JsSetProperty
将WinRT类型的对象设置为全局对象的属性。
集成VS调试方便是Chakra的另一大优点。将VS的C#项目内的调试器类型设置为"Script"(如图),并在代码中调用 JsStartDebugging
,并且指定js源代码文件位置(见上文),在VS中打开相应js文件,附加调试器,即可开始调试脚本。