--- /dev/null
+/* \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
--- /dev/null
+/* \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
--- /dev/null
+/* \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