Files
yallie 9fa3874800 Added the contents of the archive.
Moved the original file to the archive subfolder.
2017-11-20 16:11:42 +03:00

760 lines
24 KiB
C#

// ==++==
//
//
// Copyright (c) 2002 Microsoft Corporation. All rights reserved.
//
// The use and distribution terms for this software are contained in the file
// named license.txt, which can be found in the root of this distribution.
// By using this software in any fashion, you are agreeing to be bound by the
// terms of this license.
//
// You must not remove this notice, or any other, from this software.
//
//
// ==--==
namespace Microsoft.Vsa
{
using System;
using System.Collections;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Security.Permissions;
using System.Threading;
public abstract class BaseVsaEngine : IVsaEngine{
// === Members ===
protected string applicationPath;
protected System.Reflection.Assembly loadedAssembly;
protected string compiledRootNamespace;
protected IVsaSite engineSite;
protected bool genDebugInfo;
protected bool haveCompiledState; // true if there is compiled state in the engine
protected bool failedCompilation; // true if IVsaEngine.Compile returned false on the most recent call (false by default)
protected bool isClosed;
protected bool isEngineCompiled; // true if current compiled state reflects current source state
protected bool isDebugInfoSupported;
protected bool isEngineDirty; // true if current source state does not reflect the persisted source state
protected bool isEngineInitialized;
protected bool isEngineRunning;
protected IVsaItems vsaItems;
protected string scriptLanguage;
protected int errorLocale;
static protected Hashtable nameTable = new Hashtable(10);
protected string engineName;
protected string engineMoniker;
protected string rootNamespace;
protected Type startupClass;
protected BaseVsaStartup startupInstance;
protected string assemblyVersion;
protected System.Security.Policy.Evidence executionEvidence;
// === Constructor ===
public BaseVsaEngine(string language, string version, bool supportDebug){
// Set default property values and initial state
this.applicationPath = "";
this.compiledRootNamespace = null;
this.genDebugInfo = false;
this.haveCompiledState = false;
this.failedCompilation = false;
this.isClosed = false;
this.isEngineCompiled = false;
this.isEngineDirty = false;
this.isEngineInitialized = false;
this.isEngineRunning = false;
this.vsaItems = null;
this.engineSite = null;
this.errorLocale = CultureInfo.CurrentCulture.LCID;
this.engineName = "";
this.rootNamespace = "";
this.engineMoniker = "";
// Set implementation-dependent values
this.scriptLanguage = language;
this.assemblyVersion = version;
this.isDebugInfoSupported = supportDebug;
this.executionEvidence = null;
}
// === Helper Methods ===
protected VsaException Error(VsaError vsaErrorNumber){
return new VsaException(vsaErrorNumber);
}
internal void TryObtainLock() {
if (!Monitor.TryEnter(this))
throw new VsaException(VsaError.EngineBusy);
}
internal void ReleaseLock() {
Monitor.Exit(this);
}
// Pre is a set of preconditions that must hold in order to perform certain
// operations on or with the engine
protected enum Pre{
None = 0x0000,
EngineNotClosed = 0x0001, // optional; tested by default
SupportForDebug = 0x0002,
EngineCompiled = 0x0004,
EngineRunning = 0x0008,
EngineNotRunning = 0x0010,
RootMonikerSet = 0x0020,
RootMonikerNotSet = 0x0040,
RootNamespaceSet = 0x0080,
SiteSet = 0x0100,
SiteNotSet = 0x0200,
EngineInitialised = 0x0400,
EngineNotInitialised = 0x0800,
}
private bool IsCondition(Pre flag, Pre test){
return ((flag & test) != Pre.None);
}
// The Preconditions method tests a set of preconditions and throws the
// appropriate VsaException if any of them do not hold.
// Pre.EngineNotClosed is always tested, regardless of whether or not it
// appears in the flags bitfield (including when only Pre.None is indicated).
protected void Preconditions(Pre flags){
// Every operation on this object requires that the engine not be closed
if (this.isClosed)
throw Error(VsaError.EngineClosed);
if (flags == (Pre.EngineNotClosed | Pre.None))
return;
if (IsCondition(flags, Pre.SupportForDebug) && !this.isDebugInfoSupported)
throw Error(VsaError.DebugInfoNotSupported);
if (IsCondition(flags, Pre.EngineCompiled) && !this.haveCompiledState)
throw Error(VsaError.EngineNotCompiled);
if (IsCondition(flags, Pre.EngineRunning) && !this.isEngineRunning)
throw Error(VsaError.EngineNotRunning);
if (IsCondition(flags, Pre.EngineNotRunning) && this.isEngineRunning)
throw Error(VsaError.EngineRunning);
if (IsCondition(flags, Pre.RootMonikerSet) && (this.engineMoniker == ""))
throw Error(VsaError.RootMonikerNotSet);
if (IsCondition(flags, Pre.RootMonikerNotSet) && (this.engineMoniker != ""))
throw Error(VsaError.RootMonikerAlreadySet);
if (IsCondition(flags, Pre.RootNamespaceSet) && (this.rootNamespace == ""))
throw Error(VsaError.RootNamespaceNotSet);
if (IsCondition(flags, Pre.SiteSet) && (this.engineSite == null))
throw Error(VsaError.SiteNotSet);
if (IsCondition(flags, Pre.SiteNotSet) && (this.engineSite != null))
throw Error(VsaError.SiteAlreadySet);
if (IsCondition(flags, Pre.EngineInitialised) && !this.isEngineInitialized)
throw Error(VsaError.EngineNotInitialized);
if (IsCondition(flags, Pre.EngineNotInitialised) && this.isEngineInitialized)
throw Error(VsaError.EngineInitialized);
}
// === Properties ===
// IVsaEngine.AppDomain is not supported by managed engines
public System._AppDomain AppDomain{
get{
Preconditions(Pre.EngineNotClosed);
throw new NotSupportedException();
}
set{
Preconditions(Pre.EngineNotClosed);
throw new VsaException(VsaError.AppDomainCannotBeSet);
}
}
public System.Security.Policy.Evidence Evidence{
get{
Preconditions(Pre.EngineNotClosed | Pre.EngineInitialised);
return this.executionEvidence;
}
set{
Preconditions(Pre.EngineNotClosed |
Pre.EngineNotRunning |
Pre.EngineInitialised);
this.executionEvidence = value;
}
}
// IVsaEngine.ApplicationBase is not supported by managed engines
public string ApplicationBase{
get{
Preconditions(Pre.EngineNotClosed);
throw new NotSupportedException();
}
set{
Preconditions(Pre.EngineNotClosed);
throw new VsaException(VsaError.ApplicationBaseCannotBeSet);
}
}
public System.Reflection.Assembly Assembly{
get{
Preconditions(Pre.EngineNotClosed | Pre.EngineRunning);
return this.loadedAssembly;
}
}
public bool GenerateDebugInfo{
get{
Preconditions(Pre.EngineNotClosed | Pre.EngineInitialised);
return this.genDebugInfo;
}
set{
this.TryObtainLock();
try{
Preconditions(Pre.EngineNotClosed |
Pre.EngineNotRunning |
Pre.EngineInitialised |
Pre.SupportForDebug);
if (this.genDebugInfo != value){
this.genDebugInfo = value;
this.isEngineDirty = true;
this.isEngineCompiled = false;
}
}finally{
this.ReleaseLock();
}
}
}
public bool IsCompiled{
get{
Preconditions(Pre.EngineNotClosed | Pre.EngineInitialised);
return this.isEngineCompiled;
}
}
public bool IsDirty{
get{
Preconditions(Pre.EngineNotClosed | Pre.EngineInitialised);
return this.isEngineDirty;
}
// IsDirty setter is used by VsaItems to notify the engine when new items have been
// manipulated or added to/removed from the engine. It is not part of an interface.
set{
this.TryObtainLock();
try{
Preconditions(Pre.EngineNotClosed);
this.isEngineDirty = value;
if (this.isEngineDirty)
this.isEngineCompiled = false;
}finally{
this.ReleaseLock();
}
}
}
public bool IsRunning{
get{
Preconditions(Pre.EngineNotClosed | Pre.EngineInitialised);
return this.isEngineRunning;
}
}
public IVsaItems Items{
get{
Preconditions(Pre.EngineNotClosed | Pre.EngineInitialised);
return this.vsaItems;
}
}
public string Language{
get{
Preconditions(Pre.EngineNotClosed | Pre.EngineInitialised);
return this.scriptLanguage;
}
}
public int LCID{
get{
Preconditions(Pre.EngineNotClosed | Pre.EngineInitialised);
return this.errorLocale;
}
set{
this.TryObtainLock();
try{
Preconditions(Pre.EngineNotClosed | Pre.EngineInitialised | Pre.EngineNotRunning);
try{
CultureInfo errorCultureInfo = new CultureInfo(value);
}catch(ArgumentException){
throw Error(VsaError.LCIDNotSupported);
}
this.errorLocale = value;
this.isEngineDirty = true;
}finally{
this.ReleaseLock();
}
}
}
public string Name{
get{
Preconditions(Pre.EngineNotClosed | Pre.EngineInitialised);
return this.engineName;
}
set{
this.TryObtainLock();
try{
Preconditions(Pre.EngineNotClosed | Pre.EngineNotRunning | Pre.EngineInitialised);
// if the new name is the same as the old one, do nothing
if (this.engineName == value) return;
// put the name into a static table so that we can detect duplicates
lock(nameTable){
if (nameTable[value] != null)
throw Error(VsaError.EngineNameInUse);
nameTable[value] = new Object();
if (this.engineName != null && this.engineName.Length > 0)
nameTable[this.engineName] = null;
}
this.engineName = value;
this.isEngineDirty = true;
this.isEngineCompiled = false;
}finally{
this.ReleaseLock();
}
}
}
public string RootMoniker{
get{
Preconditions(Pre.EngineNotClosed);
return this.engineMoniker;
}
set{
this.TryObtainLock();
try{
Preconditions(Pre.EngineNotClosed | Pre.RootMonikerNotSet);
this.ValidateRootMoniker(value);
this.engineMoniker = value;
}finally{
this.ReleaseLock();
}
}
}
public string RootNamespace{
get{
Preconditions(Pre.EngineNotClosed | Pre.EngineInitialised);
return this.rootNamespace;
}
set{
this.TryObtainLock();
try{
Preconditions(Pre.EngineNotClosed | Pre.EngineNotRunning | Pre.EngineInitialised);
if (!IsValidNamespaceName(value))
throw Error(VsaError.RootNamespaceInvalid);
this.rootNamespace = value;
this.isEngineDirty = true;
this.isEngineCompiled = false;
}finally{
this.ReleaseLock();
}
}
}
public IVsaSite Site{
get{
Preconditions(Pre.EngineNotClosed | Pre.RootMonikerSet);
return this.engineSite;
}
set{
this.TryObtainLock();
try{
Preconditions(Pre.EngineNotClosed | Pre.RootMonikerSet | Pre.SiteNotSet);
if (value == null)
throw Error(VsaError.SiteInvalid);
this.engineSite = value;
}finally{
this.ReleaseLock();
}
}
}
public string Version{
get{
Preconditions(Pre.EngineNotClosed | Pre.EngineInitialised);
return this.assemblyVersion;
}
}
// === Methods ===
public virtual void Close(){
this.TryObtainLock();
try{
Preconditions(Pre.EngineNotClosed);
if (this.isEngineRunning)
this.Reset();
lock(nameTable){
if (this.engineName != null && this.engineName.Length > 0)
nameTable[this.engineName] = null;
}
this.DoClose();
this.isClosed = true;
}finally{
this.ReleaseLock();
}
}
public virtual bool Compile(){
this.TryObtainLock();
try{
Preconditions(Pre.EngineNotClosed |
Pre.EngineNotRunning |
Pre.EngineInitialised |
Pre.RootNamespaceSet);
// look through items for at least one code item
bool haveCode = false;
for (int i = 0, n = this.vsaItems.Count; !haveCode && i < n; ++i){
IVsaItem item = vsaItems[i];
haveCode = this.vsaItems[i].ItemType == VsaItemType.Code;
}
if (!haveCode)
throw Error(VsaError.EngineEmpty);
try{
this.ResetCompiledState();
this.isEngineCompiled = DoCompile();
}catch(VsaException){
throw;
}catch(Exception e){
throw new VsaException(VsaError.InternalCompilerError, e.ToString(), e);
}
if (this.isEngineCompiled){
this.haveCompiledState = true;
this.failedCompilation = false;
this.compiledRootNamespace = this.rootNamespace;
}
return this.isEngineCompiled;
}finally{
this.ReleaseLock();
}
}
public virtual object GetOption(string name){
this.TryObtainLock();
try{
Preconditions(Pre.EngineNotClosed | Pre.EngineInitialised);
object option = GetCustomOption(name);
return option;
}finally{
this.ReleaseLock();
}
}
public virtual void InitNew(){
this.TryObtainLock();
try{
Preconditions(Pre.EngineNotClosed |
Pre.EngineNotInitialised |
Pre.RootMonikerSet |
Pre.SiteSet);
this.isEngineInitialized = true;
}finally{
this.ReleaseLock();
}
}
// Load the local compiled state into the AppDomain and return the loaded assembly
protected virtual Assembly LoadCompiledState(){
Debug.Assert(this.haveCompiledState);
byte[] pe;
byte[] pdb;
this.DoSaveCompiledState(out pe, out pdb);
return Assembly.Load(pe, pdb, this.executionEvidence);
}
public virtual void LoadSourceState(IVsaPersistSite site){
this.TryObtainLock();
try{
Preconditions(Pre.EngineNotClosed |
Pre.EngineNotInitialised |
Pre.RootMonikerSet |
Pre.SiteSet);
this.isEngineInitialized = true;
try{
this.DoLoadSourceState(site);
}catch{
this.isEngineInitialized = false;
throw;
}
this.isEngineDirty = false;
}finally{
this.ReleaseLock();
}
}
public virtual void Reset(){
this.TryObtainLock();
try{
Preconditions(Pre.EngineNotClosed | Pre.EngineRunning);
try{
this.startupInstance.Shutdown();
}catch(Exception e){
throw new VsaException(VsaError.EngineCannotReset, e.ToString(), e);
}
this.isEngineRunning = false;
this.loadedAssembly = null;
}finally{
this.ReleaseLock();
}
}
public virtual void RevokeCache(){
this.TryObtainLock();
try{
Preconditions(Pre.EngineNotClosed | Pre.EngineNotRunning | Pre.RootMonikerSet);
try{
System.AppDomain.CurrentDomain.SetData(this.engineMoniker, null);
}catch(Exception e){
throw new VsaException(VsaError.RevokeFailed, e.ToString(), e);
}
}finally{
this.ReleaseLock();
}
}
public virtual void Run(){
this.TryObtainLock();
try{
Preconditions(Pre.EngineNotClosed |
Pre.EngineNotRunning |
Pre.RootMonikerSet |
Pre.SiteSet |
Pre.RootNamespaceSet);
// managed engines cannot support the AppDomain property so we always use the current one
AppDomain appDomain = System.AppDomain.CurrentDomain;
if (this.haveCompiledState){
// make sure the RootNamespace hasn't been changed since the compilation
if (this.rootNamespace != this.compiledRootNamespace)
throw new VsaException(VsaError.RootNamespaceInvalid);
this.loadedAssembly = this.LoadCompiledState();
appDomain.SetData(this.engineMoniker, this.loadedAssembly);
}else{
// if Compile returned false last time it was called, quit before cache lookup or callback
if (this.failedCompilation)
throw new VsaException(VsaError.EngineNotCompiled);
this.startupClass = null;
this.loadedAssembly = appDomain.GetData(this.engineMoniker) as Assembly;
if (this.loadedAssembly == null){
// assembly is not in the cache, so callback to the site for compiled state
// prevent multiple callbacks which could result in duplicate assemblies being loaded into the AppDomain
string mutexName = this.engineMoniker + "/" + appDomain.GetHashCode().ToString(CultureInfo.InvariantCulture);
Mutex compiledStateCallbackMutex = new Mutex(false, mutexName);
if (compiledStateCallbackMutex.WaitOne()){
try{
// see if there was another callback before we got the lock
this.loadedAssembly = appDomain.GetData(this.engineMoniker) as Assembly;
if (this.loadedAssembly == null){
byte[] pe;
byte[] pdb;
this.engineSite.GetCompiledState(out pe, out pdb);
if (pe == null)
throw new VsaException(VsaError.GetCompiledStateFailed);
this.loadedAssembly = Assembly.Load(pe, pdb, this.executionEvidence);
appDomain.SetData(this.engineMoniker, this.loadedAssembly);
}
}finally{
compiledStateCallbackMutex.ReleaseMutex();
compiledStateCallbackMutex.Close();
}
}
}
}
// update the _Startup class Type object (if needed)
try{
if (this.startupClass == null)
this.startupClass = this.loadedAssembly.GetType(this.rootNamespace + "._Startup", true);
}catch(Exception e){
// the _Startup class was not in the RootNamespace
throw new VsaException(VsaError.BadAssembly, e.ToString(), e);
}
// set the engine to running, callback for global and event instances, then run global code
try{
this.startupInstance = (BaseVsaStartup)Activator.CreateInstance(this.startupClass);
this.isEngineRunning = true;
this.startupInstance.SetSite(this.engineSite);
this.startupInstance.Startup();
}catch(Exception e){
throw new VsaException(VsaError.UnknownError, e.ToString(), e);
}
}finally{
this.ReleaseLock();
}
}
public virtual void SetOption(string name, object value){
this.TryObtainLock();
try{
Preconditions(Pre.EngineNotClosed |
Pre.EngineNotRunning |
Pre.EngineInitialised);
this.SetCustomOption(name, value);
}finally{
this.ReleaseLock();
}
}
public virtual void SaveCompiledState(out byte[] pe, out byte[] debugInfo){
this.TryObtainLock();
try{
Preconditions(Pre.EngineNotClosed |
Pre.EngineNotRunning |
Pre.EngineCompiled |
Pre.EngineInitialised);
this.DoSaveCompiledState(out pe, out debugInfo);
}finally{
this.ReleaseLock();
}
}
public virtual void SaveSourceState(IVsaPersistSite site){
this.TryObtainLock();
try{
Preconditions(Pre.EngineNotClosed | Pre.EngineNotRunning | Pre.EngineInitialised);
if (site == null)
throw Error(VsaError.SiteInvalid);
try{
DoSaveSourceState(site);
}catch(Exception e){
throw new VsaException(VsaError.SaveElementFailed, e.ToString(), e);
}
}finally{
this.ReleaseLock();
}
}
protected virtual void ValidateRootMoniker(string rootMoniker){
// Refer to RFC 2396 for details: ftp://nis.nsf.net/internet/documents/rfc/rfc2396.txt
if (rootMoniker == null)
throw new VsaException(VsaError.RootMonikerInvalid);
Uri moniker = null;
try{
moniker = new Uri(rootMoniker);
}catch(UriFormatException){
throw new VsaException(VsaError.RootMonikerInvalid);
}
// The overall format is okay, so now we validate the protocol
string protocol = moniker.Scheme;
if (protocol.Length == 0)
throw new VsaException(VsaError.RootMonikerProtocolInvalid);
string[] knownSchemes = {
"file",
"ftp",
"gopher",
"http",
"https",
"javascript",
"mailto",
"microsoft",
"news",
"res",
"smtp",
"socks",
"vbscript",
"xlang",
"xml",
"xpath",
"xsd",
"xsl",
};
foreach (string scheme in knownSchemes){
// Scheme names are case-insensitive (RFC 2396 section 3.2)
if (String.Compare(scheme, protocol, true, CultureInfo.InvariantCulture) == 0)
throw new VsaException(VsaError.RootMonikerProtocolInvalid);
}
}
// === Abstract Methods ===
// Implement this method to clean up data structures
protected abstract void DoClose();
// Return flag for compilation success.
// Errors should be reported to the engine site via OnCompilerError.
// Internal compiler errors should throw exceptions.
protected abstract bool DoCompile();
// Implement this method to load source state from site
protected abstract void DoLoadSourceState(IVsaPersistSite site);
protected abstract void DoSaveCompiledState(out byte[] pe, out byte[] debugInfo);
// Save source items using IVsaPersistSite.SaveElement
protected abstract void DoSaveSourceState(IVsaPersistSite site);
// Enable custom options
protected abstract object GetCustomOption(string name);
// Verify that the name is a valid namespace name
protected abstract bool IsValidNamespaceName(string name);
// Verify that the identifier is valid for the language
public abstract bool IsValidIdentifier(string ident);
// Discard the existing compiled state so we can compile a new source state
// Postconditions:
// this.haveCompiledState == false
// this.failedCompilation == true
// this.compiledRootNamespace == null
// this.startupClass == null
protected abstract void ResetCompiledState();
// Enable custom options
protected abstract void SetCustomOption(string name, object value);
}
// BaseVsaSite is a default implementation of IVsaSite. Note that GetCompiledState
// defers to two properties so that languages that don't support declaring out params
// can provide an implementation without having to change languages.
public class BaseVsaSite: IVsaSite{
public virtual byte[] Assembly{
get{
throw new VsaException(VsaError.GetCompiledStateFailed);
}
}
public virtual byte[] DebugInfo{
get{
return null;
}
}
public virtual void GetCompiledState(out byte[] pe, out byte[] debugInfo){
pe = this.Assembly;
debugInfo = this.DebugInfo;
}
public virtual object GetEventSourceInstance( string itemName, string eventSourceName){
throw new VsaException(VsaError.CallbackUnexpected);
}
public virtual object GetGlobalInstance(string name){
throw new VsaException(VsaError.CallbackUnexpected);
}
public virtual void Notify(string notify, object optional){
throw new VsaException(VsaError.CallbackUnexpected);
}
public virtual bool OnCompilerError(IVsaError error){
return false;
}
}
public abstract class BaseVsaStartup{
protected IVsaSite site;
public void SetSite(IVsaSite site){
this.site = site;
}
public abstract void Startup();
public abstract void Shutdown();
}
}