/* * Copyright 2019 faddenSoft * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Text; using CommonUtil; using PluginCommon; namespace SourceGen.Sandbox { /// /// Maintains a collection of IPlugin instances, or communicates with the remote /// PluginManager that holds the collection. Whether the plugins are instantiated /// locally depends on how the class is constructed. /// public class ScriptManager { public const string FILENAME_EXT = ".cs"; public static readonly string FILENAME_FILTER = Res.Strings.FILE_FILTER_CS; /// /// If true, the DomainManager will use the keep-alive timer hack. /// public static bool UseKeepAliveHack { get; set; } /// /// Reference to DomainManager, if we're using one. /// public DomainManager DomainMgr { get; private set; } /// /// Collection of loaded plugins, if we're not using a DomainManager. /// private Dictionary mActivePlugins; /// /// Reference to project, from which we can get the file data and project path name. /// private DisasmProject mProject; /// /// Constructor. /// public ScriptManager(DisasmProject proj) { mProject = proj; if (!proj.UseMainAppDomainForPlugins) { DomainMgr = new DomainManager(UseKeepAliveHack); DomainMgr.CreateDomain("Plugin Domain", PluginDllCache.GetPluginDirPath()); DomainMgr.PluginMgr.SetFileData(proj.FileData); } else { mActivePlugins = new Dictionary(); } } /// /// Cleans up, discarding the AppDomain if one was created. Do not continue to use /// the object after calling this. /// public void Cleanup() { if (DomainMgr != null) { DomainMgr.Dispose(); DomainMgr = null; } mActivePlugins = null; mProject = null; } /// /// Clears the list of plugins. This does not unload assemblies. Call this when /// the list of extension scripts configured into the project has changed. /// public void Clear() { if (DomainMgr == null) { mActivePlugins.Clear(); } else { DomainMgr.PluginMgr.ClearPluginList(); } } /// /// Attempts to load the specified plugin. If the plugin is already loaded, this /// does nothing. If not, the assembly is loaded and an instance is created. /// /// Script identifier. /// Report with errors and warnings. /// True on success. public bool LoadPlugin(string scriptIdent, out FileLoadReport report) { // Make sure the most recent version is compiled. string dllPath = PluginDllCache.GenerateScriptDll(scriptIdent, mProject.ProjectPathName, out report); if (dllPath == null) { return false; } if (DomainMgr == null) { if (mActivePlugins.TryGetValue(scriptIdent, out IPlugin plugin)) { return true; } Assembly asm = Assembly.LoadFile(dllPath); plugin = PluginDllCache.ConstructIPlugin(asm); mActivePlugins.Add(scriptIdent, plugin); report = new FileLoadReport(dllPath); // empty report return true; } else { IPlugin plugin = DomainMgr.PluginMgr.LoadPlugin(dllPath, scriptIdent); return plugin != null; } } public IPlugin GetInstance(string scriptIdent) { if (DomainMgr == null) { if (mActivePlugins.TryGetValue(scriptIdent, out IPlugin plugin)) { return plugin; } Debug.Assert(false); return null; } else { return DomainMgr.PluginMgr.GetPlugin(scriptIdent); } } /// /// Generates a list of references to instances of loaded plugins. /// /// Newly-created list of plugin references. public List GetAllInstances() { if (DomainMgr == null) { List list = new List(mActivePlugins.Count); foreach (KeyValuePair kvp in mActivePlugins) { list.Add(kvp.Value); } return list; } else { return DomainMgr.PluginMgr.GetActivePlugins(); } } /// /// Prepares all active scripts for action. /// /// Reference to object providing app services. public void PrepareScripts(IApplication appRef) { List platSyms = GeneratePlatSymList(); if (DomainMgr == null) { foreach (KeyValuePair kvp in mActivePlugins) { kvp.Value.Prepare(appRef, mProject.FileData, platSyms); } } else { DomainMgr.PluginMgr.PreparePlugins(appRef, platSyms); } } /// /// Gathers a list of platform symbols from the project's symbol table. /// private List GeneratePlatSymList() { List platSyms = new List(); SymbolTable symTab = mProject.SymbolTable; foreach (Symbol sym in symTab) { if (!(sym is DefSymbol)) { // ignore user labels continue; } DefSymbol defSym = sym as DefSymbol; if (defSym.SymbolSource != Symbol.Source.Platform) { // ignore project symbols continue; } platSyms.Add(new PlatSym(defSym.Label, defSym.Value, defSym.Tag)); } return platSyms; } /// /// For debugging purposes, get some information about the currently loaded /// extension scripts. /// public string DebugGetLoadedScriptInfo() { StringBuilder sb = new StringBuilder(); if (DomainMgr == null) { foreach (KeyValuePair kvp in mActivePlugins) { string loc = kvp.Value.GetType().Assembly.Location; sb.Append("[main] "); sb.Append(loc); sb.Append("\r\n "); DebugGetScriptInfo(kvp.Value, sb); } } else { List plugins = DomainMgr.PluginMgr.GetActivePlugins(); foreach (IPlugin plugin in plugins) { string loc = DomainMgr.PluginMgr.GetPluginAssemblyLocation(plugin); sb.AppendFormat("[sub {0}] ", DomainMgr.Id); sb.Append(loc); sb.Append("\r\n "); DebugGetScriptInfo(plugin, sb); } } return sb.ToString(); } private void DebugGetScriptInfo(IPlugin plugin, StringBuilder sb) { sb.Append(plugin.Identifier); sb.Append(":"); // The plugin is actually a MarshalByRefObject, so we can't use reflection // to gather the list of interfaces. // TODO(maybe): add a call that does the query on the remote site if (plugin is PluginCommon.IPlugin_InlineJsr) { sb.Append(" InlineJsr"); } if (plugin is PluginCommon.IPlugin_InlineJsl) { sb.Append(" InlineJsl"); } if (plugin is PluginCommon.IPlugin_InlineBrk) { sb.Append(" InlineBrk"); } sb.Append("\r\n"); } } }