/* * Copyright 2018 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.Linq; using System.Reflection; using System.Threading; using CommonUtil; namespace PluginCommon { /// /// Manages loaded plugins, in the "remote" AppDomain. /// public sealed class PluginManager : MarshalByRefObject { /// /// Collection of instances of active plugins, keyed by script identifier. Other /// plugin assemblies may be present in the AppDomain, but have not been identified /// by the application as being of interest. /// private Dictionary mActivePlugins = new Dictionary(); /// /// Reference to file data. /// private byte[] mFileData; private DateTime mLastPing; /// /// Constructor, invoked from CreateInstanceAndUnwrap(). /// public PluginManager() { Debug.WriteLine("PluginManager ctor (id=" + AppDomain.CurrentDomain.Id + ")"); mLastPing = DateTime.Now; // Seems to require [SecurityCritical] //Type lsc = Type.GetType("System.Runtime.Remoting.Lifetime.LifetimeServices"); //PropertyInfo prop = lsc.GetProperty("LeaseTime"); //prop.SetValue(null, TimeSpan.FromSeconds(30)); } ~PluginManager() { Debug.WriteLine("~PluginManager (id=" + AppDomain.CurrentDomain.Id + ")"); } /// /// Sets the file data to use for all plugins. /// /// The file data argument will be an AppDomain-local copy of the data, made by the /// argument marshalling code. So plugins can scribble all over it without trashing /// the original. We want to store it in PluginManager so we don't make a new copy /// for each individual plugin. /// /// 65xx code and data. public void SetFileData(byte[] fileData) { mFileData = fileData; } /// /// Tests simple round-trip communication. This may be called from an arbitrary thread. /// /// /// This is used for keep-alives and health checks, so it may be called frequently. /// public int Ping(int val) { //Debug.WriteLine("PluginManager Ping tid=" + Thread.CurrentThread.ManagedThreadId + // " (id=" + AppDomain.CurrentDomain.Id + "): " + val); int result = (int)(DateTime.Now - mLastPing).TotalSeconds; mLastPing = DateTime.Now; return result; } /// /// Creates a plugin instance from a compiled assembly. Pass in the script identifier /// for future lookups. If the plugin has already been instantiated, that object /// will be returned. /// /// Full path to compiled assembly. /// Identifier to use in e.g. GetPlugin(). /// Reference to plugin instance. public IPlugin LoadPlugin(string dllPath, string scriptIdent, out string failMsg) { if (mActivePlugins.TryGetValue(dllPath, out IPlugin ip)) { Debug.WriteLine("PM: returning cached plugin for " + dllPath); failMsg = string.Empty; return ip; } Assembly asm = Assembly.LoadFile(dllPath); foreach (Type type in asm.GetExportedTypes()) { // Using a System.Linq extension method. if (type.IsClass && !type.IsAbstract && type.GetInterfaces().Contains(typeof(IPlugin))) { ConstructorInfo ctor = type.GetConstructor(Type.EmptyTypes); IPlugin iplugin; try { iplugin = (IPlugin)ctor.Invoke(null); } catch (Exception ex) { if (ex.InnerException != null) { failMsg = ex.InnerException.Message; } else { failMsg = ex.Message; } Debug.WriteLine("LoadPlugin: failed to load '" + scriptIdent + "': " + failMsg); return null; } Debug.WriteLine("PM created instance: " + iplugin); mActivePlugins.Add(scriptIdent, iplugin); failMsg = string.Empty; return iplugin; } } throw new Exception("No IPlugin class found in " + dllPath); } /// /// Gets an instance of a previously-loaded plugin. /// /// Script identifier that was passed to LoadPlugin(). /// Reference to instance of plugin. public IPlugin GetPlugin(string scriptIdent) { if (mActivePlugins.TryGetValue(scriptIdent, out IPlugin plugin)) { return plugin; } return null; } /// /// Returns a string with the assembly's location. /// public string GetPluginAssemblyLocation(IPlugin plugin) { return plugin.GetType().Assembly.Location; } /// /// Generates a list of references to instances of active plugins. /// /// Newly-created list of plugin references. public Dictionary GetActivePlugins() { Dictionary dict = new Dictionary(mActivePlugins.Count); foreach (KeyValuePair kvp in mActivePlugins) { // copy the contents; probably not necessary across AppDomain dict.Add(kvp.Key, kvp.Value); } Debug.WriteLine("PluginManager: returning " + dict.Count + " plugins (id=" + AppDomain.CurrentDomain.Id + ")"); return dict; } /// /// Clears the list of loaded plugins. This does not unload the assemblies from /// the AppDomain. /// public void ClearPluginList() { mActivePlugins.Clear(); } /// /// Invokes the Prepare() method on all active plugins. /// /// Reference to host object providing app services. /// Length of data spanned by address map. /// Serialized AddressMap entries. /// SymbolTable contents, converted to PlSymbol. public void PreparePlugins(IApplication appRef, int spanLength, List addrEntries, List plSyms) { AddressMap addrMap = new AddressMap(spanLength, addrEntries); AddressTranslate addrTrans = new AddressTranslate(addrMap); foreach (KeyValuePair kvp in mActivePlugins) { IPlugin ipl = kvp.Value; ipl.Prepare(appRef, mFileData, addrTrans); if (ipl is IPlugin_SymbolList) { try { ((IPlugin_SymbolList)ipl).UpdateSymbolList(plSyms); } catch (Exception ex) { throw new Exception("Failed in UpdateSymbolList(" + kvp.Key + ")", ex); } } } } /// /// Invokes the Unprepare() method on all active plugins. /// public void UnpreparePlugins() { foreach (KeyValuePair kvp in mActivePlugins) { IPlugin ipl = kvp.Value; ipl.Unprepare(); } } /// /// Returns true if any of the plugins report that the before or after label is /// significant. /// public bool IsLabelSignificant(string labelBefore, string labelAfter) { foreach (KeyValuePair kvp in mActivePlugins) { IPlugin ipl = kvp.Value; if (ipl is IPlugin_SymbolList && ((IPlugin_SymbolList)ipl).IsLabelSignificant(labelBefore, labelAfter)) { return true; } } return false; } #if false /// /// DEBUG ONLY: establish a fast lease timeout. Normally the lease /// is five minutes; this reduces it to a few seconds. (The actual time is /// also affected by LifetimeServices.LeaseManagerPollTime, which defaults /// to 10 seconds.) /// /// Unfortunately this must be tagged [SecurityCritical] to match the method being /// overridden, but in a partially-trusted sandbox that's not allowed. You have /// to relax security entirely for this to work. /// //[SecurityPermissionAttribute(SecurityAction.Demand, // Flags = SecurityPermissionFlag.Infrastructure)] [System.Security.SecurityCritical] public override object InitializeLifetimeService() { object lease = base.InitializeLifetimeService(); // netstandard2.0 doesn't have System.Runtime.Remoting.Lifetime, so use reflection PropertyInfo leaseState = lease.GetType().GetProperty("CurrentState"); PropertyInfo initialLeaseTime = lease.GetType().GetProperty("InitialLeaseTime"); PropertyInfo sponsorshipTimeout = lease.GetType().GetProperty("SponsorshipTimeout"); PropertyInfo renewOnCallTime = lease.GetType().GetProperty("RenewOnCallTime"); Console.WriteLine("Default lease: ini=" + initialLeaseTime.GetValue(lease) + " spon=" + sponsorshipTimeout.GetValue(lease) + " renOC=" + renewOnCallTime.GetValue(lease)); if ((int)leaseState.GetValue(lease) == 1 /*LeaseState.Initial*/) { // Initial lease duration. initialLeaseTime.SetValue(lease, TimeSpan.FromSeconds(8)); // How long we will wait for the sponsor to respond // with a lease renewal time. sponsorshipTimeout.SetValue(lease, TimeSpan.FromSeconds(5)); // Each call to the remote object extends the lease so that // it has at least this much time left. renewOnCallTime.SetValue(lease, TimeSpan.FromSeconds(2)); } return lease; } #endif } }