几个月前在翻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。其实不了解直接照抄代码拿着用也没什么不好,就是出了错之后不好排除。
使用C#调用Chakra API
UWP是可以直接使用chakra.dll大部分函数的,除去JsStartProfiling
JsStopProfiling
JsEnumerateHeap
和 JsIsEnumeratingHeap
四个。
然而Microsoft并没有在SDK里提供C#/WinRT API,所以需要用P/Invoke进行基本的封装。这里以Microsoft官方示例为准。
将上述的Native.cs以及该目录下所有文件都加入项目。
使用C#调用Javascript
主要步骤:
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);
}
使用Javascript调用WinRT
在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文件,附加调试器,即可开始调试脚本。