Chakra實戰:UWP與js交互(C#)
來自: http://hjc.im/uwp-chakra-js-1/
幾個月前在翻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文件,附加調試器,即可開始調試腳本。
