为了回答标题为“如何在C#中解析和执行JS”的问题,下面的代码包装了Windows脚本引擎。它支持32位和64位环境。
在您的特定情况下,这意味着取决于.JS代码,您可能必须模拟/实现一些HTMLDOM元素,例如“文档”,“窗口”等(使用MyItem类使用“命名项”功能)。正是Internet Explorer所做的)。
以下是您可以使用它的一些示例:
1)直接表达式求值:
Console.WriteLine(scriptEngine.eval("jscript", "1+2/3"));将显示1.66666666666667
2)函数调用,带有可选参数:
using (scriptEngine engine = new scriptEngine("jscript")){ Parsedscript parsed = engine.Parse("function MyFunc(x){return 1+2+x}"); Console.WriteLine(parsed.CallMethod("MyFunc", 3));}将显示6
3)带有命名项和可选参数的函数调用:
using (scriptEngine engine = new scriptEngine("jscript")){ Parsedscript parsed = engine.Parse("function MyFunc(x){return 1+2+x+My.Num}"); MyItem item = new MyItem(); item.Num = 4; engine.SetNamedItem("My", item); Console.WriteLine(parsed.CallMethod("MyFunc", 3));}[ComVisible(true)] // script engines are COM components.public class MyItem{ public int Num { get; set; }}将显示10。
编辑 :我添加了使用CLSID代替脚本语言名称的可能性,因此我们可以重复使用新的快速IE9 +“chakra”
Javascript引擎,如下所示:
using (scriptEngine engine = new scriptEngine("{16d51579-a30b-4c8b-a276-0ff4dc41e755}")){ // continue with chakra now}这是完整的源代码:
/// <summary>/// Represents a Windows script Engine such as Jscript, VBscript, etc./// </summary>public sealed class scriptEngine : IDisposable{ /// <summary> /// The name of the function used for simple evaluation. /// </summary> public const string MethodName = "evalMethod"; /// <summary> /// The default scripting language name. /// </summary> public const string DefaultLanguage = JavascriptLanguage; /// <summary> /// The Javascript or jscript scripting language name. /// </summary> public const string JavascriptLanguage = "javascript"; /// <summary> /// The javascript or jscript scripting language name. /// </summary> public const string VBscriptLanguage = "vbscript"; /// <summary> /// The chakra javascript engine CLSID. The value is {16d51579-a30b-4c8b-a276-0ff4dc41e755}. /// </summary> public const string ChakraClsid = "{16d51579-a30b-4c8b-a276-0ff4dc41e755}"; private IActivescript _engine; private IActivescriptParse32 _parse32; private IActivescriptParse64 _parse64; internal scriptSite Site; private Version _version; private string _name; [Guid("BB1A2AE1-A4F9-11cf-8F20-00805F2CD064"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IActivescript { [PreserveSig] int SetscriptSite(IActivescriptSite pass); [PreserveSig] int GetscriptSite(Guid riid, out IntPtr site); [PreserveSig] int SetscriptState(scriptState state); [PreserveSig] int GetscriptState(out scriptState scriptState); [PreserveSig] int Close(); [PreserveSig] int AddNamedItem(string name, scriptItem flags); [PreserveSig] int AddTypeLib(Guid typeLib, uint major, uint minor, uint flags); [PreserveSig] int GetscriptDispatch(string itemName, out IntPtr dispatch); [PreserveSig] int GetCurrentscriptThreadID(out uint thread); [PreserveSig] int GetscriptThreadID(uint win32ThreadId, out uint thread); [PreserveSig] int GetscriptThreadState(uint thread, out scriptThreadState state); [PreserveSig] int InterruptscriptThread(uint thread, out System.Runtime.InteropServices.ComTypes.EXCEPINFO exceptionInfo, uint flags); [PreserveSig] int Clone(out IActivescript script); } [Guid("4954E0D0-FBC7-11D1-8410-006008C3FBFC"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IActivescriptProperty { [PreserveSig] int GetProperty(int dwProperty, IntPtr pvarIndex, out object pvarValue); [PreserveSig] int SetProperty(int dwProperty, IntPtr pvarIndex, ref object pvarValue); } [Guid("DB01A1E3-A42B-11cf-8F20-00805F2CD064"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IActivescriptSite { [PreserveSig] int GetLCID(out int lcid); [PreserveSig] int GetItemInfo(string name, scriptInfo returnMask, out IntPtr item, IntPtr typeInfo); [PreserveSig] int GetDocVersionString(out string version); [PreserveSig] int OnscriptTerminate(object result, System.Runtime.InteropServices.ComTypes.EXCEPINFO exceptionInfo); [PreserveSig] int onStateChange(scriptState scriptState); [PreserveSig] int OnscriptError(IActivescriptError scriptError); [PreserveSig] int OnEnterscript(); [PreserveSig] int OnLeavescript(); } [Guid("EAE1BA61-A4ED-11cf-8F20-00805F2CD064"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IActivescriptError { [PreserveSig] int GetExceptionInfo(out System.Runtime.InteropServices.ComTypes.EXCEPINFO exceptionInfo); [PreserveSig] int GetSourcePosition(out uint sourceContext, out int lineNumber, out int characterPosition); [PreserveSig] int GetSourceLineText(out string sourceLine); } [Guid("BB1A2AE2-A4F9-11cf-8F20-00805F2CD064"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IActivescriptParse32 { [PreserveSig] int InitNew(); [PreserveSig] int Addscriptlet(string defaultName, string pre, string itemName, string subItemName, string eventName, string delimiter, IntPtr sourceContextcookie, uint startingLineNumber, scriptText flags, out string name, out System.Runtime.InteropServices.ComTypes.EXCEPINFO exceptionInfo); [PreserveSig] int ParsescriptText(string pre, string itemName, IntPtr context, string delimiter, int sourceContextcookie, uint startingLineNumber, scriptText flags, out object result, out System.Runtime.InteropServices.ComTypes.EXCEPINFO exceptionInfo); } [Guid("C7EF7658-E1EE-480E-97EA-D52CB4D76D17"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IActivescriptParse64 { [PreserveSig] int InitNew(); [PreserveSig] int Addscriptlet(string defaultName, string pre, string itemName, string subItemName, string eventName, string delimiter, IntPtr sourceContextcookie, uint startingLineNumber, scriptText flags, out string name, out System.Runtime.InteropServices.ComTypes.EXCEPINFO exceptionInfo); [PreserveSig] int ParsescriptText(string pre, string itemName, IntPtr context, string delimiter, long sourceContextcookie, uint startingLineNumber, scriptText flags, out object result, out System.Runtime.InteropServices.ComTypes.EXCEPINFO exceptionInfo); } [Flags] private enum scriptText { None = 0, //DelayExecution = 1, //IsVisible = 2, Isexpression = 32, IsPersistent = 64, //HostManageSource = 128 } [Flags] private enum scriptInfo { //None = 0, //IUnknown = 1, ITypeInfo = 2 } [Flags] private enum scriptItem { //None = 0, IsVisible = 2, IsSource = 4, //GlobalMembers = 8, //IsPersistent = 64, //Codeonly = 512, //NoCode = 1024 } private enum scriptThreadState { //NotInscript = 0, //Running = 1 } private enum scriptState { Uninitialized = 0, Started = 1, Connected = 2, Disconnected = 3, Closed = 4, Initialized = 5 } private const int TYPE_E_ELEMENTNOTFOUND = unchecked((int)(0x8002802B)); private const int E_NOTIMPL = -2147467263; /// <summary> /// Determines if a script engine with the input name exists. /// </summary> /// <param name="language">The language.</param> /// <returns>true if the engine exists; false otherwise.</returns> public static Version GetVersion(string language) { if (language == null) throw new ArgumentNullException("language"); Type engine; Guid clsid; if (Guid.TryParse(language, out clsid)) { engine = Type.GetTypeFromCLSID(clsid, false); } else { engine = Type.GetTypeFromProgID(language, false); } if (engine == null) return null; IActivescript scriptEngine = Activator.CreateInstance(engine) as IActivescript; if (scriptEngine == null) return null; IActivescriptProperty scriptProperty = scriptEngine as IActivescriptProperty; if (scriptProperty == null) return new Version(1, 0, 0, 0); int major = GetProperty(scriptProperty, scriptPROP_MAJORVERSION, 0); int minor = GetProperty(scriptProperty, scriptPROP_MINORVERSION, 0); int revision = GetProperty(scriptProperty, scriptPROP_BUILDNUMBER, 0); Version version = new Version(major, minor, Environment.OSVersion.Version.Build, revision); Marshal.ReleaseComObject(scriptProperty); Marshal.ReleaseComObject(scriptEngine); return version; } private static T GetProperty<T>(IActivescriptProperty prop, int index, T defaultValue) { object value; if (prop.GetProperty(index, IntPtr.Zero, out value) != 0) return defaultValue; try { return (T)Convert.ChangeType(value, typeof(T)); } catch { return defaultValue; } } /// <summary> /// Initializes a new instance of the <see cref="scriptEngine"/> class. /// </summary> /// <param name="language">The scripting language. Standard Windows script engines names are 'jscript' or 'vbscript'.</param> public scriptEngine(string language) { if (language == null) throw new ArgumentNullException("language"); Type engine; Guid clsid; if (Guid.TryParse(language, out clsid)) { engine = Type.GetTypeFromCLSID(clsid, true); } else { engine = Type.GetTypeFromProgID(language, true); } _engine = Activator.CreateInstance(engine) as IActivescript; if (_engine == null) throw new ArgumentException(language + " is not an Windows script Engine", "language"); Site = new scriptSite(); _engine.SetscriptSite(Site); // support 32-bit & 64-bit process if (IntPtr.Size == 4) { _parse32 = (IActivescriptParse32)_engine; _parse32.InitNew(); } else { _parse64 = (IActivescriptParse64)_engine; _parse64.InitNew(); } } private const int scriptPROP_NAME = 0x00000000; private const int scriptPROP_MAJORVERSION = 0x00000001; private const int scriptPROP_MINORVERSION = 0x00000002; private const int scriptPROP_BUILDNUMBER = 0x00000003; /// <summary> /// Gets the engine version. /// </summary> /// <value> /// The version. /// </value> public Version Version { get { if (_version == null) { int major = GetProperty(scriptPROP_MAJORVERSION, 0); int minor = GetProperty(scriptPROP_MINORVERSION, 0); int revision = GetProperty(scriptPROP_BUILDNUMBER, 0); _version = new Version(major, minor, Environment.OSVersion.Version.Build, revision); } return _version; } } /// <summary> /// Gets the engine name. /// </summary> /// <value> /// The name. /// </value> public string Name { get { if (_name == null) { _name = GetProperty(scriptPROP_NAME, string.Empty); } return _name; } } /// <summary> /// Gets a script engine property. /// </summary> /// <typeparam name="T">The expected property type.</typeparam> /// <param name="index">The property index.</param> /// <param name="defaultValue">The default value if not found.</param> /// <returns>The value of the property or the default value.</returns> public T GetProperty<T>(int index, T defaultValue) { object value; if (!TryGetProperty(index, out value)) return defaultValue; try { return (T)Convert.ChangeType(value, typeof(T)); } catch { return defaultValue; } } /// <summary> /// Gets a script engine property. /// </summary> /// <param name="index">The property index.</param> /// <param name="value">The value.</param> /// <returns>true if the property was successfully got; false otherwise.</returns> public bool TryGetProperty(int index, out object value) { value = null; IActivescriptProperty property = _engine as IActivescriptProperty; if (property == null) return false; return property.GetProperty(index, IntPtr.Zero, out value) == 0; } /// <summary> /// Sets a script engine property. /// </summary> /// <param name="index">The property index.</param> /// <param name="value">The value.</param> /// <returns>true if the property was successfully set; false otherwise.</returns> public bool SetProperty(int index, object value) { IActivescriptProperty property = _engine as IActivescriptProperty; if (property == null) return false; return property.SetProperty(index, IntPtr.Zero, ref value) == 0; } /// <summary> /// Adds the name of a root-level item to the scripting engine's name space. /// </summary> /// <param name="name">The name. May not be null.</param> /// <param name="value">The value. It must be a ComVisible object.</param> public void SetNamedItem(string name, object value) { if (name == null) throw new ArgumentNullException("name"); _engine.AddNamedItem(name, scriptItem.IsVisible | scriptItem.IsSource); Site.NamedItems[name] = value; } internal class scriptSite : IActivescriptSite { internal scriptException LastException; internal Dictionary<string, object> NamedItems = new Dictionary<string, object>(); int IActivescriptSite.GetLCID(out int lcid) { lcid = Thread.CurrentThread.CurrentCulture.LCID; return 0; } int IActivescriptSite.GetItemInfo(string name, scriptInfo returnMask, out IntPtr item, IntPtr typeInfo) { item = IntPtr.Zero; if ((returnMask & scriptInfo.ITypeInfo) == scriptInfo.ITypeInfo) return E_NOTIMPL; object value; if (!NamedItems.TryGetValue(name, out value)) return TYPE_E_ELEMENTNOTFOUND; item = Marshal.GetIUnknownForObject(value); return 0; } int IActivescriptSite.GetDocVersionString(out string version) { version = null; return 0; } int IActivescriptSite.OnscriptTerminate(object result, System.Runtime.InteropServices.ComTypes.EXCEPINFO exceptionInfo) { return 0; } int IActivescriptSite.onStateChange(scriptState scriptState) { return 0; } int IActivescriptSite.OnscriptError(IActivescriptError scriptError) { string sourceLine = null; try { scriptError.GetSourceLineText(out sourceLine); } catch { // happens sometimes... } uint sourceContext; int lineNumber; int characterPosition; scriptError.GetSourcePosition(out sourceContext, out lineNumber, out characterPosition); lineNumber++; characterPosition++; System.Runtime.InteropServices.ComTypes.EXCEPINFO exceptionInfo; scriptError.GetExceptionInfo(out exceptionInfo); string message; if (!string.IsNullOrEmpty(sourceLine)) { message = "script exception: {1}. Error number {0} (0x{0:X8}): {2} at line {3}, column {4}. Source line: '{5}'."; } else { message = "script exception: {1}. Error number {0} (0x{0:X8}): {2} at line {3}, column {4}."; } LastException = new scriptException(string.Format(message, exceptionInfo.spre, exceptionInfo.bstrSource, exceptionInfo.bstrDescription, lineNumber, characterPosition, sourceLine)); LastException.Column = characterPosition; LastException.Description = exceptionInfo.bstrDescription; LastException.Line = lineNumber; LastException.Number = exceptionInfo.spre; LastException.Text = sourceLine; return 0; } int IActivescriptSite.OnEnterscript() { LastException = null; return 0; } int IActivescriptSite.OnLeavescript() { return 0; } } /// <summary> /// evaluates an expression using the specified language. /// </summary> /// <param name="language">The language.</param> /// <param name="expression">The expression. May not be null.</param> /// <returns>The result of the evaluation.</returns> public static object eval(string language, string expression) { return eval(language, expression, null); } /// <summary> /// evaluates an expression using the specified language, with an optional array of named items. /// </summary> /// <param name="language">The language.</param> /// <param name="expression">The expression. May not be null.</param> /// <param name="namedItems">The named items array.</param> /// <returns>The result of the evaluation.</returns> public static object eval(string language, string expression, params KeyValuePair<string, object>[] namedItems) { if (language == null) throw new ArgumentNullException("language"); if (expression == null) throw new ArgumentNullException("expression"); using (scriptEngine engine = new scriptEngine(language)) { if (namedItems != null) { foreach (KeyValuePair<string, object> kvp in namedItems) { engine.SetNamedItem(kvp.Key, kvp.Value); } } return engine.eval(expression); } } /// <summary> /// evaluates an expression. /// </summary> /// <param name="expression">The expression. May not be null.</param> /// <returns>The result of the evaluation.</returns> public object eval(string expression) { if (expression == null) throw new ArgumentNullException("expression"); return Parse(expression, true); } /// <summary> /// Parses the specified text and returns an object that can be used for evaluation. /// </summary> /// <param name="text">The text to parse.</param> /// <returns>An instance of the Parsedscript class.</returns> public Parsedscript Parse(string text) { if (text == null) throw new ArgumentNullException("text"); return (Parsedscript)Parse(text, false); } private object Parse(string text, bool expression) { const string varName = "x___"; object result; _engine.SetscriptState(scriptState.Connected); scriptText flags = scriptText.None; if (expression) { flags |= scriptText.Isexpression; } try { // immediate expression computation seems to work only for 64-bit // so hack something for 32-bit... System.Runtime.InteropServices.ComTypes.EXCEPINFO exceptionInfo; if (_parse32 != null) { if (expression) { // should work for jscript & vbscript at least... text = varName + "=" + text; } _parse32.ParsescriptText(text, null, IntPtr.Zero, null, 0, 0, flags, out result, out exceptionInfo); } else { _parse64.ParsescriptText(text, null, IntPtr.Zero, null, 0, 0, flags, out result, out exceptionInfo); } } catch { if (Site.LastException != null) throw Site.LastException; throw; } IntPtr dispatch; if (expression) { // continue our 32-bit hack... if (_parse32 != null) { _engine.GetscriptDispatch(null, out dispatch); object dp = Marshal.GetObjectForIUnknown(dispatch); try { return dp.GetType().InvokeMember(varName, BindingFlags.GetProperty, null, dp, null); } catch { if (Site.LastException != null) throw Site.LastException; throw; } } return result; } _engine.GetscriptDispatch(null, out dispatch); Parsedscript parsed = new Parsedscript(this, dispatch); return parsed; } /// <summary> /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// </summary> public void Dispose() { if (_parse32 != null) { Marshal.ReleaseComObject(_parse32); _parse32 = null; } if (_parse64 != null) { Marshal.ReleaseComObject(_parse64); _parse64 = null; } if (_engine != null) { Marshal.ReleaseComObject(_engine); _engine = null; } }}public sealed class Parsedscript : IDisposable{ private object _dispatch; private readonly scriptEngine _engine; internal Parsedscript(scriptEngine engine, IntPtr dispatch) { _engine = engine; _dispatch = Marshal.GetObjectForIUnknown(dispatch); } public object CallMethod(string methodName, params object[] arguments) { if (_dispatch == null) throw new InvalidOperationException(); if (methodName == null) throw new ArgumentNullException("methodName"); try { return _dispatch.GetType().InvokeMember(methodName, BindingFlags.InvokeMethod, null, _dispatch, arguments); } catch { if (_engine.Site.LastException != null) throw _engine.Site.LastException; throw; } } void IDisposable.Dispose() { if (_dispatch != null) { Marshal.ReleaseComObject(_dispatch); _dispatch = null; } }}[Serializable]public class scriptException : Exception{ public scriptException() : base("script Exception") { } public scriptException(string message) : base(message) { } public scriptException(Exception innerException) : base(null, innerException) { } public scriptException(string message, Exception innerException) : base(message, innerException) { } protected scriptException(SerializationInfo info, StreamingContext context) : base(info, context) { } public string Description { get; internal set; } public int Line { get; internal set; } public int Column { get; internal set; } public int Number { get; internal set; } public string Text { get; internal set; }}


