309 lines
14 KiB
C#
Raw Normal View History

/*
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_cli
* Copyright (C) 2008, Michael Giagnocavo <mgg@packetrino.com>
*
* 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 <mgg@packetrino.com>
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Michael Giagnocavo <mgg@packetrino.com>
* David Brazier <David.Brazier@360crm.co.uk>
*
* 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 Dictionary<string, Type> functions = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
// Only class name. Last in wins.
static Dictionary<string, Type> shortFunctions = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
#region Load/Unload
static string managedDir;
public static bool Load()
{
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, _loadAssembly);
// This is a simple one-time loader to get things in memory
// Some day we should allow reloading of modules or something
var allTypes = loadAssemblies(managedDir).SelectMany(a => a.GetExportedTypes());
loadFunctions(allTypes);
return true;
}
public static bool LoadAssembly(string filename) {
try {
string path = Path.Combine(managedDir, filename);
if (!File.Exists(path)) {
Log.WriteLine(LogLevel.Error, "File not found: '{0}'.", path);
return false;
}
var asm = Assembly.LoadFile(path);
loadFunctions(asm.GetExportedTypes());
return true;
} catch (Exception ex) {
Log.WriteLine(LogLevel.Error, "Exception in LoadAssembly('{0}'): {1}", filename, ex.ToString());
return false;
}
}
delegate bool ExecuteDelegate(string cmd, IntPtr streamH, IntPtr eventH);
delegate bool ExecuteBackgroundDelegate(string cmd);
delegate bool RunDelegate(string cmd, IntPtr session);
delegate bool LoadAssemblyDelegate(string filename);
static readonly ExecuteDelegate _execute = Execute;
static readonly ExecuteBackgroundDelegate _executeBackground = ExecuteBackground;
static readonly RunDelegate _run = Run;
static readonly LoadAssemblyDelegate _loadAssembly = LoadAssembly;
//SWITCH_MOD_DECLARE(void) InitManagedDelegates(runFunction run, executeFunction execute, executeBackgroundFunction executeBackground, loadAssemblyFunction loadAssembly)
[DllImport("mod_managed", CharSet = CharSet.Ansi)]
static extern void InitManagedDelegates(RunDelegate run, ExecuteDelegate execute, ExecuteBackgroundDelegate executeBackground, LoadAssemblyDelegate loadAssembly);
// 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(IEnumerable<Type> allTypes)
{
var functions = new Dictionary<string, Type>(Loader.functions, StringComparer.OrdinalIgnoreCase);
var shortFunctions = new Dictionary<string, Type>(Loader.shortFunctions, StringComparer.OrdinalIgnoreCase);
var filteredTypes = allTypes
.Where(t => !t.IsAbstract)
.Where(t => t.IsSubclassOf(typeof(AppFunction)) || t.IsSubclassOf(typeof(ApiFunction)));
foreach (var t in filteredTypes) {
try {
if (functions.ContainsKey(t.FullName)) {
functions.Remove(t.FullName);
Log.WriteLine(LogLevel.Warning, "Replacing function {0}.", t.FullName);
}
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);
}
}
Loader.functions = functions;
Loader.shortFunctions = shortFunctions;
}
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<TFunction>(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[] { ' ' };
/// <summary>Returns a string couple containing the module name and arguments</summary>
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<ApiFunction>(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<ApiFunction>(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;
}
}
}
/// <summary>Runs an application function.</summary>
public static bool Run(string command, IntPtr sessionHandle)
{
Log.WriteLine(LogLevel.Debug, "mod_managed_lib: 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<AppFunction>(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
}
}