// 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.Reflection;
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 Type accessContext;
private ScriptAccess defaultAccess;
private bool enforceAnonymousTypeAccess;
private bool exposeHostObjectStaticMembers;
private DocumentSettings documentSettings;
private readonly DocumentSettings defaultDocumentSettings = new DocumentSettings();
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.
[Obsolete("Use ScriptEngine(string name, string fileNameExtensions) instead.")]
protected ScriptEngine(string name)
: this(name, null)
{
}
///
/// Initializes a new script engine instance with the specified list of supported file name extensions.
///
/// A name to associate with the instance. Currently this name is used only as a label in presentation contexts such as debugger user interfaces.
/// A semicolon-delimited list of supported file name extensions.
protected ScriptEngine(string name, string fileNameExtensions)
{
Name = nameManager.GetUniqueName(name, GetType().GetRootName());
defaultDocumentSettings.FileNameExtensions = fileNameExtensions;
extensionMethodTable = realExtensionMethodTable = new ExtensionMethodTable();
}
#endregion
#region public members
///
/// Gets the name associated with the script engine instance.
///
public string Name { get; }
///
/// 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 => 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 => 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 => defaultAccess;
set
{
defaultAccess = value;
OnAccessSettingsChanged();
}
}
///
/// Enables or disables access restrictions for anonymous types.
///
///
/// Anonymous types are
/// internal
/// and therefore accessible only within the same assembly, but ClearScript 5.5.3 and
/// earlier permitted access to the public properties of an object even if its type was
/// internal. Newer versions strictly enforce , but because
/// anonymous types are particularly useful for scripting, ClearScript by default continues
/// to expose their properties to external contexts. To override this behavior and enable
/// normal access restrictions for anonymous types, set this property to true.
///
public bool EnforceAnonymousTypeAccess
{
get => enforceAnonymousTypeAccess;
set
{
enforceAnonymousTypeAccess = value;
OnAccessSettingsChanged();
}
}
///
/// Controls whether host objects provide access to the static members of their exposed types to script code.
///
public bool ExposeHostObjectStaticMembers
{
get => exposeHostObjectStaticMembers;
set
{
exposeHostObjectStaticMembers = value;
OnAccessSettingsChanged();
}
}
///
/// Enables or disables extension method support.
///
public bool DisableExtensionMethods
{
get => extensionMethodTable == emptyExtensionMethodTable;
set
{
var newExtensionMethodTable = value ? emptyExtensionMethodTable : realExtensionMethodTable;
if (newExtensionMethodTable != extensionMethodTable)
{
ScriptInvoke(() =>
{
if (newExtensionMethodTable != extensionMethodTable)
{
extensionMethodTable = newExtensionMethodTable;
bindCache.Clear();
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 floating point narrowing.
///
///
/// When this property is set to true, no attempt is made to convert floating-point
/// values imported from the script engine to the narrowest equivalent .NET representation.
/// The default behavior is more likely to result in successful method binding in specific
/// scenarios, so setting this property to true is not recommended.
///
public bool DisableFloatNarrowing { 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 the engine's undefined import value.
///
///
/// Some script languages support one or more special non-null values that represent
/// nonexistent, missing, unknown, or undefined data. When such a value is marshaled to the
/// host, the script engine maps it to the value of this property. The default value is
/// .
///
public object UndefinedImportValue { get; set; } = Undefined.Value;
///
/// 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 dynamically.
///
///
/// 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; }
///
/// Allows the host to access script resources.
///
///
/// The value of this property is an object that is bound to the script engine's root
/// namespace. It allows you to access global script resources via the
/// class interface. Doing so is likely to perform better than
/// dynamic access via .
///
public abstract ScriptObject Global { get; }
///
/// Gets or sets the script engine's document settings.
///
public DocumentSettings DocumentSettings
{
get => documentSettings ?? defaultDocumentSettings;
set => documentSettings = value;
}
///
/// 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, nameof(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, nameof(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));
}
///
/// Exposes host types to script code.
///
/// The types to expose.
///
///
/// This method uses each specified type'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 AddHostTypes(params Type[] types)
{
if (types != null)
{
foreach (var type in types)
{
if (type != null)
{
AddHostType(type);
}
}
}
}
///
/// 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. Only Windows Script engines honor
/// .
///
///
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 meta-information.
///
/// A structure containing meta-information for 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.MakeUnique(this), code, false);
}
///
/// Loads and executes a script document.
///
/// A string specifying the document to be loaded and executed.
///
/// In some script languages the distinction between statements and expressions is
/// significant but ambiguous for certain syntactic elements. This method always
/// interprets script code loaded from the specified document as a statement.
///
public void ExecuteDocument(string specifier)
{
ExecuteDocument(specifier, null);
}
///
/// Loads and executes a document with the specified category.
///
/// A string specifying the document to be loaded and executed.
/// An optional category for the requested document.
///
/// In some script languages the distinction between statements and expressions is
/// significant but ambiguous for certain syntactic elements. This method always
/// interprets script code loaded from the specified document as a statement.
///
public void ExecuteDocument(string specifier, DocumentCategory category)
{
ExecuteDocument(specifier, category, null);
}
///
/// Loads and executes a document with the specified category and context callback.
///
/// A string specifying the document to be loaded and executed.
/// An optional category for the requested document.
/// An optional context callback for the requested document.
///
/// In some script languages the distinction between statements and expressions is
/// significant but ambiguous for certain syntactic elements. This method always
/// interprets script code loaded from the specified document as a statement.
///
public void ExecuteDocument(string specifier, DocumentCategory category, DocumentContextCallback contextCallback)
{
MiscHelpers.VerifyNonBlankArgument(specifier, nameof(specifier), "Invalid document specifier");
var document = DocumentSettings.LoadDocument(null, specifier, category, contextCallback);
Execute(document.Info, document.GetTextContents());
}
///
/// 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)
{
var documentInfo = new DocumentInfo("Command") { Flags = DocumentFlags.IsTransient };
return GetCommandResultString(Evaluate(documentInfo.MakeUnique(this), 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. Only Windows Script engines honor
/// .
///
///
/// 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 meta-information.
///
/// A structure containing meta-information for 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.MakeUnique(this, DocumentFlags.IsTransient), code, true);
}
///
/// Loads and evaluates a script document.
///
/// A string specifying the document to be loaded and evaluated.
/// 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 script code loaded from the specified document as an expression.
///
///
/// For information about the types of result values that script code can return, see
/// .
///
///
public object EvaluateDocument(string specifier)
{
return EvaluateDocument(specifier, null);
}
///
/// Loads and evaluates a document with the specified category.
///
/// A string specifying the document to be loaded and evaluated.
/// An optional category for the requested document.
/// 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 script code loaded from the specified document as an expression.
///
///
/// For information about the types of result values that script code can return, see
/// .
///
///
public object EvaluateDocument(string specifier, DocumentCategory category)
{
return EvaluateDocument(specifier, category, null);
}
///
/// Loads and evaluates a document with the specified category and context callback.
///
/// A string specifying the document to be loaded and evaluated.
/// An optional category for the requested document.
/// An optional context callback for the requested document.
/// 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 script code loaded from the specified document as an expression.
///
///
/// For information about the types of result values that script code can return, see
/// .
///
///
public object EvaluateDocument(string specifier, DocumentCategory category, DocumentContextCallback contextCallback)
{
MiscHelpers.VerifyNonBlankArgument(specifier, nameof(specifier), "Invalid document specifier");
var document = DocumentSettings.LoadDocument(null, specifier, category, contextCallback);
return Evaluate(document.Info, document.GetTextContents());
}
///
/// 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, nameof(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 abstract IUniqueNameManager DocumentNameManager { get; }
internal virtual bool EnumerateInstanceMethods => true;
internal virtual bool EnumerateExtensionMethods => 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?.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(UniqueDocumentInfo documentInfo, string code, bool evaluate);
internal abstract object ExecuteRaw(UniqueDocumentInfo documentInfo, string code, bool evaluate);
internal object Evaluate(UniqueDocumentInfo documentInfo, string code, bool marshalResult)
{
var result = Execute(documentInfo, code, true);
if (marshalResult)
{
result = MarshalToHost(result, false);
}
return result;
}
internal string GetCommandResultString(object result)
{
if (result is HostItem hostItem)
{
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
internal object EnumerationSettingsToken { get; private set; } = new object();
internal void OnEnumerationSettingsChanged()
{
EnumerationSettingsToken = new object();
}
#endregion
#region extension method table
private static readonly ExtensionMethodTable emptyExtensionMethodTable = new ExtensionMethodTable();
private readonly ExtensionMethodTable realExtensionMethodTable;
private ExtensionMethodTable extensionMethodTable;
internal void ProcessExtensionMethodType(Type type)
{
if (extensionMethodTable != emptyExtensionMethodTable)
{
if (extensionMethodTable.ProcessType(type, AccessContext, DefaultAccess))
{
bindCache.Clear();
}
}
}
internal ExtensionMethodSummary ExtensionMethodSummary => extensionMethodTable.Summary;
internal void RebuildExtensionMethodSummary()
{
if (extensionMethodTable != emptyExtensionMethodTable)
{
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)
{
if (target is HostObject hostObject)
{
return GetOrCreateHostItemForHostObject(hostObject, hostObject.Target, flags, createHostItem);
}
if (target is HostType hostType)
{
return GetOrCreateHostItemForHostType(hostType, flags, createHostItem);
}
if (target is HostMethod hostMethod)
{
return GetOrCreateHostItemForHostObject(hostMethod, hostMethod, flags, createHostItem);
}
if (target is HostVariableBase hostVariable)
{
return GetOrCreateHostItemForHostObject(hostVariable, hostVariable, flags, createHostItem);
}
if (target is HostIndexedProperty hostIndexedProperty)
{
return GetOrCreateHostItemForHostObject(hostIndexedProperty, hostIndexedProperty, flags, createHostItem);
}
return CreateHostItem(target, flags, createHostItem, null);
}
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);
}
}
return CreateHostItem(hostTarget, flags, createHostItem, cacheEntry);
}
private HostItem GetOrCreateHostItemForHostType(HostType hostType, HostItemFlags flags, HostItem.CreateFunc createHostItem)
{
if (hostType.Types.Length != 1)
{
return CreateHostItem(hostType, flags, createHostItem, null);
}
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);
}
}
return CreateHostItem(hostType, flags, createHostItem, cacheEntry);
}
private HostItem CreateHostItem(HostTarget hostTarget, HostItemFlags flags, HostItem.CreateFunc createHostItem, List cacheEntry)
{
var newHostItem = createHostItem(this, hostTarget, flags);
if (cacheEntry != null)
{
cacheEntry.Add(new WeakReference(newHostItem));
}
if (hostTarget.Target is IScriptableObject scriptableObject)
{
scriptableObject.OnExposedToScriptCode(this);
}
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, HostTargetFlags targetFlags)
{
var cacheEntry = sharedHostObjectMemberDataCache.GetOrCreateValue(target.Type);
List activeWeakRefs = null;
var staleWeakRefCount = 0;
foreach (var weakRef in cacheEntry)
{
var memberData = weakRef.Target as HostTargetMemberDataWithContext;
if (memberData == null)
{
staleWeakRefCount++;
}
else
{
if ((memberData.AccessContext == targetAccessContext) && (memberData.DefaultAccess == targetDefaultAccess) && (memberData.TargetFlags == targetFlags))
{
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 HostTargetMemberDataWithContext(targetAccessContext, targetDefaultAccess, targetFlags);
cacheEntry.Add(new WeakReference(newMemberData));
return newMemberData;
}
#endregion
#region event connections
private readonly EventConnectionMap eventConnectionMap = new EventConnectionMap();
internal EventConnection CreateEventConnection(object source, EventInfo eventInfo, Delegate handler)
{
return eventConnectionMap.Create(this, source, eventInfo, handler);
}
internal void BreakEventConnection(IEventConnection connection)
{
eventConnectionMap.Break(connection);
}
private void BreakAllEventConnections()
{
eventConnectionMap.Dispose();
}
#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 virtual void Dispose(bool disposing)
{
if (disposing)
{
BreakAllEventConnections();
}
}
///
/// 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 sealed class ScriptFrame
{
public Exception HostException { get; set; }
public IScriptEngineException ScriptError { get; set; }
public IScriptEngineException PendingScriptError { get; set; }
public bool InterruptRequested { get; set; }
}
#endregion
#region Nested type: EventConnectionMap
private sealed class EventConnectionMap : IDisposable
{
private readonly HashSet map = new HashSet();
private readonly InterlockedOneWayFlag disposedFlag = new InterlockedOneWayFlag();
internal EventConnection Create(ScriptEngine engine, object source, EventInfo eventInfo, Delegate handler)
{
var connection = new EventConnection(engine, source, eventInfo, handler);
if (!disposedFlag.IsSet)
{
lock (map)
{
map.Add(connection);
}
}
return connection;
}
internal void Break(IEventConnection connection)
{
var mustBreak = true;
if (!disposedFlag.IsSet)
{
lock (map)
{
mustBreak = map.Remove(connection);
}
}
if (mustBreak)
{
connection.Break();
}
}
public void Dispose()
{
if (disposedFlag.Set())
{
var connections = new List();
lock (map)
{
connections.AddRange(map);
}
connections.ForEach(connection => connection.Break());
}
}
}
#endregion
}
}