/* * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_cli * Copyright (C) 2008, Michael Giagnocavo * * Version: MPL 1.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (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.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_cli * * The Initial Developer of the Original Code is * Michael Giagnocavo * Portions created by the Initial Developer are Copyright (C) * the Initial Developer. All Rights Reserved. * * Contributor(s): * * Michael Giagnocavo * David Brazier * * Loader.cs -- mod_mono managed loader * */ using System; using System.Collections.Generic; using System.Text; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; namespace FreeSWITCH { internal static class Loader { // Stores a list of the loaded function types so we can instantiate them as needed static readonly Dictionary functions = new Dictionary(StringComparer.InvariantCultureIgnoreCase); // Only class name. Last in wins. static readonly Dictionary shortFunctions = new Dictionary(StringComparer.InvariantCultureIgnoreCase); #region Load/Unload public static bool Load() { string managedDir = Path.Combine(Native.freeswitch.SWITCH_GLOBAL_dirs.mod_dir, "managed"); Log.WriteLine(LogLevel.Debug, "mod_managed_lib loader is starting with directory '{0}'.", managedDir); if (!Directory.Exists(managedDir)) { Log.WriteLine(LogLevel.Error, "Managed directory not found: {0}", managedDir); return false; } AppDomain.CurrentDomain.AssemblyResolve += (_, rargs) => { Log.WriteLine(LogLevel.Debug, "Trying to resolve assembly '{0}'.", rargs.Name); if (rargs.Name == Assembly.GetExecutingAssembly().FullName) return Assembly.GetExecutingAssembly(); var path = Path.Combine(managedDir, rargs.Name + ".dll"); Log.WriteLine(LogLevel.Debug, "Resolving to: '" + path + "'."); return File.Exists(path) ? Assembly.LoadFile(path) : null; }; InitManagedDelegates(_run, _execute, _executeBackground); // This is a simple one-time loader to get things in memory // Some day we should allow reloading of modules or something loadAssemblies(managedDir) .SelectMany(a => a.GetExportedTypes()) .Where(t => !t.IsAbstract) .Where(t => t.IsSubclassOf(typeof(AppFunction)) || t.IsSubclassOf(typeof(ApiFunction))) .ToList() .loadFunctions(); return true; } delegate bool ExecuteDelegate(string cmd, IntPtr streamH, IntPtr eventH); delegate bool ExecuteBackgroundDelegate(string cmd); delegate bool RunDelegate(string cmd, IntPtr session); static readonly ExecuteDelegate _execute = Execute; static readonly ExecuteBackgroundDelegate _executeBackground = ExecuteBackground; static readonly RunDelegate _run = Run; //SWITCH_MOD_DECLARE(void) InitManagedDelegates(runFunction run, executeFunction execute, executeBackgroundFunction executeBackground) [DllImport("mod_managed")] static extern void InitManagedDelegates(RunDelegate run, ExecuteDelegate execute, ExecuteBackgroundDelegate executeBackground); // Be rather lenient in finding the Load and Unload methods static readonly BindingFlags methodBindingFlags = BindingFlags.Static | // Required BindingFlags.Public | BindingFlags.NonPublic | // Implementors might decide to make the load method private BindingFlags.IgnoreCase | // Some case insensitive languages? BindingFlags.FlattenHierarchy; // Allow inherited methods for hierarchies static void loadFunctions(this List allTypes) { foreach (var t in allTypes) { try { if (functions.ContainsKey(t.FullName)) { Log.WriteLine(LogLevel.Error, "Duplicate function {0}. Skipping.", t.FullName); continue; } var loadMethod = t.GetMethod("Load", methodBindingFlags, null, Type.EmptyTypes, null); var shouldLoad = Convert.ToBoolean(loadMethod.Invoke(null, null)); // We don't require the Load method to return a bool exactly if (shouldLoad) { Log.WriteLine(LogLevel.Notice, "Function {0} loaded.", t.FullName); functions.Add(t.FullName, t); shortFunctions[t.Name] = t; } else { Log.WriteLine(LogLevel.Notice, "Function {0} requested not to be loaded.", t.FullName); } } catch (Exception ex) { logException("Load", t.FullName, ex); } } } static Assembly[] loadAssemblies(string managedDir) { // load the modules in the mod/managed directory Log.WriteLine(LogLevel.Notice, "loadAssemblies: {0}", managedDir); foreach (string s in Directory.GetFiles(managedDir, "*.dll", SearchOption.AllDirectories)) { string f = Path.Combine(managedDir, s); try { Log.WriteLine(LogLevel.Debug, "Loading '{0}'.", f); System.Reflection.Assembly.LoadFile(f); } catch (Exception ex) { Log.WriteLine(LogLevel.Notice, "Assembly.LoadFile failed; skipping {0} ({1})", f, ex.Message); } } return AppDomain.CurrentDomain.GetAssemblies(); // Includes anything else already loaded } public static void Unload() { Log.WriteLine(LogLevel.Debug, "mod_managed_lib Loader is unloading."); foreach (var t in functions.Values) { try { var unloadMethod = t.GetMethod("Unload", methodBindingFlags, null, Type.EmptyTypes, null); unloadMethod.Invoke(null, null); } catch (Exception ex) { logException("Unload", t.FullName, ex); } } } #endregion #region Execution static Type getFunctionType(string fullName) { Type t; if (!functions.TryGetValue(fullName, out t) || !t.IsSubclassOf(typeof(TFunction))) { if (!shortFunctions.TryGetValue(fullName, out t) || !t.IsSubclassOf(typeof(TFunction))) { Log.WriteLine(LogLevel.Error, "Could not find function {0}.", fullName); return null; } } return t; } static readonly char[] spaceArray = new[] { ' ' }; /// Returns a string couple containing the module name and arguments static string[] parseCommand(string command) { if (string.IsNullOrEmpty(command)) { Log.WriteLine(LogLevel.Error, "No arguments supplied."); return null; } var args = command.Split(spaceArray, 2, StringSplitOptions.RemoveEmptyEntries); if (args.Length == 0 || string.IsNullOrEmpty(args[0]) || string.IsNullOrEmpty(args[0].Trim())) { Log.WriteLine(LogLevel.Error, "Module name not supplied."); return null; } if (args.Length == 1) { return new[] { args[0], "" }; } else { return args; } } public static bool ExecuteBackground(string command) { try { var parsed = parseCommand(command); if (parsed == null) return false; var fullName = parsed[0]; var args = parsed[1]; var fType = getFunctionType(fullName); if (fType == null) return false; new System.Threading.Thread(() => { try { var f = (ApiFunction)Activator.CreateInstance(fType); f.ExecuteBackground(args); Log.WriteLine(LogLevel.Debug, "ExecuteBackground in {0} completed.", fullName); } catch (Exception ex) { logException("ExecuteBackground", fullName, ex); } }).Start(); return true; } catch (Exception ex) { Log.WriteLine(LogLevel.Error, "Exception in ExecuteBackground({0}): {1}", command, ex.ToString()); return false; } } public static bool Execute(string command, IntPtr streamHandle, IntPtr eventHandle) { System.Diagnostics.Debug.Assert(streamHandle != IntPtr.Zero, "streamHandle is null."); var parsed = parseCommand(command); if (parsed == null) return false; var fullName = parsed[0]; var args = parsed[1]; var fType = getFunctionType(fullName); if (fType == null) return false; using (var stream = new Native.Stream(new Native.switch_stream_handle(streamHandle, false))) using (var evt = eventHandle == IntPtr.Zero ? null : new Native.Event(new Native.switch_event(eventHandle, false), 0)) { try { var f = (ApiFunction)Activator.CreateInstance(fType); f.Execute(stream, evt, args); return true; } catch (Exception ex) { logException("Execute", fullName, ex); return false; } } } /// Runs an application function. public static bool Run(string command, IntPtr sessionHandle) { Log.WriteLine(LogLevel.Debug, "mod_mono attempting to run application '{0}'.", command); System.Diagnostics.Debug.Assert(sessionHandle != IntPtr.Zero, "sessionHandle is null."); var parsed = parseCommand(command); if (parsed == null) return false; var fullName = parsed[0]; var args = parsed[1]; var fType = getFunctionType(fullName); if (fType == null) return false; using (var session = new Native.ManagedSession(new Native.SWIGTYPE_p_switch_core_session(sessionHandle, false))) { session.Initialize(); session.SetAutoHangup(false); try { var f = (AppFunction)Activator.CreateInstance(fType); f.RunInternal(session, args); return true; } catch (Exception ex) { logException("Run", fullName, ex); return false; } } } static void logException(string action, string moduleName, Exception ex) { Log.WriteLine(LogLevel.Error, "{0} exception in {1}: {2}", action, moduleName, ex.Message); Log.WriteLine(LogLevel.Debug, "{0} exception: {1}", moduleName, ex.ToString()); } #endregion } }