]> git.ipfire.org Git - thirdparty/freeswitch.git/commitdiff
Oops. Here's the actual new mod_managed files
authorMichael Giagnocavo <mgg@giagnocavo.net>
Sun, 26 Jul 2009 16:31:03 +0000 (16:31 +0000)
committerMichael Giagnocavo <mgg@giagnocavo.net>
Sun, 26 Jul 2009 16:31:03 +0000 (16:31 +0000)
git-svn-id: http://svn.freeswitch.org/svn/freeswitch/trunk@14366 d0543943-73ff-0310-b7d9-9358b9ac24b2

src/mod/languages/mod_managed/managed/PluginInterfaces.cs [new file with mode: 0644]
src/mod/languages/mod_managed/managed/PluginManager.cs [new file with mode: 0644]
src/mod/languages/mod_managed/managed/ScriptPluginManager.cs [new file with mode: 0644]

diff --git a/src/mod/languages/mod_managed/managed/PluginInterfaces.cs b/src/mod/languages/mod_managed/managed/PluginInterfaces.cs
new file mode 100644 (file)
index 0000000..06d2a38
--- /dev/null
@@ -0,0 +1,91 @@
+/* \r
+ * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_managed\r
+ * Copyright (C) 2008, Michael Giagnocavo <mgg@giagnocavo.net>\r
+ *\r
+ * Version: MPL 1.1\r
+ *\r
+ * The contents of this file are subject to the Mozilla Public License Version\r
+ * 1.1 (the "License"); you may not use this file except in compliance with\r
+ * the License. You may obtain a copy of the License at\r
+ * http://www.mozilla.org/MPL/\r
+ *\r
+ * Software distributed under the License is distributed on an "AS IS" basis,\r
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License\r
+ * for the specific language governing rights and limitations under the\r
+ * License.\r
+ *\r
+ * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_managed\r
+ *\r
+ * The Initial Developer of the Original Code is\r
+ * Michael Giagnocavo <mgg@giagnocavo.net>\r
+ * Portions created by the Initial Developer are Copyright (C)\r
+ * the Initial Developer. All Rights Reserved.\r
+ *\r
+ * Contributor(s):\r
+ * \r
+ * Michael Giagnocavo <mgg@giagnocavo.net>\r
+ * Jeff Lenk <jeff@jefflenk.com>\r
+ * \r
+ * PluginInterfaces.cs -- Public interfaces for plugins\r
+ *\r
+ */\r
+\r
+using System;\r
+using System.Collections.Generic;\r
+using System.Linq;\r
+\r
+namespace FreeSWITCH {\r
+\r
+    public class AppContext {\r
+        readonly string arguments;\r
+        readonly Native.ManagedSession session;\r
+\r
+        public AppContext(string arguments, Native.ManagedSession session) {\r
+            this.arguments = arguments;\r
+            this.session = session;\r
+        }\r
+\r
+        public string Arguments { get { return arguments; } }\r
+        public Native.ManagedSession Session { get { return session; } }\r
+    }\r
+\r
+    public class ApiContext {\r
+        readonly string arguments;\r
+        readonly Native.Stream stream;\r
+        readonly Native.Event evt;\r
+\r
+        public ApiContext(string arguments, Native.Stream stream, Native.Event evt) {\r
+            this.arguments = arguments;\r
+            this.stream = stream;\r
+            this.evt = evt;\r
+        }\r
+\r
+        public string Arguments { get { return arguments; } }\r
+        public Native.Stream Stream { get { return stream; } }\r
+        public Native.Event Event { get { return evt; } }\r
+    }\r
+\r
+    public class ApiBackgroundContext {\r
+        readonly string arguments;\r
+\r
+        public ApiBackgroundContext(string arguments) {\r
+            this.arguments = arguments;\r
+        }\r
+\r
+        public string Arguments { get { return arguments; } }\r
+    }\r
+\r
+    public interface IApiPlugin {\r
+        void Execute(ApiContext context);\r
+        void ExecuteBackground(ApiBackgroundContext context);\r
+    }\r
+\r
+    public interface IAppPlugin {\r
+        void Run(AppContext context);\r
+    }\r
+\r
+    public interface ILoadNotificationPlugin {\r
+        bool Load();\r
+    }\r
+\r
+}\r
diff --git a/src/mod/languages/mod_managed/managed/PluginManager.cs b/src/mod/languages/mod_managed/managed/PluginManager.cs
new file mode 100644 (file)
index 0000000..c82d663
--- /dev/null
@@ -0,0 +1,291 @@
+/* \r
+ * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_managed\r
+ * Copyright (C) 2008, Michael Giagnocavo <mgg@giagnocavo.net>\r
+ *\r
+ * Version: MPL 1.1\r
+ *\r
+ * The contents of this file are subject to the Mozilla Public License Version\r
+ * 1.1 (the "License"); you may not use this file except in compliance with\r
+ * the License. You may obtain a copy of the License at\r
+ * http://www.mozilla.org/MPL/\r
+ *\r
+ * Software distributed under the License is distributed on an "AS IS" basis,\r
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License\r
+ * for the specific language governing rights and limitations under the\r
+ * License.\r
+ *\r
+ * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_managed\r
+ *\r
+ * The Initial Developer of the Original Code is\r
+ * Michael Giagnocavo <mgg@giagnocavo.net>\r
+ * Portions created by the Initial Developer are Copyright (C)\r
+ * the Initial Developer. All Rights Reserved.\r
+ *\r
+ * Contributor(s):\r
+ * \r
+ * Michael Giagnocavo <mgg@giagnocavo.net>\r
+ * Jeff Lenk <jeff@jefflenk.com>\r
+ * \r
+ * PluginManager.cs -- Plugin execution code\r
+ *\r
+ */\r
+\r
+using System;\r
+using System.Collections.Generic;\r
+using System.Linq;\r
+using System.Reflection;\r
+using System.Reflection.Emit;\r
+\r
+namespace FreeSWITCH {\r
+\r
+    internal abstract class PluginExecutor : MarshalByRefObject {\r
+        public override object InitializeLifetimeService() {\r
+            return null;\r
+        }\r
+\r
+        /// <summary>Names by which this plugin may be executed.</summary>\r
+        public List<string> Aliases { get { return aliases; } }\r
+        readonly List<string> aliases = new List<string>();\r
+        \r
+        /// <summary>The canonical name to identify this plugin (informative).</summary>\r
+        public string Name { get { return name; } }\r
+        readonly string name;\r
+\r
+        protected PluginExecutor(string name, List<string> aliases) {\r
+            if (string.IsNullOrEmpty(name)) throw new ArgumentException("No name provided.");\r
+            if (aliases == null || aliases.Count == 0) throw new ArgumentException("No aliases provided.");\r
+            this.name = name;\r
+            this.aliases = aliases.Distinct().ToList();\r
+        }\r
+\r
+        int useCount = 0;\r
+        protected void IncreaseUse() {\r
+            System.Threading.Interlocked.Increment(ref useCount);\r
+        }\r
+        protected void DecreaseUse() {\r
+            var count = System.Threading.Interlocked.Decrement(ref useCount);\r
+            if (count == 0 && onZeroUse != null) {\r
+                onZeroUse();\r
+            }\r
+        }\r
+\r
+        Action onZeroUse;\r
+        public void SetZeroUseNotification(Action onZeroUse) {\r
+            this.onZeroUse = onZeroUse;\r
+            if (useCount == 0) onZeroUse();\r
+        }\r
+\r
+        protected static void LogException(string action, string moduleName, Exception ex) {\r
+            Log.WriteLine(LogLevel.Error, "{0} exception in {1}: {2}", action, moduleName, ex.Message);\r
+            Log.WriteLine(LogLevel.Debug, "{0} exception: {1}", moduleName, ex.ToString());\r
+        }\r
+    }\r
+\r
+    internal sealed class AppPluginExecutor : PluginExecutor {\r
+\r
+        readonly Func<IAppPlugin> createPlugin;\r
+\r
+        public AppPluginExecutor(string name, List<string> aliases, Func<IAppPlugin> creator)\r
+            : base(name, aliases) {\r
+            if (creator == null) throw new ArgumentNullException("Creator cannot be null.");\r
+            this.createPlugin = creator;\r
+        }\r
+\r
+        public bool Execute(string args, IntPtr sessionHandle) {\r
+            IncreaseUse();\r
+            try {\r
+                using (var session = new Native.ManagedSession(new Native.SWIGTYPE_p_switch_core_session(sessionHandle, false))) {\r
+                    session.Initialize();\r
+                    session.SetAutoHangup(false);\r
+                    try {\r
+                        var plugin = createPlugin();\r
+                        var context = new AppContext(args, session);;\r
+                        plugin.Run(context);\r
+                        return true;\r
+                    } catch (Exception ex) {\r
+                        LogException("Run", Name, ex);\r
+                        return false;\r
+                    }\r
+                }\r
+            } finally {\r
+                DecreaseUse();\r
+            }\r
+        }\r
+    }\r
+\r
+    internal sealed class ApiPluginExecutor : PluginExecutor {\r
+\r
+        readonly Func<IApiPlugin> createPlugin;\r
+\r
+        public ApiPluginExecutor(string name, List<string> aliases, Func<IApiPlugin> creator)\r
+            : base(name, aliases) {\r
+            if (creator == null) throw new ArgumentNullException("Creator cannot be null.");\r
+            this.createPlugin = creator;\r
+        }\r
+\r
+        public bool ExecuteApi(string args, IntPtr streamHandle, IntPtr eventHandle) {\r
+            IncreaseUse();\r
+            try {\r
+                using (var stream = new Native.Stream(new Native.switch_stream_handle(streamHandle, false)))\r
+                using (var evt = eventHandle == IntPtr.Zero ? null : new Native.Event(new Native.switch_event(eventHandle, false), 0)) {\r
+                    try {\r
+                        var context = new ApiContext(args, stream, evt);\r
+                        var plugin = createPlugin();\r
+                        plugin.Execute(context);\r
+                        return true;\r
+                    } catch (Exception ex) {\r
+                        LogException("Execute", Name, ex);\r
+                        return false;\r
+                    }\r
+                }\r
+            } finally {\r
+                DecreaseUse();\r
+            }\r
+        }\r
+\r
+        public bool ExecuteApiBackground(string args) {\r
+            // Background doesn't affect use count\r
+            new System.Threading.Thread(() => {\r
+                try {\r
+                    var context = new ApiBackgroundContext(args);\r
+                    var plugin = createPlugin();\r
+                    plugin.ExecuteBackground(context);\r
+                    Log.WriteLine(LogLevel.Debug, "ExecuteBackground in {0} completed.", Name);\r
+                } catch (Exception ex) {\r
+                    LogException("ExecuteBackground", Name, ex);\r
+                }\r
+            }).Start();\r
+            return true;\r
+        }\r
+    }\r
+\r
+    internal abstract class PluginManager : MarshalByRefObject {\r
+        public override object InitializeLifetimeService() {\r
+            return null;\r
+        }\r
+\r
+        public List<ApiPluginExecutor> ApiExecutors { get { return _apiExecutors; } }\r
+        readonly List<ApiPluginExecutor> _apiExecutors = new List<ApiPluginExecutor>();\r
+\r
+        public List<AppPluginExecutor> AppExecutors { get { return _appExecutors; }  }\r
+\r
+        readonly List<AppPluginExecutor> _appExecutors = new List<AppPluginExecutor>();\r
+\r
+        bool isLoaded = false;\r
+\r
+        public bool Load(string file) {\r
+            Console.WriteLine("Loading {0} from domain {1}", file, AppDomain.CurrentDomain.FriendlyName);\r
+            if (isLoaded) throw new InvalidOperationException("PluginManager has already been loaded.");\r
+            if (string.IsNullOrEmpty(file)) throw new ArgumentNullException("file cannot be null or empty.");\r
+            if (AppDomain.CurrentDomain.IsDefaultAppDomain()) throw new InvalidOperationException("PluginManager must load in its own AppDomain.");\r
+            var res = LoadInternal(file);\r
+            isLoaded = true;\r
+\r
+            res = res && AppExecutors.Count > 0 && ApiExecutors.Count > 0;\r
+            return res;\r
+        }\r
+\r
+        protected abstract bool LoadInternal(string fileName);\r
+\r
+        protected bool RunLoadNotify(Type[] allTypes) {\r
+            // Run Load on all the load plugins\r
+            var ty = typeof(ILoadNotificationPlugin);\r
+            var pluginTypes = allTypes.Where(x => ty.IsAssignableFrom(x) && !x.IsAbstract).ToList();\r
+            if (pluginTypes.Count == 0) return true;\r
+            foreach (var pt in pluginTypes) {\r
+                var load = ((ILoadNotificationPlugin)Activator.CreateInstance(pt, false));\r
+                if (!load.Load()) {\r
+                    Log.WriteLine(LogLevel.Notice, "Type {0} requested no loading. Assembly will not be loaded.", pt.FullName);\r
+                    return false;\r
+                }\r
+            }\r
+            return true;\r
+        }\r
+\r
+        protected void AddApiPlugins(Type[] allTypes) {\r
+            var iApiTy = typeof(IApiPlugin);\r
+            foreach (var ty in allTypes.Where(x => iApiTy.IsAssignableFrom(x) && !x.IsAbstract)) {\r
+                var del = CreateConstructorDelegate<IApiPlugin>(ty);\r
+                var exec = new ApiPluginExecutor(ty.FullName, new List<string> { ty.FullName, ty.Name }, del);\r
+                this.ApiExecutors.Add(exec);\r
+            }\r
+        }\r
+\r
+        protected void AddAppPlugins(Type[] allTypes) {\r
+            var iAppTy = typeof(IAppPlugin);\r
+            foreach (var ty in allTypes.Where(x => iAppTy.IsAssignableFrom(x) && !x.IsAbstract)) {\r
+                var del = CreateConstructorDelegate<IAppPlugin>(ty);\r
+                var exec = new AppPluginExecutor(ty.FullName, new List<string> { ty.FullName, ty.Name }, del);\r
+                this.AppExecutors.Add(exec);\r
+            }\r
+        }\r
+\r
+        #region Unload\r
+\r
+        bool isUnloading = false;\r
+        int unloadCount;\r
+        System.Threading.ManualResetEvent unloadSignal = new System.Threading.ManualResetEvent(false);\r
+        void decreaseUnloadCount() {\r
+            if (System.Threading.Interlocked.Decrement(ref unloadCount) == 0) {\r
+                unloadSignal.Set();\r
+            }\r
+        }\r
+\r
+        public void BlockUntilUnloadIsSafe() {\r
+            if (isUnloading) throw new InvalidOperationException("PluginManager is already unloading.");\r
+            isUnloading = true;\r
+            unloadCount = ApiExecutors.Count + AppExecutors.Count;\r
+            ApiExecutors.ForEach(x => x.SetZeroUseNotification(decreaseUnloadCount));\r
+            AppExecutors.ForEach(x => x.SetZeroUseNotification(decreaseUnloadCount));\r
+            unloadSignal.WaitOne();\r
+        }\r
+\r
+        #endregion\r
+\r
+        public static Func<T> CreateConstructorDelegate<T>(Type ty) {\r
+            var destTy = typeof(T);\r
+            if (!destTy.IsAssignableFrom(ty)) throw new ArgumentException(string.Format("Type {0} is not assignable from {1}.", destTy.FullName, ty.FullName));\r
+            var con = ty.GetConstructor(Type.EmptyTypes);\r
+            if (con == null) throw new ArgumentException(string.Format("Type {0} doesn't have an accessible parameterless constructor.", ty.FullName));\r
+\r
+            var rand = Guid.NewGuid().ToString().Replace("-", "");\r
+            var dm = new DynamicMethod("CREATE_" + ty.FullName.Replace('.', '_') + rand, ty, null, true);\r
+            var il = dm.GetILGenerator();\r
+            il.Emit(OpCodes.Newobj, ty.GetConstructor(Type.EmptyTypes));\r
+            il.Emit(OpCodes.Ret);\r
+\r
+            return (Func<T>)dm.CreateDelegate(typeof(Func<T>));\r
+        }\r
+    }\r
+\r
+    internal class AsmPluginManager : PluginManager {\r
+\r
+        protected override bool LoadInternal(string fileName) {\r
+            Assembly asm;\r
+            try {\r
+                asm = Assembly.LoadFrom(fileName);\r
+            } catch (Exception ex) {\r
+                Log.WriteLine(LogLevel.Info, "Couldn't load {0}: {1}", fileName, ex.Message);\r
+                return false;\r
+            }\r
+\r
+            // Ensure it's a plugin assembly\r
+            var ourName = Assembly.GetExecutingAssembly().GetName().Name;\r
+            if (!asm.GetReferencedAssemblies().Any(n => n.Name == ourName)) {\r
+                Log.WriteLine(LogLevel.Debug, "Assembly {0} doesn't reference FreeSWITCH.Managed, not loading.");\r
+                return false; \r
+            }\r
+\r
+            // See if it wants to be loaded\r
+            var allTypes = asm.GetExportedTypes();\r
+            if (!RunLoadNotify(allTypes)) return false;\r
+\r
+            AddApiPlugins(allTypes);\r
+            AddAppPlugins(allTypes);\r
+\r
+            return true;\r
+        }\r
+\r
+    }\r
+\r
+}\r
diff --git a/src/mod/languages/mod_managed/managed/ScriptPluginManager.cs b/src/mod/languages/mod_managed/managed/ScriptPluginManager.cs
new file mode 100644 (file)
index 0000000..7b0192d
--- /dev/null
@@ -0,0 +1,242 @@
+/* \r
+ * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_managed\r
+ * Copyright (C) 2008, Michael Giagnocavo <mgg@giagnocavo.net>\r
+ *\r
+ * Version: MPL 1.1\r
+ *\r
+ * The contents of this file are subject to the Mozilla Public License Version\r
+ * 1.1 (the "License"); you may not use this file except in compliance with\r
+ * the License. You may obtain a copy of the License at\r
+ * http://www.mozilla.org/MPL/\r
+ *\r
+ * Software distributed under the License is distributed on an "AS IS" basis,\r
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License\r
+ * for the specific language governing rights and limitations under the\r
+ * License.\r
+ *\r
+ * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_managed\r
+ *\r
+ * The Initial Developer of the Original Code is\r
+ * Michael Giagnocavo <mgg@giagnocavo.net>\r
+ * Portions created by the Initial Developer are Copyright (C)\r
+ * the Initial Developer. All Rights Reserved.\r
+ *\r
+ * Contributor(s):\r
+ * \r
+ * Michael Giagnocavo <mgg@giagnocavo.net>\r
+ * \r
+ * ScriptPluginManager.cs -- Dynamic compilation and script execution\r
+ *\r
+ */\r
+\r
+using System;\r
+using System.Collections.Generic;\r
+using System.Linq;\r
+using System.Text;\r
+using System.CodeDom;\r
+using System.CodeDom.Compiler;\r
+using System.IO;\r
+using System.Reflection;\r
+using System.Reflection.Emit;\r
+\r
+\r
+namespace FreeSWITCH {\r
+\r
+    public enum ScriptContextType {\r
+        App,\r
+        Api,\r
+        ApiBackground,\r
+    }\r
+\r
+    public static class Script {\r
+\r
+        [ThreadStatic]\r
+        internal static ScriptContextType contextType;\r
+        [ThreadStatic]\r
+        internal static object context;\r
+\r
+        public static ScriptContextType ContextType { get { return contextType; } }\r
+\r
+        public static ApiContext GetApiContext() {\r
+            return getContext<ApiContext>(ScriptContextType.Api);\r
+        }\r
+        public static ApiBackgroundContext GetApiBackgroundContext() {\r
+            return getContext<ApiBackgroundContext>(ScriptContextType.ApiBackground);\r
+        }\r
+        public static AppContext GetAppContext() {\r
+            return getContext<AppContext>(ScriptContextType.App);\r
+        }\r
+\r
+        public static T getContext<T>(ScriptContextType sct) {\r
+            var ctx = context;\r
+            if (ctx == null) throw new InvalidOperationException("Current context is null.");\r
+            if (contextType != sct) throw new InvalidOperationException("Current ScriptContextType is not " + sct.ToString() + ".");\r
+            return (T)ctx;\r
+        }\r
+\r
+    }\r
+\r
+    internal class ScriptPluginManager : PluginManager {\r
+\r
+        protected override bool LoadInternal(string fileName) {\r
+            Assembly asm;\r
+            if (Path.GetExtension(fileName).ToLowerInvariant() == ".exe") {\r
+                asm = Assembly.LoadFrom(fileName);\r
+            } else {\r
+                asm = compileAssembly(fileName);\r
+            }\r
+            if (asm == null) return false;\r
+\r
+            return processAssembly(fileName, asm);\r
+        }\r
+\r
+        Assembly compileAssembly(string fileName) {\r
+            var comp = new CompilerParameters();\r
+            var mainRefs = new List<string> { \r
+                Path.Combine(Native.freeswitch.SWITCH_GLOBAL_dirs.mod_dir, "FreeSWITCH.Managed.dll"),\r
+                "System.dll", "System.Xml.dll", "System.Data.dll" \r
+            };\r
+            var extraRefs = new List<string> {\r
+                "System.Core.dll", \r
+                "System.Xml.Linq.dll",\r
+            };\r
+            comp.ReferencedAssemblies.AddRange(mainRefs.ToArray());\r
+            comp.ReferencedAssemblies.AddRange(extraRefs.ToArray());\r
+            CodeDomProvider cdp;\r
+            var ext = Path.GetExtension(fileName).ToLowerInvariant();\r
+            switch (ext) {\r
+                case ".fsx":\r
+                    cdp = CodeDomProvider.CreateProvider("f#");\r
+                    break;\r
+                case ".csx":\r
+                    cdp = new Microsoft.CSharp.CSharpCodeProvider(new Dictionary<string, string> { { "CompilerVersion", "v3.5" } });\r
+                    break;\r
+                case ".vbx":\r
+                    cdp = new Microsoft.VisualBasic.VBCodeProvider(new Dictionary<string, string> { { "CompilerVersion", "v3.5" } });\r
+                    break;\r
+                case ".jsx":\r
+                    // Have to figure out better JS support\r
+                    cdp = CodeDomProvider.CreateProvider("js");\r
+                    extraRefs.ForEach(comp.ReferencedAssemblies.Remove);\r
+                    break;\r
+                default:\r
+                    if (CodeDomProvider.IsDefinedExtension(ext)) {\r
+                        cdp = CodeDomProvider.CreateProvider(CodeDomProvider.GetLanguageFromExtension(ext));\r
+                    } else {\r
+                        return null;\r
+                    }\r
+                    break;\r
+            }\r
+\r
+            comp.GenerateInMemory = true;\r
+            comp.GenerateExecutable = true;\r
+\r
+            Log.WriteLine(LogLevel.Info, "Compiling {0}", fileName);\r
+            var res = cdp.CompileAssemblyFromFile(comp, fileName);\r
+\r
+            var errors = res.Errors.Cast<CompilerError>().Where(x => !x.IsWarning).ToList();\r
+            if (errors.Count > 0) {\r
+                Log.WriteLine(LogLevel.Error, "There were {0} errors compiling {1}.", errors.Count, fileName);\r
+                foreach (var err in errors) {\r
+                    if (string.IsNullOrEmpty(err.FileName)) {\r
+                        Log.WriteLine(LogLevel.Error, "{0}: {1}", err.ErrorNumber, err.ErrorText);\r
+                    } else {\r
+                        Log.WriteLine(LogLevel.Error, "{0}: {1}:{2}:{3} {4}", err.ErrorNumber, err.FileName, err.Line, err.Column, err.ErrorText);\r
+                    }\r
+                }\r
+                return null;\r
+            }\r
+            Log.WriteLine(LogLevel.Info, "File {0} compiled successfully.", fileName);\r
+            return res.CompiledAssembly;\r
+        }\r
+\r
+        bool processAssembly(string fileName, Assembly asm) {\r
+            var allTypes = asm.GetExportedTypes();\r
+            if (!RunLoadNotify(allTypes)) return false;\r
+\r
+            // Scripts can specify classes too\r
+            AddApiPlugins(allTypes);\r
+            AddAppPlugins(allTypes);\r
+\r
+            // Add the script executors\r
+            var entryPoint = getEntryDelegate(asm.EntryPoint);\r
+            var name = Path.GetFileName(fileName);\r
+            var aliases = new List<string> { name };\r
+            this.ApiExecutors.Add(new ApiPluginExecutor(name, aliases, () => new ScriptApiWrapper(entryPoint)));\r
+            this.AppExecutors.Add(new AppPluginExecutor(name, aliases, () => new ScriptAppWrapper(entryPoint)));\r
+\r
+            return true;\r
+        }\r
+\r
+        class ScriptApiWrapper : IApiPlugin {\r
+\r
+            readonly Action entryPoint;\r
+            public ScriptApiWrapper(Action entryPoint) {\r
+                this.entryPoint = entryPoint;\r
+            }\r
+\r
+            public void Execute(ApiContext context) {\r
+                Script.contextType = ScriptContextType.Api;\r
+                Script.context = context;\r
+                try {\r
+                    entryPoint();\r
+                } finally {\r
+                    Script.context = null;\r
+                }\r
+            }\r
+\r
+            public void ExecuteBackground(ApiBackgroundContext context) {\r
+                Script.contextType = ScriptContextType.ApiBackground;\r
+                Script.context = context;\r
+                try {\r
+                    entryPoint();\r
+                } finally {\r
+                    Script.context = null;\r
+                }\r
+            }\r
+\r
+        }\r
+\r
+        class ScriptAppWrapper : IAppPlugin {\r
+            \r
+            readonly Action entryPoint;\r
+            public ScriptAppWrapper(Action entryPoint) {\r
+                this.entryPoint = entryPoint;\r
+            }\r
+\r
+            public void Run(AppContext context) {\r
+                Script.contextType = ScriptContextType.App;\r
+                Script.context = context;\r
+                try {\r
+                    entryPoint();\r
+                } finally {\r
+                    Script.context = null;\r
+                }\r
+            }\r
+\r
+        }\r
+\r
+        static Action getEntryDelegate(MethodInfo entryPoint) {\r
+            if (!entryPoint.IsPublic || !entryPoint.DeclaringType.IsPublic) {\r
+                Log.WriteLine(LogLevel.Error, "Entry point: {0}.{1} is not public. This may cause errors with Mono.",\r
+                    entryPoint.DeclaringType.FullName, entryPoint.Name);\r
+            }\r
+            var dm = new DynamicMethod(entryPoint.DeclaringType.Assembly.GetName().Name + "_entrypoint_" + entryPoint.DeclaringType.FullName + entryPoint.Name, null, null, true);\r
+            var il = dm.GetILGenerator();\r
+            var args = entryPoint.GetParameters();\r
+            if (args.Length > 1) throw new ArgumentException("Cannot handle entry points with more than 1 parameter.");\r
+            if (args.Length == 1) {\r
+                if (args[0].ParameterType != typeof(string[])) throw new ArgumentException("Entry point paramter must be a string array.");\r
+                il.Emit(OpCodes.Ldc_I4_0);\r
+                il.Emit(OpCodes.Newarr, typeof(string));\r
+            }\r
+            il.EmitCall(OpCodes.Call, entryPoint, null);\r
+            if (entryPoint.ReturnType != typeof(void)) {\r
+                il.Emit(OpCodes.Pop);\r
+            }\r
+            il.Emit(OpCodes.Ret);\r
+            return (Action)dm.CreateDelegate(typeof(Action));\r
+        }\r
+\r
+    }\r
+}\r