// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using Microsoft.ClearScript.Util; namespace Microsoft.ClearScript { ///

/// Provides the base implementation for all script engines. /// public abstract class ScriptEngine : IDisposable { #region data private readonly string name; private Type accessContext; private ScriptAccess defaultAccess; private static readonly IUniqueNameManager nameManager = new UniqueNameManager(); private static readonly object nullHostObjectProxy = new object(); [ThreadStatic] private static ScriptEngine currentEngine; #endregion #region constructors /// /// Initializes a new script engine instance. /// /// A name to associate with the instance. Currently this name is used only as a label in presentation contexts such as debugger user interfaces. protected ScriptEngine(string name) { this.name = nameManager.GetUniqueName(name, GetType().GetRootName()); } #endregion #region public members /// /// Gets the name associated with the script engine instance. /// public string Name { get { return name; } } /// /// Gets the script engine that is invoking a host member on the current thread. /// /// /// If multiple script engines are invoking host members on the current thread, this /// property gets the one responsible for the most deeply nested invocation. If no script /// engines are invoking host members on the current thread, this property returns /// null. /// public static ScriptEngine Current { get { return currentEngine; } } /// /// Gets the script engine's recommended file name extension for script files. /// public abstract string FileNameExtension { get; } /// /// Allows script code to access non-public host resources. /// /// /// By setting this property to a type you declare that script code running in the current /// script engine is to be treated as if it were part of that type's implementation. Doing /// so does not expose any host resources to script code, but it affects which host /// resources are importable and which members of exposed resources are accessible. /// public Type AccessContext { get { return accessContext; } set { accessContext = value; OnAccessSettingsChanged(); } } /// /// Gets or sets the default script access setting for all members of exposed objects. /// /// /// Use , , or /// their subclasses to override this property for individual types and members. Note that /// this property has no effect on the method binding algorithm. If a script-based call is /// bound to a method that is blocked by this property, it will be rejected even if an /// overload exists that could receive the call. /// public ScriptAccess DefaultAccess { get { return defaultAccess; } set { defaultAccess = value; OnAccessSettingsChanged(); } } /// /// Enables or disables script code formatting. /// /// /// When this property is set to true, the script engine may format script code /// before executing or compiling it. This is intended to facilitate interactive debugging. /// The formatting operation currently includes stripping leading and trailing blank lines /// and removing global indentation. /// public bool FormatCode { get; set; } /// /// Controls whether script code is permitted to use reflection. /// /// /// When this property is set to true, script code running in the current script /// engine is permitted to use reflection. This affects /// Object.GetType(), /// Exception.GetType(), /// Delegate.Method, /// and . /// By default, any attempt to invoke these members from script code results in an /// exception. /// public bool AllowReflection { get; set; } /// /// Enables or disables type restriction for field, property, and method return values. /// /// /// When this property is set to true, script code running in the current script /// engine has access to the runtime types of all exposed host resources, which by default /// are restricted to their declared types. The default behavior is a general requirement /// for correct method binding, so setting this property to true is not recommended. /// /// public bool DisableTypeRestriction { get; set; } /// /// Enables or disables type restriction for array and list elements retrieved by index. /// /// /// In ClearScript 5.4.4 and earlier, indexed array and list elements were exempt from type /// restriction. ClearScript 5.4.5 introduced a breaking change to correct this, but you can /// set this property to true to restore the exemption if you have older script code /// that depends on it. /// /// public bool DisableListIndexTypeRestriction { get; set; } /// /// Enables or disables null wrapping for field, property, and method return values. /// /// /// When this property is set to true, all field, property, and method return values /// are marshaled with full .NET type information even if they are null. Note that /// such values will always fail equality comparison with JavaScript's /// null, /// VBScript's /// Nothing, /// and other similar values. Instead, use or /// to perform such a comparison. /// /// /// public bool EnableNullResultWrapping { get; set; } /// /// Enables or disables the use of reflection-based method binding as a fallback. /// /// /// When this property is set to true, the script engine attempts to use /// reflection-based method binding when the default method binding algorithm fails. This /// approach reduces type safety, but it may be useful for running legacy scripts that rely /// on the specific behavior of reflection-based method binding. /// public bool UseReflectionBindFallback { get; set; } /// /// Enables or disables automatic host variable tunneling for by-reference arguments to script functions and delegates. /// /// /// When this property is set to true, the script engine replaces by-reference /// arguments to script functions and delegates with host variables, allowing script code /// to simulate output arguments if the script language does not support them natively. /// /// public bool EnableAutoHostVariables { get; set; } /// /// Gets or sets a callback that can be used to halt script execution. /// /// /// During script execution the script engine periodically invokes this callback to /// determine whether it should continue. If the callback returns false, the script /// engine terminates script execution and throws an exception. /// public ContinuationCallback ContinuationCallback { get; set; } /// /// Allows the host to access script resources directly. /// /// /// The value of this property is an object that is bound to the script engine's root /// namespace. It dynamically supports properties and methods that correspond to global /// script objects and functions. /// public abstract dynamic Script { get; } /// /// Exposes a host object to script code. /// /// A name for the new global script item that will represent the object. /// The object to expose. /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// public void AddHostObject(string itemName, object target) { AddHostObject(itemName, HostItemFlags.None, target); } /// /// Exposes a host object to script code with the specified options. /// /// A name for the new global script item that will represent the object. /// A value that selects options for the operation. /// The object to expose. /// /// /// Once a host object is exposed to script code, its members are accessible via the script /// language's native syntax for member access. The following table provides details about /// the mapping between host members and script-accessible properties and methods. /// /// /// /// /// Member Type /// Exposed As /// Remarks /// /// /// Constructor /// N/A /// /// To invoke a constructor from script code, call /// HostFunctions.newObj(T). /// /// /// /// Property/Field /// Property /// N/A /// /// /// Method /// Method /// /// Overloaded host methods are merged into a single script-callable method. At /// runtime the correct host method is selected based on the argument types. /// /// /// /// Generic Method /// Method /// /// The ClearScript library supports dynamic C#-like type inference when invoking /// generic methods. However, some methods require explicit type arguments. To call /// such a method from script code, you must place the required number of /// host type objects /// at the beginning of the argument list. Doing so for methods that do not require /// explicit type arguments is optional. /// /// /// /// Extension Method /// Method /// /// Extension methods are available if the type that implements them has been /// exposed in the current script engine. /// /// /// /// Indexer /// Property /// /// Indexers appear as properties named "Item" that accept one or more index values /// as arguments. In addition, objects that implement expose /// properties with numeric names that match their valid indices. This includes /// one-dimensional host arrays and other collections. Multidimensional host arrays /// do not expose functional indexers; you must use /// Array.GetValue /// and /// Array.SetValue /// instead. /// /// /// /// Event /// Property /// /// Events are exposed as read-only properties of type . /// /// /// /// /// public void AddHostObject(string itemName, HostItemFlags flags, object target) { MiscHelpers.VerifyNonNullArgument(target, "target"); AddHostItem(itemName, flags, target); } /// /// Exposes a host object to script code with the specified type restriction. /// /// The type whose members are to be made accessible from script code. /// A name for the new global script item that will represent the object. /// The object to expose. /// /// /// This method can be used to restrict script access to the members of a particular /// interface or base class. /// /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// /// public void AddRestrictedHostObject(string itemName, T target) { AddRestrictedHostObject(itemName, HostItemFlags.None, target); } /// /// Exposes a host object to script code with the specified type restriction and options. /// /// The type whose members are to be made accessible from script code. /// A name for the new global script item that will represent the object. /// A value that selects options for the operation. /// The object to expose. /// /// /// This method can be used to restrict script access to the members of a particular /// interface or base class. /// /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// /// public void AddRestrictedHostObject(string itemName, HostItemFlags flags, T target) { AddHostItem(itemName, flags, HostItem.Wrap(this, target, typeof(T))); } /// /// Creates a COM/ActiveX object and exposes it to script code. The registered class is /// specified by programmatic identifier (ProgID). /// /// A name for the new global script item that will represent the object. /// The programmatic identifier (ProgID) of the registered class to instantiate. /// /// /// The argument can be a class identifier (CLSID) in standard /// GUID format with braces (e.g., "{0D43FE01-F093-11CF-8940-00A0C9054228}"). /// /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// /// public void AddCOMObject(string itemName, string progID) { AddCOMObject(itemName, HostItemFlags.None, progID); } /// /// Creates a COM/ActiveX object on the specified server and exposes it to script code. The /// registered class is specified by programmatic identifier (ProgID). /// /// A name for the new global script item that will represent the object. /// The programmatic identifier (ProgID) of the registered class to instantiate. /// The name of the server on which to create the object. /// /// /// The argument can be a class identifier (CLSID) in standard /// GUID format with braces (e.g., "{0D43FE01-F093-11CF-8940-00A0C9054228}"). /// /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// /// public void AddCOMObject(string itemName, string progID, string serverName) { AddCOMObject(itemName, HostItemFlags.None, progID, serverName); } /// /// Creates a COM/ActiveX object and exposes it to script code with the specified options. /// The registered class is specified by programmatic identifier (ProgID). /// /// A name for the new global script item that will represent the object. /// A value that selects options for the operation. /// The programmatic identifier (ProgID) of the registered class to instantiate. /// /// /// The argument can be a class identifier (CLSID) in standard /// GUID format with braces (e.g., "{0D43FE01-F093-11CF-8940-00A0C9054228}"). /// /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// /// public void AddCOMObject(string itemName, HostItemFlags flags, string progID) { AddCOMObject(itemName, flags, progID, null); } /// /// Creates a COM/ActiveX object on the specified server and exposes it to script code with /// the specified options. The registered class is specified by programmatic identifier (ProgID). /// /// A name for the new global script item that will represent the object. /// A value that selects options for the operation. /// The programmatic identifier (ProgID) of the registered class to instantiate. /// The name of the server on which to create the object. /// /// /// The argument can be a class identifier (CLSID) in standard /// GUID format with braces (e.g., "{0D43FE01-F093-11CF-8940-00A0C9054228}"). /// /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// /// public void AddCOMObject(string itemName, HostItemFlags flags, string progID, string serverName) { AddHostItem(itemName, flags, MiscHelpers.CreateCOMObject(progID, serverName)); } /// /// Creates a COM/ActiveX object and exposes it to script code. The registered class is /// specified by class identifier (CLSID). /// /// A name for the new global script item that will represent the object. /// The class identifier (CLSID) of the registered class to instantiate. /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// public void AddCOMObject(string itemName, Guid clsid) { AddCOMObject(itemName, HostItemFlags.None, clsid); } /// /// Creates a COM/ActiveX object on the specified server and exposes it to script code. The /// registered class is specified by class identifier (CLSID). /// /// A name for the new global script item that will represent the object. /// The class identifier (CLSID) of the registered class to instantiate. /// The name of the server on which to create the object. /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// public void AddCOMObject(string itemName, Guid clsid, string serverName) { AddCOMObject(itemName, HostItemFlags.None, clsid, serverName); } /// /// Creates a COM/ActiveX object and exposes it to script code with the specified options. /// The registered class is specified by class identifier (CLSID). /// /// A name for the new global script item that will represent the object. /// A value that selects options for the operation. /// The class identifier (CLSID) of the registered class to instantiate. /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// public void AddCOMObject(string itemName, HostItemFlags flags, Guid clsid) { AddCOMObject(itemName, flags, clsid, null); } /// /// Creates a COM/ActiveX object on the specified server and exposes it to script code with /// the specified options. The registered class is specified by class identifier (CLSID). /// /// A name for the new global script item that will represent the object. /// A value that selects options for the operation. /// The class identifier (CLSID) of the registered class to instantiate. /// The name of the server on which to create the object. /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// public void AddCOMObject(string itemName, HostItemFlags flags, Guid clsid, string serverName) { AddHostItem(itemName, flags, MiscHelpers.CreateCOMObject(clsid, serverName)); } /// /// Exposes a host type to script code with a default name. /// /// The type to expose. /// /// /// This method uses 's name for the new global script item that /// will represent it. /// /// /// Host types are exposed to script code in the form of objects whose properties and /// methods are bound to the type's static members and nested types. If the type has /// generic parameters, the corresponding object will be invocable with type arguments to /// yield a specific type. /// /// /// For more information about the mapping between host members and script-callable /// properties and methods, see . /// /// public void AddHostType(Type type) { AddHostType(HostItemFlags.None, type); } /// /// Exposes a host type to script code with a default name and the specified options. /// /// A value that selects options for the operation. /// The type to expose. /// /// /// This method uses 's name for the new global script item that /// will represent it. /// /// /// Host types are exposed to script code in the form of objects whose properties and /// methods are bound to the type's static members and nested types. If the type has /// generic parameters, the corresponding object will be invocable with type arguments to /// yield a specific type. /// /// /// For more information about the mapping between host members and script-callable /// properties and methods, see . /// /// public void AddHostType(HostItemFlags flags, Type type) { AddHostType(type.GetRootName(), flags, type); } /// /// Exposes a host type to script code. /// /// A name for the new global script item that will represent the type. /// The type to expose. /// /// /// Host types are exposed to script code in the form of objects whose properties and /// methods are bound to the type's static members and nested types. If the type has /// generic parameters, the corresponding object will be invocable with type arguments to /// yield a specific type. /// /// /// For more information about the mapping between host members and script-callable /// properties and methods, see . /// /// public void AddHostType(string itemName, Type type) { AddHostType(itemName, HostItemFlags.None, type); } /// /// Exposes a host type to script code with the specified options. /// /// A name for the new global script item that will represent the type. /// A value that selects options for the operation. /// The type to expose. /// /// /// Host types are exposed to script code in the form of objects whose properties and /// methods are bound to the type's static members and nested types. If the type has /// generic parameters, the corresponding object will be invocable with type arguments to /// yield a specific type. /// /// /// For more information about the mapping between host members and script-callable /// properties and methods, see . /// /// public void AddHostType(string itemName, HostItemFlags flags, Type type) { MiscHelpers.VerifyNonNullArgument(type, "type"); AddHostItem(itemName, flags, HostType.Wrap(type)); } /// /// Exposes a host type to script code. The type is specified by name. /// /// A name for the new global script item that will represent the type. /// The fully qualified name of the type to expose. /// Optional generic type arguments. /// /// /// Host types are exposed to script code in the form of objects whose properties and /// methods are bound to the type's static members and nested types. If the type has /// generic parameters, the corresponding object will be invocable with type arguments to /// yield a specific type. /// /// /// For more information about the mapping between host members and script-callable /// properties and methods, see . /// /// public void AddHostType(string itemName, string typeName, params Type[] typeArgs) { AddHostType(itemName, HostItemFlags.None, typeName, typeArgs); } /// /// Exposes a host type to script code with the specified options. The type is specified by name. /// /// A name for the new global script item that will represent the type. /// A value that selects options for the operation. /// The fully qualified name of the type to expose. /// Optional generic type arguments. /// /// /// Host types are exposed to script code in the form of objects whose properties and /// methods are bound to the type's static members and nested types. If the type has /// generic parameters, the corresponding object will be invocable with type arguments to /// yield a specific type. /// /// /// For more information about the mapping between host members and script-callable /// properties and methods, see . /// /// public void AddHostType(string itemName, HostItemFlags flags, string typeName, params Type[] typeArgs) { AddHostItem(itemName, flags, TypeHelpers.ImportType(typeName, null, false, typeArgs)); } /// /// Exposes a host type to script code. The type is specified by type name and assembly name. /// /// A name for the new global script item that will represent the type. /// The fully qualified name of the type to expose. /// The name of the assembly that contains the type to expose. /// Optional generic type arguments. /// /// /// Host types are exposed to script code in the form of objects whose properties and /// methods are bound to the type's static members and nested types. If the type has /// generic parameters, the corresponding object will be invocable with type arguments to /// yield a specific type. /// /// /// For more information about the mapping between host members and script-callable /// properties and methods, see . /// /// public void AddHostType(string itemName, string typeName, string assemblyName, params Type[] typeArgs) { AddHostType(itemName, HostItemFlags.None, typeName, assemblyName, typeArgs); } /// /// Exposes a host type to script code with the specified options. The type is specified by /// type name and assembly name. /// /// A name for the new global script item that will represent the type. /// A value that selects options for the operation. /// The fully qualified name of the type to expose. /// The name of the assembly that contains the type to expose. /// Optional generic type arguments. /// /// /// Host types are exposed to script code in the form of objects whose properties and /// methods are bound to the type's static members and nested types. If the type has /// generic parameters, the corresponding object will be invocable with type arguments to /// yield a specific type. /// /// /// For more information about the mapping between host members and script-callable /// properties and methods, see . /// /// public void AddHostType(string itemName, HostItemFlags flags, string typeName, string assemblyName, params Type[] typeArgs) { AddHostItem(itemName, flags, TypeHelpers.ImportType(typeName, assemblyName, true, typeArgs)); } /// /// Imports a COM/ActiveX type and exposes it to script code. The registered class is /// specified by programmatic identifier (ProgID). /// /// A name for the new global script item that will represent the type. /// The programmatic identifier (ProgID) of the registered class to import. /// /// /// The argument can be a class identifier (CLSID) in standard /// GUID format with braces (e.g., "{0D43FE01-F093-11CF-8940-00A0C9054228}"). /// /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// /// public void AddCOMType(string itemName, string progID) { AddCOMType(itemName, HostItemFlags.None, progID); } /// /// Imports a COM/ActiveX type from the specified server and exposes it to script code. The /// registered class is specified by programmatic identifier (ProgID). /// /// A name for the new global script item that will represent the type. /// The programmatic identifier (ProgID) of the registered class to import. /// The name of the server from which to import the type. /// /// /// The argument can be a class identifier (CLSID) in standard /// GUID format with braces (e.g., "{0D43FE01-F093-11CF-8940-00A0C9054228}"). /// /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// /// public void AddCOMType(string itemName, string progID, string serverName) { AddCOMType(itemName, HostItemFlags.None, progID, serverName); } /// /// Imports a COM/ActiveX type and exposes it to script code with the specified options. /// The registered class is specified by programmatic identifier (ProgID). /// /// A name for the new global script item that will represent the type. /// A value that selects options for the operation. /// The programmatic identifier (ProgID) of the registered class to import. /// /// /// The argument can be a class identifier (CLSID) in standard /// GUID format with braces (e.g., "{0D43FE01-F093-11CF-8940-00A0C9054228}"). /// /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// /// public void AddCOMType(string itemName, HostItemFlags flags, string progID) { AddCOMType(itemName, flags, progID, null); } /// /// Imports a COM/ActiveX type from the specified server and exposes it to script code with /// the specified options. The registered class is specified by programmatic identifier (ProgID). /// /// A name for the new global script item that will represent the type. /// A value that selects options for the operation. /// The programmatic identifier (ProgID) of the registered class to import. /// The name of the server from which to import the type. /// /// /// The argument can be a class identifier (CLSID) in standard /// GUID format with braces (e.g., "{0D43FE01-F093-11CF-8940-00A0C9054228}"). /// /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// /// public void AddCOMType(string itemName, HostItemFlags flags, string progID, string serverName) { AddHostItem(itemName, flags, HostType.Wrap(MiscHelpers.GetCOMType(progID, serverName))); } /// /// Imports a COM/ActiveX type and exposes it to script code. The registered class is /// specified by class identifier (CLSID). /// /// A name for the new global script item that will represent the type. /// The class identifier (CLSID) of the registered class to import. /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// public void AddCOMType(string itemName, Guid clsid) { AddCOMType(itemName, HostItemFlags.None, clsid); } /// /// Imports a COM/ActiveX type from the specified server and exposes it to script code. The /// registered class is specified by class identifier (CLSID). /// /// A name for the new global script item that will represent the type. /// The class identifier (CLSID) of the registered class to import. /// The name of the server from which to import the type. /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// public void AddCOMType(string itemName, Guid clsid, string serverName) { AddCOMType(itemName, HostItemFlags.None, clsid, serverName); } /// /// Imports a COM/ActiveX type and exposes it to script code with the specified options. /// The registered class is specified by class identifier (CLSID). /// /// A name for the new global script item that will represent the type. /// A value that selects options for the operation. /// The class identifier (CLSID) of the registered class to import. /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// public void AddCOMType(string itemName, HostItemFlags flags, Guid clsid) { AddCOMType(itemName, flags, clsid, null); } /// /// Imports a COM/ActiveX type from the specified server and exposes it to script code with /// the specified options. The registered class is specified by class identifier (CLSID). /// /// A name for the new global script item that will represent the type. /// A value that selects options for the operation. /// The class identifier (CLSID) of the registered class to import. /// The name of the server from which to import the type. /// /// For information about the mapping between host members and script-callable properties /// and methods, see . /// public void AddCOMType(string itemName, HostItemFlags flags, Guid clsid, string serverName) { AddHostItem(itemName, flags, HostType.Wrap(MiscHelpers.GetCOMType(clsid, serverName))); } /// /// Executes script code. /// /// The script code to execute. /// /// /// In some script languages the distinction between statements and expressions is /// significant but ambiguous for certain syntactic elements. This method always /// interprets the specified script code as a statement. /// /// /// If a debugger is attached, it will present the specified script code to the user as a /// document with an automatically selected name. This document will not be discarded /// after execution. /// /// public void Execute(string code) { Execute(null, code); } /// /// Executes script code with an associated document name. /// /// A document name for the script code. Currently this name is used only as a label in presentation contexts such as debugger user interfaces. /// The script code to execute. /// /// /// In some script languages the distinction between statements and expressions is /// significant but ambiguous for certain syntactic elements. This method always /// interprets the specified script code as a statement. /// /// /// If a debugger is attached, it will present the specified script code to the user as a /// document with the specified name. This document will not be discarded after execution. /// /// public void Execute(string documentName, string code) { Execute(documentName, false, code); } /// /// Executes script code with an associated document name, optionally discarding the document after execution. /// /// A document name for the script code. Currently this name is used only as a label in presentation contexts such as debugger user interfaces. /// True to discard the script document after execution, false otherwise. /// The script code to execute. /// /// /// In some script languages the distinction between statements and expressions is /// significant but ambiguous for certain syntactic elements. This method always /// interprets the specified script code as a statement. /// /// /// If a debugger is attached, it will present the specified script code to the user as a /// document with the specified name. Discarding this document removes it from view but /// has no effect on the script engine. /// /// public void Execute(string documentName, bool discard, string code) { Execute(new DocumentInfo(documentName) { Flags = discard ? DocumentFlags.IsTransient : DocumentFlags.None }, code); } /// /// Executes script code with the specified document information. /// /// A structure containing information about the script document. /// The script code to execute. /// /// In some script languages the distinction between statements and expressions is /// significant but ambiguous for certain syntactic elements. This method always /// interprets the specified script code as a statement. /// public void Execute(DocumentInfo documentInfo, string code) { Execute(documentInfo, code, false); } /// /// Executes script code as a command. /// /// The script command to execute. /// The command output. /// /// This method is similar to but optimized for command /// consoles. The specified command must be limited to a single expression or statement. /// Script engines can override this method to customize command execution as well as the /// process of converting the result to a string for console output. /// public virtual string ExecuteCommand(string command) { return GetCommandResultString(Evaluate(new DocumentInfo("Command") { Flags = DocumentFlags.IsTransient }, command, false)); } /// /// Evaluates script code. /// /// The script code to evaluate. /// The result value. /// /// /// In some script languages the distinction between statements and expressions is /// significant but ambiguous for certain syntactic elements. This method always /// interprets the specified script code as an expression. /// /// /// If a debugger is attached, it will present the specified script code to the user as a /// document with an automatically selected name. This document will be discarded after /// execution. /// /// /// For information about the types of result values that script code can return, see /// . /// /// public object Evaluate(string code) { return Evaluate(null, code); } /// /// Evaluates script code with an associated document name. /// /// A document name for the script code. Currently this name is used only as a label in presentation contexts such as debugger user interfaces. /// The script code to evaluate. /// The result value. /// /// /// In some script languages the distinction between statements and expressions is /// significant but ambiguous for certain syntactic elements. This method always /// interprets the specified script code as an expression. /// /// /// If a debugger is attached, it will present the specified script code to the user as a /// document with the specified name. This document will be discarded after execution. /// /// /// For information about the types of result values that script code can return, see /// . /// /// public object Evaluate(string documentName, string code) { return Evaluate(documentName, true, code); } /// /// Evaluates script code with an associated document name, optionally discarding the document after execution. /// /// A document name for the script code. Currently this name is used only as a label in presentation contexts such as debugger user interfaces. /// True to discard the script document after execution, false otherwise. /// The script code to evaluate. /// The result value. /// /// /// In some script languages the distinction between statements and expressions is /// significant but ambiguous for certain syntactic elements. This method always /// interprets the specified script code as an expression. /// /// /// If a debugger is attached, it will present the specified script code to the user as a /// document with the specified name. Discarding this document removes it from view but /// has no effect on the script engine. /// /// /// The following table summarizes the types of result values that script code can return. /// /// /// Type /// Returned As /// Remarks /// /// /// String /// System.String /// N/A /// /// /// Boolean /// System.Boolean /// N/A /// /// /// Number /// System.Int32 or System.Double /// /// Other numeric types are possible. The exact conversions between script and .NET /// numeric types are defined by the script engine. /// /// /// /// Null Reference /// null /// N/A /// /// /// Undefined /// /// /// This represents JavaScript's /// undefined, /// VBScript's /// Empty, /// etc. /// /// /// /// Void /// /// /// This is returned when script code forwards the result of a host method that returns no value. /// /// /// /// Host Object /// Native .NET type /// /// This includes all .NET types not mentioned above, including value types (enums, /// structs, etc.), and instances of all other classes. Script code can only create /// these objects by invoking a host method or constructor. They are returned to /// the host in their native .NET form. /// /// /// /// Script Object /// /// /// This includes all native script objects that have no .NET representation. C#'s /// dynamic /// keyword provides a convenient way to access them. /// /// /// /// Other /// Unspecified /// /// This includes host types and other ClearScript-specific objects intended for /// script code use only. It may also include language-specific values that the /// ClearScript library does not support. /// /// /// /// /// public object Evaluate(string documentName, bool discard, string code) { return Evaluate(new DocumentInfo(documentName) { Flags = discard ? DocumentFlags.IsTransient : DocumentFlags.None }, code); } /// /// Evaluates script code with the specified document information. /// /// A structure containing information about the script document. /// The script code to evaluate. /// The result value. /// /// /// In some script languages the distinction between statements and expressions is /// significant but ambiguous for certain syntactic elements. This method always /// interprets the specified script code as an expression. /// /// /// For information about the types of result values that script code can return, see /// . /// /// public object Evaluate(DocumentInfo documentInfo, string code) { return Evaluate(documentInfo, code, true); } /// /// Invokes a global function or procedure. /// /// The name of the global function or procedure to invoke. /// Optional invocation arguments. /// The return value if a function was invoked, an undefined value otherwise. public object Invoke(string funcName, params object[] args) { MiscHelpers.VerifyNonBlankArgument(funcName, "funcName", "Invalid function name"); return ((IDynamic)Script).InvokeMethod(funcName, args ?? ArrayHelpers.GetEmptyArray()); } /// /// Gets a string representation of the script call stack. /// /// The script call stack formatted as a string. /// /// This method returns an empty string if the script engine is not executing script code. /// The stack trace text format is defined by the script engine. /// public abstract string GetStackTrace(); /// /// Interrupts script execution and causes the script engine to throw an exception. /// /// /// This method can be called safely from any thread. /// public abstract void Interrupt(); /// /// Performs garbage collection. /// /// True to perform exhaustive garbage collection, false to favor speed over completeness. public abstract void CollectGarbage(bool exhaustive); #endregion #region internal members internal virtual bool EnumerateInstanceMethods { get { return true; } } internal virtual bool EnumerateExtensionMethods { get { return EnumerateInstanceMethods; } } internal abstract void AddHostItem(string itemName, HostItemFlags flags, object item); internal object PrepareResult(T result, ScriptMemberFlags flags, bool isListIndexResult) { return PrepareResult(result, typeof(T), flags, isListIndexResult); } internal virtual object PrepareResult(object result, Type type, ScriptMemberFlags flags, bool isListIndexResult) { var wrapNull = flags.HasFlag(ScriptMemberFlags.WrapNullResult) || EnableNullResultWrapping; if (wrapNull && (result == null)) { return HostObject.WrapResult(null, type, true); } if (!flags.HasFlag(ScriptMemberFlags.ExposeRuntimeType) && !DisableTypeRestriction && (!isListIndexResult || !DisableListIndexTypeRestriction)) { return HostObject.WrapResult(result, type, wrapNull); } return result; } internal abstract object MarshalToScript(object obj, HostItemFlags flags); internal object MarshalToScript(object obj) { var hostItem = obj as HostItem; return MarshalToScript(obj, (hostItem != null) ? hostItem.Flags : HostItemFlags.None); } internal object[] MarshalToScript(object[] args) { return args.Select(MarshalToScript).ToArray(); } internal abstract object MarshalToHost(object obj, bool preserveHostTarget); internal object[] MarshalToHost(object[] args, bool preserveHostTargets) { return args.Select(arg => MarshalToHost(arg, preserveHostTargets)).ToArray(); } internal abstract object Execute(DocumentInfo documentInfo, string code, bool evaluate); internal object Evaluate(DocumentInfo documentInfo, string code, bool marshalResult) { if (!documentInfo.Flags.HasValue) { documentInfo.Flags = DocumentFlags.IsTransient; } var result = Execute(documentInfo, code, true); if (marshalResult) { result = MarshalToHost(result, false); } return result; } internal string GetCommandResultString(object result) { var hostItem = result as HostItem; if (hostItem != null) { if (hostItem.Target is IHostVariable) { return result.ToString(); } } var marshaledResult = MarshalToHost(result, false); if (marshaledResult is VoidResult) { return null; } if (marshaledResult == null) { return "[null]"; } if (marshaledResult is Undefined) { return marshaledResult.ToString(); } if (marshaledResult is ScriptItem) { return "[ScriptObject]"; } return result.ToString(); } internal void RequestInterrupt() { // Some script engines don't support IActiveScript::InterruptScriptThread(). This // method provides an alternate mechanism based on IActiveScriptSiteInterruptPoll. var tempScriptFrame = CurrentScriptFrame; if (tempScriptFrame != null) { tempScriptFrame.InterruptRequested = true; } } internal void CheckReflection() { if (!AllowReflection) { throw new UnauthorizedAccessException("Use of reflection is prohibited in this script engine"); } } internal virtual void OnAccessSettingsChanged() { } #endregion #region host-side invocation internal virtual void HostInvoke(Action action) { action(); } internal virtual T HostInvoke(Func func) { return func(); } #endregion #region script-side invocation internal ScriptFrame CurrentScriptFrame { get; private set; } internal IDisposable CreateEngineScope() { return Scope.Create(() => MiscHelpers.Exchange(ref currentEngine, this), previousEngine => currentEngine = previousEngine); } internal virtual void ScriptInvoke(Action action) { using (CreateEngineScope()) { ScriptInvokeInternal(action); } } internal virtual T ScriptInvoke(Func func) { using (CreateEngineScope()) { return ScriptInvokeInternal(func); } } internal void ScriptInvokeInternal(Action action) { var previousScriptFrame = CurrentScriptFrame; CurrentScriptFrame = new ScriptFrame(); try { action(); } finally { CurrentScriptFrame = previousScriptFrame; } } internal T ScriptInvokeInternal(Func func) { var previousScriptFrame = CurrentScriptFrame; CurrentScriptFrame = new ScriptFrame(); try { return func(); } finally { CurrentScriptFrame = previousScriptFrame; } } internal void ThrowScriptError() { if (CurrentScriptFrame != null) { ThrowScriptError(CurrentScriptFrame.ScriptError); } } internal static void ThrowScriptError(IScriptEngineException scriptError) { if (scriptError != null) { if (scriptError is ScriptInterruptedException) { throw new ScriptInterruptedException(scriptError.EngineName, scriptError.Message, scriptError.ErrorDetails, scriptError.HResult, scriptError.IsFatal, scriptError.ExecutionStarted, scriptError.ScriptException, scriptError.InnerException); } throw new ScriptEngineException(scriptError.EngineName, scriptError.Message, scriptError.ErrorDetails, scriptError.HResult, scriptError.IsFatal, scriptError.ExecutionStarted, scriptError.ScriptException, scriptError.InnerException); } } #endregion #region synchronized invocation internal virtual void SyncInvoke(Action action) { action(); } internal virtual T SyncInvoke(Func func) { return func(); } #endregion #region enumeration settings private object enumerationSettingsToken = new object(); internal object EnumerationSettingsToken { get { return enumerationSettingsToken; } } internal void OnEnumerationSettingsChanged() { enumerationSettingsToken = new object(); } #endregion #region extension method table private readonly ExtensionMethodTable extensionMethodTable = new ExtensionMethodTable(); internal void ProcessExtensionMethodType(Type type) { if (extensionMethodTable.ProcessType(type, DefaultAccess)) { bindCache.Clear(); } } internal ExtensionMethodSummary ExtensionMethodSummary { get { return extensionMethodTable.Summary; } } internal void RebuildExtensionMethodSummary() { extensionMethodTable.RebuildSummary(); } #endregion #region bind cache private readonly Dictionary bindCache = new Dictionary(); internal void CacheBindResult(BindSignature signature, object result) { bindCache.Add(signature, result); } internal bool TryGetCachedBindResult(BindSignature signature, out object result) { return bindCache.TryGetValue(signature, out result); } #endregion #region host item cache private readonly ConditionalWeakTable> hostObjectHostItemCache = new ConditionalWeakTable>(); private readonly ConditionalWeakTable> hostTypeHostItemCache = new ConditionalWeakTable>(); internal HostItem GetOrCreateHostItem(HostTarget target, HostItemFlags flags, HostItem.CreateFunc createHostItem) { var hostObject = target as HostObject; if (hostObject != null) { return GetOrCreateHostItemForHostObject(hostObject, hostObject.Target, flags, createHostItem); } var hostType = target as HostType; if (hostType != null) { return GetOrCreateHostItemForHostType(hostType, flags, createHostItem); } var hostMethod = target as HostMethod; if (hostMethod != null) { return GetOrCreateHostItemForHostObject(hostMethod, hostMethod, flags, createHostItem); } var hostVariable = target as HostVariableBase; if (hostVariable != null) { return GetOrCreateHostItemForHostObject(hostVariable, hostVariable, flags, createHostItem); } var hostIndexedProperty = target as HostIndexedProperty; if (hostIndexedProperty != null) { return GetOrCreateHostItemForHostObject(hostIndexedProperty, hostIndexedProperty, flags, createHostItem); } return createHostItem(this, target, flags); } private HostItem GetOrCreateHostItemForHostObject(HostTarget hostTarget, object target, HostItemFlags flags, HostItem.CreateFunc createHostItem) { var cacheEntry = hostObjectHostItemCache.GetOrCreateValue(target ?? nullHostObjectProxy); List activeWeakRefs = null; var staleWeakRefCount = 0; foreach (var weakRef in cacheEntry) { var hostItem = weakRef.Target as HostItem; if (hostItem == null) { staleWeakRefCount++; } else { if ((hostItem.Target.Type == hostTarget.Type) && (hostItem.Flags == flags)) { return hostItem; } if (activeWeakRefs == null) { activeWeakRefs = new List(cacheEntry.Count); } activeWeakRefs.Add(weakRef); } } if (staleWeakRefCount > 4) { cacheEntry.Clear(); if (activeWeakRefs != null) { cacheEntry.Capacity = activeWeakRefs.Count + 1; cacheEntry.AddRange(activeWeakRefs); } } var newHostItem = createHostItem(this, hostTarget, flags); cacheEntry.Add(new WeakReference(newHostItem)); return newHostItem; } private HostItem GetOrCreateHostItemForHostType(HostType hostType, HostItemFlags flags, HostItem.CreateFunc createHostItem) { if (hostType.Types.Length != 1) { return createHostItem(this, hostType, flags); } var cacheEntry = hostTypeHostItemCache.GetOrCreateValue(hostType.Types[0]); List activeWeakRefs = null; var staleWeakRefCount = 0; foreach (var weakRef in cacheEntry) { var hostItem = weakRef.Target as HostItem; if (hostItem == null) { staleWeakRefCount++; } else { if (hostItem.Flags == flags) { return hostItem; } if (activeWeakRefs == null) { activeWeakRefs = new List(cacheEntry.Count); } activeWeakRefs.Add(weakRef); } } if (staleWeakRefCount > 4) { cacheEntry.Clear(); if (activeWeakRefs != null) { cacheEntry.Capacity = activeWeakRefs.Count + 1; cacheEntry.AddRange(activeWeakRefs); } } var newHostItem = createHostItem(this, hostType, flags); cacheEntry.Add(new WeakReference(newHostItem)); return newHostItem; } #endregion #region host item collateral internal abstract HostItemCollateral HostItemCollateral { get; } #endregion #region shared host target member data internal readonly HostTargetMemberData SharedHostMethodMemberData = new HostTargetMemberData(); internal readonly HostTargetMemberData SharedHostIndexedPropertyMemberData = new HostTargetMemberData(); internal readonly HostTargetMemberData SharedScriptMethodMemberData = new HostTargetMemberData(); private readonly ConditionalWeakTable> sharedHostObjectMemberDataCache = new ConditionalWeakTable>(); internal HostTargetMemberData GetSharedHostObjectMemberData(HostObject target, Type targetAccessContext, ScriptAccess targetDefaultAccess) { var cacheEntry = sharedHostObjectMemberDataCache.GetOrCreateValue(target.Type); List activeWeakRefs = null; var staleWeakRefCount = 0; foreach (var weakRef in cacheEntry) { var memberData = weakRef.Target as SharedHostObjectMemberData; if (memberData == null) { staleWeakRefCount++; } else { if ((memberData.AccessContext == targetAccessContext) && (memberData.DefaultAccess == targetDefaultAccess)) { return memberData; } if (activeWeakRefs == null) { activeWeakRefs = new List(cacheEntry.Count); } activeWeakRefs.Add(weakRef); } } if (staleWeakRefCount > 4) { cacheEntry.Clear(); if (activeWeakRefs != null) { cacheEntry.Capacity = activeWeakRefs.Count + 1; cacheEntry.AddRange(activeWeakRefs); } } var newMemberData = new SharedHostObjectMemberData(targetAccessContext, targetDefaultAccess); cacheEntry.Add(new WeakReference(newMemberData)); return newMemberData; } #endregion #region disposal / finalization /// /// Releases all resources used by the script engine. /// /// /// Call Dispose() when you are finished using the script engine. Dispose() /// leaves the script engine in an unusable state. After calling Dispose(), you must /// release all references to the script engine so the garbage collector can reclaim the /// memory that the script engine was occupying. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases the unmanaged resources used by the script engine and optionally releases the managed resources. /// /// True to release both managed and unmanaged resources; false to release only unmanaged resources. /// /// This method is called by the public method and the /// Finalize method. invokes the /// protected Dispose(Boolean) method with the /// parameter set to true. Finalize invokes /// Dispose(Boolean) with set to false. /// protected abstract void Dispose(bool disposing); /// /// Releases unmanaged resources and performs other cleanup operations before the script engine is reclaimed by garbage collection. /// /// /// This method overrides . Application code should not /// call this method; an object's Finalize() method is automatically invoked during /// garbage collection, unless finalization by the garbage collector has been disabled by a /// call to . /// ~ScriptEngine() { Dispose(false); } #endregion #region Nested type: ScriptFrame internal class ScriptFrame { public Exception HostException { get; set; } public IScriptEngineException ScriptError { get; set; } public IScriptEngineException PendingScriptError { get; set; } public bool InterruptRequested { get; set; } } #endregion } }