New mod_managed with multi-appdomain (reloading) and scripting support
git-svn-id: http://svn.freeswitch.org/svn/freeswitch/trunk@14364 d0543943-73ff-0310-b7d9-9358b9ac24b2
This commit is contained in:
parent
e0fbc0c343
commit
20a2aee4fb
|
@ -34,7 +34,7 @@ swigclean: clean
|
||||||
rm -f freeswitch_wrap.cxx freeswitch_wrap.cpp managed/swig.cs
|
rm -f freeswitch_wrap.cxx freeswitch_wrap.cpp managed/swig.cs
|
||||||
|
|
||||||
freeswitch_wrap.cxx:
|
freeswitch_wrap.cxx:
|
||||||
swig -I../../../include -v -O -c++ -csharp -namespace FreeSWITCH.Native -dllimport mod_managed freeswitch.i
|
swig -I../../../include -v -O -c++ -csharp -namespace FreeSWITCH.Native -dllimport mod_managed -DSWIG_CSHARP_NO_STRING_HELPER freeswitch.i
|
||||||
rm -f ./managed/swig.cs
|
rm -f ./managed/swig.cs
|
||||||
cat *.cs > ./managed/swig.cs
|
cat *.cs > ./managed/swig.cs
|
||||||
rm -f *.cs
|
rm -f *.cs
|
||||||
|
|
|
@ -1,5 +1,70 @@
|
||||||
%module freeswitch
|
%module freeswitch
|
||||||
|
|
||||||
|
/** String fix - copied from csharphead.swg with fix for multiple appdomains **/
|
||||||
|
/* Must pass -DSWIG_CSHARP_NO_STRING_HELPER to swig */
|
||||||
|
|
||||||
|
#if defined(SWIG_CSHARP_NO_STRING_HELPER)
|
||||||
|
%insert(runtime) %{
|
||||||
|
|
||||||
|
/* Callback for returning strings to C# without leaking memory */
|
||||||
|
#ifndef _MANAGED
|
||||||
|
#include <glib.h>
|
||||||
|
#include <mono/jit/jit.h>
|
||||||
|
#include <mono/metadata/environment.h>
|
||||||
|
#include <mono/metadata/mono-config.h>
|
||||||
|
#include <mono/metadata/threads.h>
|
||||||
|
#include <mono/metadata/debug-helpers.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef char * (SWIGSTDCALL* SWIG_CSharpStringHelperCallback)(const char *);
|
||||||
|
static SWIG_CSharpStringHelperCallback SWIG_csharp_string_callback_real = NULL;
|
||||||
|
%}
|
||||||
|
|
||||||
|
%pragma(csharp) imclasscode=%{
|
||||||
|
protected class SWIGStringHelper {
|
||||||
|
|
||||||
|
public delegate string SWIGStringDelegate(string message);
|
||||||
|
static SWIGStringDelegate stringDelegate = new SWIGStringDelegate(CreateString);
|
||||||
|
|
||||||
|
[DllImport("$dllimport", EntryPoint="SWIGRegisterStringCallback_$module")]
|
||||||
|
public static extern void SWIGRegisterStringCallback_$module(SWIGStringDelegate stringDelegate);
|
||||||
|
|
||||||
|
static string CreateString(string cString) {
|
||||||
|
return cString;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SWIGStringHelper() {
|
||||||
|
SWIGRegisterStringCallback_$module(stringDelegate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static protected SWIGStringHelper swigStringHelper = new SWIGStringHelper();
|
||||||
|
%}
|
||||||
|
|
||||||
|
%insert(runtime) %{
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C"
|
||||||
|
#endif
|
||||||
|
SWIGEXPORT void SWIGSTDCALL SWIGRegisterStringCallback_freeswitch(SWIG_CSharpStringHelperCallback callback) {
|
||||||
|
/* Set this only once, in the main appdomain */
|
||||||
|
if (SWIG_csharp_string_callback_real == NULL) SWIG_csharp_string_callback_real = callback;
|
||||||
|
}
|
||||||
|
char * SWIG_csharp_string_callback(const char * str) {
|
||||||
|
#ifndef _MANAGED
|
||||||
|
// Mono won't transition appdomains properly after the callback, so we force it
|
||||||
|
MonoDomain* dom = mono_domain_get();
|
||||||
|
char* res = SWIG_csharp_string_callback_real(str);
|
||||||
|
mono_domain_set(dom, true);
|
||||||
|
return res;
|
||||||
|
#else
|
||||||
|
return SWIG_csharp_string_callback_real(str);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
%}
|
||||||
|
#endif // SWIG_CSHARP_NO_STRING_HELPER
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** insert the following includes into generated code so it compiles */
|
/** insert the following includes into generated code so it compiles */
|
||||||
%{
|
%{
|
||||||
#include "switch.h"
|
#include "switch.h"
|
||||||
|
@ -109,6 +174,9 @@
|
||||||
%ignore switch_xml_idx;
|
%ignore switch_xml_idx;
|
||||||
%ignore switch_xml_pi;
|
%ignore switch_xml_pi;
|
||||||
|
|
||||||
|
// GCC complains "ISO C++ forbids assignment of arrays"
|
||||||
|
%ignore switch_vmprintf;
|
||||||
|
|
||||||
// Real header includes now
|
// Real header includes now
|
||||||
%import switch_platform.i // This will give us all the macros we need to compile the other stuff
|
%import switch_platform.i // This will give us all the macros we need to compile the other stuff
|
||||||
|
|
||||||
|
@ -136,6 +204,5 @@
|
||||||
%include switch_core_event_hook.h
|
%include switch_core_event_hook.h
|
||||||
%include switch_scheduler.h
|
%include switch_scheduler.h
|
||||||
%include switch_config.h
|
%include switch_config.h
|
||||||
|
|
||||||
%include switch_cpp.h
|
%include switch_cpp.h
|
||||||
%include freeswitch_managed.h
|
%include freeswitch_managed.h
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
|
||||||
// You can specify all the values or you can default the Build and Revision Numbers
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
// by using the '*' as shown below:
|
// by using the '*' as shown below:
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
[assembly: AssemblyVersion("1.0.2.0")]
|
[assembly: AssemblyVersion("1.0.4.0")]
|
||||||
[assembly: AssemblyFileVersion("1.0.2.0")]
|
[assembly: AssemblyFileVersion("1.0.4.0")]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_cli
|
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_managed
|
||||||
* Copyright (C) 2008, Michael Giagnocavo <mgg@packetrino.com>
|
* Copyright (C) 2008, Michael Giagnocavo <mgg@giagnocavo.net>
|
||||||
*
|
*
|
||||||
* Version: MPL 1.1
|
* Version: MPL 1.1
|
||||||
*
|
*
|
||||||
|
@ -14,73 +14,97 @@
|
||||||
* for the specific language governing rights and limitations under the
|
* for the specific language governing rights and limitations under the
|
||||||
* License.
|
* License.
|
||||||
*
|
*
|
||||||
* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_cli
|
* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_managed
|
||||||
*
|
*
|
||||||
* The Initial Developer of the Original Code is
|
* The Initial Developer of the Original Code is
|
||||||
* Michael Giagnocavo <mgg@packetrino.com>
|
* Michael Giagnocavo <mgg@giagnocavo.net>
|
||||||
* Portions created by the Initial Developer are Copyright (C)
|
* Portions created by the Initial Developer are Copyright (C)
|
||||||
* the Initial Developer. All Rights Reserved.
|
* the Initial Developer. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Contributor(s):
|
* Contributor(s):
|
||||||
*
|
*
|
||||||
* Michael Giagnocavo <mgg@packetrino.com>
|
* Michael Giagnocavo <mgg@giagnocavo.net>
|
||||||
*
|
*
|
||||||
* Demo.cs -- mod_mono demo classes
|
* Demo.cs -- mod_mono demo classes
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#if DEBUG
|
// How to test the demo (in the mod/managed directory):
|
||||||
|
// -- Compile to dll for "normal" loading
|
||||||
|
// -- Compile to exe for script EXE loading
|
||||||
|
// -- Rename to demo.csx for dynamic compilation
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using FreeSWITCH;
|
||||||
using System.Linq;
|
using FreeSWITCH.Native;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace FreeSWITCH.Demo {
|
public class AppDemo : IAppPlugin {
|
||||||
public class AppDemo : AppFunction {
|
|
||||||
new protected static bool Load() {
|
|
||||||
Log.WriteLine(LogLevel.Info, "Inside AppDemo::Load.");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Run() {
|
ManagedSession Session;
|
||||||
Session.Answer();
|
public void Run(AppContext context) {
|
||||||
Session.DtmfReceivedFunction = (d, t) => {
|
Session = context.Session;
|
||||||
Log.WriteLine(LogLevel.Info, "Received {0} for {1}.", d, t);
|
Session.HangupFunction = hangupHook;
|
||||||
return "";
|
Session.Answer();
|
||||||
};
|
Session.DtmfReceivedFunction = (d, t) => {
|
||||||
Log.WriteLine(LogLevel.Info, "Inside AppDemo.Run (args '{0}'); HookState is {1}. Now will collect digits.", Arguments, Session.HookState);
|
Log.WriteLine(LogLevel.Notice, "Received {0} for {1}.", d, t);
|
||||||
Session.CollectDigits(5000); // Hanging up here will cause an abort and the next line won't be written
|
return "";
|
||||||
Log.WriteLine(LogLevel.Info, "AppDemo is finishing its run and will now hang up.");
|
};
|
||||||
Session.Hangup("USER_BUSY");
|
Log.WriteLine(LogLevel.Notice, "Inside AppDemo.Run (args '{0}'); HookState is {1}. Now will collect digits.", context.Arguments, Session.HookState);
|
||||||
}
|
Session.CollectDigits(5000); // Hanging up here will cause an abort and the next line won't be written
|
||||||
|
Log.WriteLine(LogLevel.Notice, "AppDemo is finishing its run and will now hang up.");
|
||||||
void hangupHook() {
|
Session.Hangup("USER_BUSY");
|
||||||
Log.WriteLine(LogLevel.Debug, "AppDemo hanging up, UUID: {0}.", this.Uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool AbortOnHangup { get { return true; } }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ApiDemo : ApiFunction {
|
void hangupHook() {
|
||||||
new protected static bool Load() {
|
Log.WriteLine(LogLevel.Notice, "AppDemo hanging up, UUID: {0}.", this.Session.Uuid);
|
||||||
Log.WriteLine(LogLevel.Debug, "Inside ApiDemo::Load.");
|
}
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ExecuteBackground(string args) {
|
}
|
||||||
Log.WriteLine(LogLevel.Debug, "ApiDemo on a background thread #({0}), with args '{1}'.",
|
|
||||||
System.Threading.Thread.CurrentThread.ManagedThreadId,
|
|
||||||
args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Execute(Native.Stream stream, Native.Event evt, string args) {
|
public class ApiDemo : IApiPlugin {
|
||||||
stream.Write(string.Format("ApiDemo executed with args '{0}' and event type {1}.",
|
|
||||||
args, evt == null ? "<none>" : evt.GetEventType()));
|
public void Execute(ApiContext context) {
|
||||||
|
context.Stream.Write(string.Format("ApiDemo executed with args '{0}' and event type {1}.",
|
||||||
|
context.Arguments, context.Event == null ? "<none>" : context.Event.GetEventType()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ExecuteBackground(ApiBackgroundContext context) {
|
||||||
|
Log.WriteLine(LogLevel.Notice, "ApiDemo on a background thread #({0}), with args '{1}'.",
|
||||||
|
System.Threading.Thread.CurrentThread.ManagedThreadId,
|
||||||
|
context.Arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LoadDemo : ILoadNotificationPlugin {
|
||||||
|
public bool Load() {
|
||||||
|
Log.WriteLine(LogLevel.Notice, "LoadDemo running.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ScriptDemo {
|
||||||
|
|
||||||
|
static void Main() {
|
||||||
|
switch (FreeSWITCH.Script.ContextType) {
|
||||||
|
case ScriptContextType.Api: {
|
||||||
|
var ctx = FreeSWITCH.Script.GetApiContext();
|
||||||
|
ctx.Stream.Write("Script executing as API with args: " + ctx.Arguments);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ScriptContextType.ApiBackground: {
|
||||||
|
var ctx = FreeSWITCH.Script.GetApiBackgroundContext();
|
||||||
|
Log.WriteLine(LogLevel.Notice, "Executing as APIBackground with args: " + ctx.Arguments);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ScriptContextType.App: {
|
||||||
|
var ctx = FreeSWITCH.Script.GetAppContext();
|
||||||
|
Log.WriteLine(LogLevel.Notice, "Executing as App with args: " + ctx.Arguments);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_cli
|
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_managed
|
||||||
* Copyright (C) 2008, Michael Giagnocavo <mgg@packetrino.com>
|
* Copyright (C) 2008, Michael Giagnocavo <mgg@giagnocavo.net>
|
||||||
*
|
*
|
||||||
* Version: MPL 1.1
|
* Version: MPL 1.1
|
||||||
*
|
*
|
||||||
|
@ -14,16 +14,16 @@
|
||||||
* for the specific language governing rights and limitations under the
|
* for the specific language governing rights and limitations under the
|
||||||
* License.
|
* License.
|
||||||
*
|
*
|
||||||
* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_cli
|
* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_managed
|
||||||
*
|
*
|
||||||
* The Initial Developer of the Original Code is
|
* The Initial Developer of the Original Code is
|
||||||
* Michael Giagnocavo <mgg@packetrino.com>
|
* Michael Giagnocavo <mgg@giagnocavo.net>
|
||||||
* Portions created by the Initial Developer are Copyright (C)
|
* Portions created by the Initial Developer are Copyright (C)
|
||||||
* the Initial Developer. All Rights Reserved.
|
* the Initial Developer. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Contributor(s):
|
* Contributor(s):
|
||||||
*
|
*
|
||||||
* Michael Giagnocavo <mgg@packetrino.com>
|
* Michael Giagnocavo <mgg@giagnocavo.net>
|
||||||
*
|
*
|
||||||
* Extensions.cs -- Helper extensions
|
* Extensions.cs -- Helper extensions
|
||||||
*
|
*
|
||||||
|
|
|
@ -52,9 +52,10 @@
|
||||||
<Compile Include="Loader.cs" />
|
<Compile Include="Loader.cs" />
|
||||||
<Compile Include="Extensions.cs" />
|
<Compile Include="Extensions.cs" />
|
||||||
<Compile Include="Log.cs" />
|
<Compile Include="Log.cs" />
|
||||||
<Compile Include="ApiFunction.cs" />
|
<None Include="Demo.cs" />
|
||||||
<Compile Include="AppFunction.cs" />
|
<Compile Include="PluginInterfaces.cs" />
|
||||||
<Compile Include="Demo.cs" />
|
<Compile Include="PluginManager.cs" />
|
||||||
|
<Compile Include="ScriptPluginManager.cs" />
|
||||||
<Compile Include="swig.cs" />
|
<Compile Include="swig.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_cli
|
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_managed
|
||||||
* Copyright (C) 2008, Michael Giagnocavo <mgg@packetrino.com>
|
* Copyright (C) 2008, Michael Giagnocavo <mgg@giagnocavo.net>
|
||||||
*
|
*
|
||||||
* Version: MPL 1.1
|
* Version: MPL 1.1
|
||||||
*
|
*
|
||||||
|
@ -14,19 +14,20 @@
|
||||||
* for the specific language governing rights and limitations under the
|
* for the specific language governing rights and limitations under the
|
||||||
* License.
|
* License.
|
||||||
*
|
*
|
||||||
* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_cli
|
* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_managed
|
||||||
*
|
*
|
||||||
* The Initial Developer of the Original Code is
|
* The Initial Developer of the Original Code is
|
||||||
* Michael Giagnocavo <mgg@packetrino.com>
|
* Michael Giagnocavo <mgg@giagnocavo.net>
|
||||||
* Portions created by the Initial Developer are Copyright (C)
|
* Portions created by the Initial Developer are Copyright (C)
|
||||||
* the Initial Developer. All Rights Reserved.
|
* the Initial Developer. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Contributor(s):
|
* Contributor(s):
|
||||||
*
|
*
|
||||||
* Michael Giagnocavo <mgg@packetrino.com>
|
* Michael Giagnocavo <mgg@giagnocavo.net>
|
||||||
* David Brazier <David.Brazier@360crm.co.uk>
|
* David Brazier <David.Brazier@360crm.co.uk>
|
||||||
|
* Jeff Lenk <jeff@jefflenk.com>
|
||||||
*
|
*
|
||||||
* Loader.cs -- mod_mono managed loader
|
* Loader.cs -- mod_managed loader
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -38,22 +39,35 @@ using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace FreeSWITCH
|
namespace FreeSWITCH {
|
||||||
{
|
internal static class Loader {
|
||||||
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
|
// Thunks
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] delegate bool ExecuteDelegate(string cmd, IntPtr streamH, IntPtr eventH);
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] delegate bool ExecuteBackgroundDelegate(string cmd);
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] delegate bool RunDelegate(string cmd, IntPtr session);
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] delegate bool ReloadDelegate(string cmd);
|
||||||
|
static readonly ExecuteDelegate _execute = Execute;
|
||||||
|
static readonly ExecuteBackgroundDelegate _executeBackground = ExecuteBackground;
|
||||||
|
static readonly RunDelegate _run = Run;
|
||||||
|
static readonly ReloadDelegate _reload = Reload;
|
||||||
|
//SWITCH_MOD_DECLARE_NONSTD(void) InitManagedDelegates(runFunction run, executeFunction execute, executeBackgroundFunction executeBackground, reloadFunction reload)
|
||||||
|
[DllImport("mod_managed", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
|
||||||
|
static extern void InitManagedDelegates(RunDelegate run, ExecuteDelegate execute, ExecuteBackgroundDelegate executeBackground, ReloadDelegate reload);
|
||||||
|
|
||||||
|
static readonly object loaderLock = new object();
|
||||||
|
|
||||||
static string managedDir;
|
static string managedDir;
|
||||||
|
static string shadowDir;
|
||||||
public static bool Load()
|
|
||||||
{
|
public static bool Load() {
|
||||||
managedDir = Path.Combine(Native.freeswitch.SWITCH_GLOBAL_dirs.mod_dir, "managed");
|
managedDir = Path.Combine(Native.freeswitch.SWITCH_GLOBAL_dirs.mod_dir, "managed");
|
||||||
|
shadowDir = Path.Combine(managedDir, "shadow");
|
||||||
|
if (Directory.Exists(shadowDir)) {
|
||||||
|
Directory.Delete(shadowDir, true);
|
||||||
|
Directory.CreateDirectory(shadowDir);
|
||||||
|
}
|
||||||
|
|
||||||
Log.WriteLine(LogLevel.Debug, "FreeSWITCH.Managed loader is starting with directory '{0}'.", managedDir);
|
Log.WriteLine(LogLevel.Debug, "FreeSWITCH.Managed loader is starting with directory '{0}'.", managedDir);
|
||||||
if (!Directory.Exists(managedDir)) {
|
if (!Directory.Exists(managedDir)) {
|
||||||
Log.WriteLine(LogLevel.Error, "Managed directory not found: {0}", managedDir);
|
Log.WriteLine(LogLevel.Error, "Managed directory not found: {0}", managedDir);
|
||||||
|
@ -61,147 +75,238 @@ namespace FreeSWITCH
|
||||||
}
|
}
|
||||||
|
|
||||||
AppDomain.CurrentDomain.AssemblyResolve += (_, rargs) => {
|
AppDomain.CurrentDomain.AssemblyResolve += (_, rargs) => {
|
||||||
Log.WriteLine(LogLevel.Debug, "Trying to resolve assembly '{0}'.", rargs.Name);
|
Log.WriteLine(LogLevel.Info, "Resolving assembly '{0}'.", rargs.Name);
|
||||||
if (rargs.Name == Assembly.GetExecutingAssembly().FullName) return Assembly.GetExecutingAssembly();
|
if (rargs.Name == Assembly.GetExecutingAssembly().FullName) return Assembly.GetExecutingAssembly();
|
||||||
var path = Path.Combine(managedDir, rargs.Name + ".dll");
|
var parts = rargs.Name.Split(',');
|
||||||
Log.WriteLine(LogLevel.Debug, "Resolving to: '" + path + "'.");
|
var path = Path.Combine(managedDir, parts[0] + ".dll");
|
||||||
|
Log.WriteLine(LogLevel.Info, "Resolving to: '" + path + "'.");
|
||||||
return File.Exists(path) ? Assembly.LoadFile(path) : null;
|
return File.Exists(path) ? Assembly.LoadFile(path) : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
InitManagedDelegates(_run, _execute, _executeBackground, _loadAssembly);
|
InitManagedDelegates(_run, _execute, _executeBackground, _reload);
|
||||||
|
|
||||||
// This is a simple one-time loader to get things in memory
|
configureWatcher();
|
||||||
// Some day we should allow reloading of modules or something
|
|
||||||
var allTypes = loadAssemblies(managedDir).SelectMany(a => a.GetExportedTypes());
|
|
||||||
loadFunctions(allTypes);
|
|
||||||
|
|
||||||
|
// Initial load
|
||||||
|
var allFiles = Directory.GetFiles(managedDir);
|
||||||
|
foreach (var file in allFiles) {
|
||||||
|
try {
|
||||||
|
loadFile(file);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.WriteLine(LogLevel.Error, "Exception loading file {0}: " + ex.ToString(), file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
initialLoadComplete = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool LoadAssembly(string filename) {
|
|
||||||
try {
|
// *** File watcher for changes
|
||||||
string path = Path.Combine(managedDir, filename);
|
// Cheap queue hack is used because multiple related files can generate a bunch of changes
|
||||||
if (!File.Exists(path)) {
|
// and a single file can also trigger a few notifications. With a simple queue, these get batched
|
||||||
Log.WriteLine(LogLevel.Error, "File not found: '{0}'.", path);
|
static readonly object watcherLock = new object();
|
||||||
return false;
|
static FileSystemWatcher watcher;
|
||||||
|
static System.Threading.Timer watcherTimer;
|
||||||
|
static HashSet<string> watcherFiles = new HashSet<string>();
|
||||||
|
static void configureWatcher() {
|
||||||
|
watcher = new FileSystemWatcher(managedDir);
|
||||||
|
watcher.IncludeSubdirectories = false;
|
||||||
|
watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite;
|
||||||
|
watcher.EnableRaisingEvents = true;
|
||||||
|
watcher.Changed += watcher_Changed;
|
||||||
|
watcher.Created += watcher_Changed;
|
||||||
|
watcher.Deleted += watcher_Changed;
|
||||||
|
watcher.Renamed += watcher_Changed;
|
||||||
|
watcherTimer = new System.Threading.Timer(_ => {
|
||||||
|
lock (watcherLock) {
|
||||||
|
foreach (var file in watcherFiles) {
|
||||||
|
try {
|
||||||
|
loadFile(file);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.WriteLine(LogLevel.Error, "Exception loading change from {0}: {1}", file, ex.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
watcherFiles.Clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
watcherTimer.Change(1000, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void watcher_Changed(object sender, FileSystemEventArgs e) {
|
||||||
|
Action<string> queueFile = x => { lock (watcherLock) { watcherFiles.Add(x); } };
|
||||||
|
try {
|
||||||
|
if (!initialLoadComplete) return;
|
||||||
|
var file = e.FullPath;
|
||||||
|
var isConfig = Path.GetExtension(file).ToLowerInvariant() == ".config";
|
||||||
|
if (isConfig) {
|
||||||
|
queueFile(Path.ChangeExtension(file, null));
|
||||||
|
} else {
|
||||||
|
queueFile(file);
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.WriteLine(LogLevel.Critical, "Unhandled exception watching for changes: " + ex.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static volatile bool initialLoadComplete = false;
|
||||||
|
static readonly List<PluginInfo> pluginInfos = new List<PluginInfo>();
|
||||||
|
// Volatile cause we haven't streamlined all the locking
|
||||||
|
// These dictionaries are never mutated; the reference is changed instead
|
||||||
|
static volatile Dictionary<string, AppPluginExecutor> appExecs = new Dictionary<string, AppPluginExecutor>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
static volatile Dictionary<string, ApiPluginExecutor> apiExecs = new Dictionary<string, ApiPluginExecutor>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
static Dictionary<string, AppPluginExecutor> getAppExecs() {
|
||||||
|
lock (loaderLock) {
|
||||||
|
return appExecs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static Dictionary<string, ApiPluginExecutor> getApiExecs() {
|
||||||
|
lock (loaderLock) {
|
||||||
|
return apiExecs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PluginInfo {
|
||||||
|
public string FileName { get; set; }
|
||||||
|
public AppDomain Domain { get; set; }
|
||||||
|
public PluginManager Manager { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
static int appDomainCount = 0;
|
||||||
|
static void loadFile(string fileName) {
|
||||||
|
// Attempts to load the file. On failure, it will call unload.
|
||||||
|
// Loading part does not take out a lock.
|
||||||
|
// Lock is only done after loading is finished and dictionaries need updating.
|
||||||
|
|
||||||
|
// We might get a load for a file that's no longer there. Just unload the old one.
|
||||||
|
if (!File.Exists(fileName)) {
|
||||||
|
unloadFile(fileName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Type pmType;
|
||||||
|
switch (Path.GetExtension(fileName).ToLowerInvariant()) {
|
||||||
|
case ".dll":
|
||||||
|
pmType = typeof(AsmPluginManager);
|
||||||
|
break;
|
||||||
|
case ".exe": // TODO these need to come from config
|
||||||
|
case ".fsx":
|
||||||
|
case ".vbx":
|
||||||
|
case ".csx":
|
||||||
|
case ".jsx":
|
||||||
|
pmType = typeof(ScriptPluginManager);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
pmType = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (pmType == null) return;
|
||||||
|
|
||||||
|
// App domain setup
|
||||||
|
var setup = new AppDomainSetup();
|
||||||
|
if (File.Exists(fileName + ".config")) {
|
||||||
|
setup.ConfigurationFile = fileName + ".config";
|
||||||
|
}
|
||||||
|
setup.ApplicationBase = Native.freeswitch.SWITCH_GLOBAL_dirs.mod_dir;
|
||||||
|
setup.ShadowCopyDirectories = managedDir + ";";
|
||||||
|
setup.LoaderOptimization = LoaderOptimization.MultiDomainHost; // TODO: would MultiDomain work better since FreeSWITCH.Managed isn't gac'd?
|
||||||
|
setup.CachePath = shadowDir;
|
||||||
|
setup.ShadowCopyFiles = "true";
|
||||||
|
setup.PrivateBinPath = "managed";
|
||||||
|
|
||||||
|
// Create domain and load PM inside
|
||||||
|
System.Threading.Interlocked.Increment(ref appDomainCount);
|
||||||
|
setup.ApplicationName = Path.GetFileName(fileName) + "_" + appDomainCount;
|
||||||
|
var domain = AppDomain.CreateDomain(setup.ApplicationName, null, setup);
|
||||||
|
|
||||||
|
var pm = (PluginManager)domain.CreateInstanceAndUnwrap(pmType.Assembly.FullName, pmType.FullName, null);
|
||||||
|
if (!pm.Load(fileName)) {
|
||||||
|
AppDomain.Unload(domain);
|
||||||
|
unloadFile(fileName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (loaderLock) {
|
||||||
|
// Update dictionaries atomically
|
||||||
|
unloadFile(fileName);
|
||||||
|
|
||||||
|
var pi = new PluginInfo { FileName = fileName, Domain = domain, Manager = pm };
|
||||||
|
pluginInfos.Add(pi);
|
||||||
|
var newAppExecs = new Dictionary<string, AppPluginExecutor>(appExecs, StringComparer.OrdinalIgnoreCase);
|
||||||
|
var newApiExecs = new Dictionary<string, ApiPluginExecutor>(apiExecs, StringComparer.OrdinalIgnoreCase);
|
||||||
|
pm.AppExecutors.ForEach(x => x.Aliases.ForEach(y => newAppExecs[y] = x));
|
||||||
|
pm.ApiExecutors.ForEach(x => x.Aliases.ForEach(y => newApiExecs[y] = x));
|
||||||
|
appExecs = newAppExecs;
|
||||||
|
apiExecs = newApiExecs;
|
||||||
|
Action<PluginExecutor, string> printLoaded = (pe, type) => {
|
||||||
|
var aliases = pe.Aliases.Aggregate((acc, x) => acc += ", " + x);
|
||||||
|
Log.WriteLine(LogLevel.Notice, "Loaded {3} {0}, aliases '{1}', into domain {2}.", pe.Name, aliases, pi.Domain.FriendlyName, type);
|
||||||
|
};
|
||||||
|
pm.AppExecutors.ForEach(x => printLoaded(x, "App"));
|
||||||
|
pm.ApiExecutors.ForEach(x => printLoaded(x, "Api"));
|
||||||
|
Log.WriteLine(LogLevel.Info, "Finished loading {0} into domain {1}.", pi.FileName, pi.Domain.FriendlyName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void unloadFile(string fileName) {
|
||||||
|
List<PluginInfo> pisToRemove;
|
||||||
|
lock (loaderLock) {
|
||||||
|
pisToRemove = pluginInfos.Where(x => string.Compare(fileName, x.FileName, StringComparison.OrdinalIgnoreCase) == 0).ToList();
|
||||||
|
if (pisToRemove.Count == 0) return; // Done
|
||||||
|
|
||||||
|
var apisToRemove = pisToRemove.SelectMany(x => x.Manager.ApiExecutors).ToList();
|
||||||
|
var appsToRemove = pisToRemove.SelectMany(x => x.Manager.AppExecutors).ToList();
|
||||||
|
pluginInfos.RemoveAll(pisToRemove.Contains);
|
||||||
|
appExecs = appExecs.Where(x => !appsToRemove.Contains(x.Value)).ToDictionary(x => x.Key, x => x.Value, StringComparer.OrdinalIgnoreCase);
|
||||||
|
apiExecs = apiExecs.Where(x => !apisToRemove.Contains(x.Value)).ToDictionary(x => x.Key, x => x.Value, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
Action<PluginExecutor, string> printRemoved = (pe, type) => {
|
||||||
|
Log.WriteLine(LogLevel.Notice, "Unloaded {0} {1} (file {2}).", type, pe.Name, fileName);
|
||||||
|
};
|
||||||
|
apisToRemove.ForEach(x => printRemoved(x, "API"));
|
||||||
|
appsToRemove.ForEach(x => printRemoved(x, "App"));
|
||||||
|
}
|
||||||
|
|
||||||
|
pisToRemove.ForEach(pi => {
|
||||||
|
var t = new System.Threading.Thread(() => {
|
||||||
|
var friendlyName = pi.Domain.FriendlyName;
|
||||||
|
Log.WriteLine(LogLevel.Info, "Starting to unload {0}, domain {1}.", pi.FileName, friendlyName);
|
||||||
|
try {
|
||||||
|
var d = pi.Domain;
|
||||||
|
pi.Manager.BlockUntilUnloadIsSafe();
|
||||||
|
pi.Manager = null;
|
||||||
|
pi.Domain = null;
|
||||||
|
AppDomain.Unload(d);
|
||||||
|
Log.WriteLine(LogLevel.Info, "Unloaded {0}, domain {1}.", pi.FileName, friendlyName);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.WriteLine(LogLevel.Critical, "Could not unload {0}, domain {1}: {2}", pi.FileName, friendlyName, ex.ToString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
t.Priority = System.Threading.ThreadPriority.BelowNormal;
|
||||||
|
t.IsBackground = true;
|
||||||
|
t.Start();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool Reload(string cmd) {
|
||||||
|
try {
|
||||||
|
if (Path.IsPathRooted(cmd)) {
|
||||||
|
loadFile(cmd);
|
||||||
|
} else {
|
||||||
|
loadFile(Path.Combine(managedDir, cmd));
|
||||||
}
|
}
|
||||||
var asm = Assembly.LoadFile(path);
|
|
||||||
loadFunctions(asm.GetExportedTypes());
|
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Log.WriteLine(LogLevel.Error, "Exception in LoadAssembly('{0}'): {1}", filename, ex.ToString());
|
Log.WriteLine(LogLevel.Error, "Error reloading {0}: {1}", cmd, ex.ToString());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
// ******* Execution
|
||||||
delegate bool ExecuteDelegate(string cmd, IntPtr streamH, IntPtr eventH);
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
||||||
delegate bool ExecuteBackgroundDelegate(string cmd);
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
||||||
delegate bool RunDelegate(string cmd, IntPtr session);
|
|
||||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
||||||
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, CallingConvention = CallingConvention.Cdecl)]
|
|
||||||
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, "FreeSWITCH.Managed 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[] { ' ' };
|
static readonly char[] spaceArray = new[] { ' ' };
|
||||||
/// <summary>Returns a string couple containing the module name and arguments</summary>
|
/// <summary>Returns a string couple containing the module name and arguments</summary>
|
||||||
static string[] parseCommand(string command)
|
static string[] parseCommand(string command) {
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(command)) {
|
if (string.IsNullOrEmpty(command)) {
|
||||||
Log.WriteLine(LogLevel.Error, "No arguments supplied.");
|
Log.WriteLine(LogLevel.Error, "No arguments supplied.");
|
||||||
return null;
|
return null;
|
||||||
|
@ -213,100 +318,73 @@ namespace FreeSWITCH
|
||||||
}
|
}
|
||||||
if (args.Length == 1) {
|
if (args.Length == 1) {
|
||||||
return new[] { args[0], "" };
|
return new[] { args[0], "" };
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool ExecuteBackground(string command)
|
public static bool ExecuteBackground(string command) {
|
||||||
{
|
|
||||||
try {
|
try {
|
||||||
var parsed = parseCommand(command);
|
var parsed = parseCommand(command);
|
||||||
if (parsed == null) return false;
|
if (parsed == null) return false;
|
||||||
var fullName = parsed[0];
|
var fullName = parsed[0];
|
||||||
var args = parsed[1];
|
var args = parsed[1];
|
||||||
|
var execs = getApiExecs();
|
||||||
var fType = getFunctionType<ApiFunction>(fullName);
|
ApiPluginExecutor exec;
|
||||||
if (fType == null) return false;
|
if (!execs.TryGetValue(fullName, out exec)) {
|
||||||
|
Log.WriteLine(LogLevel.Error, "API plugin {0} not found.", fullName);
|
||||||
new System.Threading.Thread(() => {
|
return false;
|
||||||
try {
|
}
|
||||||
var f = (ApiFunction)Activator.CreateInstance(fType);
|
return exec.ExecuteApiBackground(args);
|
||||||
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) {
|
} catch (Exception ex) {
|
||||||
Log.WriteLine(LogLevel.Error, "Exception in ExecuteBackground({0}): {1}", command, ex.ToString());
|
Log.WriteLine(LogLevel.Error, "Exception in ExecuteBackground({0}): {1}", command, ex.ToString());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool Execute(string command, IntPtr streamHandle, IntPtr eventHandle)
|
public static bool Execute(string command, IntPtr streamHandle, IntPtr eventHandle) {
|
||||||
{
|
try {
|
||||||
System.Diagnostics.Debug.Assert(streamHandle != IntPtr.Zero, "streamHandle is null.");
|
System.Diagnostics.Debug.Assert(streamHandle != IntPtr.Zero, "streamHandle is null.");
|
||||||
var parsed = parseCommand(command);
|
var parsed = parseCommand(command);
|
||||||
if (parsed == null) return false;
|
if (parsed == null) return false;
|
||||||
var fullName = parsed[0];
|
var fullName = parsed[0];
|
||||||
var args = parsed[1];
|
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)))
|
var execs = getApiExecs();
|
||||||
using (var evt = eventHandle == IntPtr.Zero ? null : new Native.Event(new Native.switch_event(eventHandle, false), 0)) {
|
ApiPluginExecutor exec;
|
||||||
|
if (!execs.TryGetValue(fullName, out exec)) {
|
||||||
try {
|
Log.WriteLine(LogLevel.Error, "API plugin {0} not found.", fullName);
|
||||||
var f = (ApiFunction)Activator.CreateInstance(fType);
|
|
||||||
f.Execute(stream, evt, args);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception ex) {
|
|
||||||
logException("Execute", fullName, ex);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
var res = exec.ExecuteApi(args, streamHandle, eventHandle);
|
||||||
|
return res;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.WriteLine(LogLevel.Error, "Exception in Execute({0}): {1}", command, ex.ToString());
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Runs an application function.</summary>
|
public static bool Run(string command, IntPtr sessionHandle) {
|
||||||
public static bool Run(string command, IntPtr sessionHandle)
|
try {
|
||||||
{
|
Log.WriteLine(LogLevel.Debug, "FreeSWITCH.Managed: attempting to run application '{0}'.", command);
|
||||||
Log.WriteLine(LogLevel.Debug, "FreeSWITCH.Managed: attempting to run application '{0}'.", command);
|
System.Diagnostics.Debug.Assert(sessionHandle != IntPtr.Zero, "sessionHandle is null.");
|
||||||
System.Diagnostics.Debug.Assert(sessionHandle != IntPtr.Zero, "sessionHandle is null.");
|
var parsed = parseCommand(command);
|
||||||
var parsed = parseCommand(command);
|
if (parsed == null) return false;
|
||||||
if (parsed == null) return false;
|
var fullName = parsed[0];
|
||||||
var fullName = parsed[0];
|
var args = parsed[1];
|
||||||
var args = parsed[1];
|
|
||||||
|
|
||||||
var fType = getFunctionType<AppFunction>(fullName);
|
AppPluginExecutor exec;
|
||||||
if (fType == null) return false;
|
var execs = getAppExecs();
|
||||||
|
if (!execs.TryGetValue(fullName, out exec)) {
|
||||||
using (var session = new Native.ManagedSession(new Native.SWIGTYPE_p_switch_core_session(sessionHandle, false))) {
|
Log.WriteLine(LogLevel.Error, "App plugin {0} not found.", fullName);
|
||||||
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
return exec.Execute(args, sessionHandle);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.WriteLine(LogLevel.Error, "Exception in Run({0}): {1}", command, ex.ToString());
|
||||||
|
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_cli
|
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_managed
|
||||||
* Copyright (C) 2008, Michael Giagnocavo <mgg@packetrino.com>
|
* Copyright (C) 2008, Michael Giagnocavo <mgg@giagnocavo.net>
|
||||||
*
|
*
|
||||||
* Version: MPL 1.1
|
* Version: MPL 1.1
|
||||||
*
|
*
|
||||||
|
@ -14,16 +14,16 @@
|
||||||
* for the specific language governing rights and limitations under the
|
* for the specific language governing rights and limitations under the
|
||||||
* License.
|
* License.
|
||||||
*
|
*
|
||||||
* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_cli
|
* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_managed
|
||||||
*
|
*
|
||||||
* The Initial Developer of the Original Code is
|
* The Initial Developer of the Original Code is
|
||||||
* Michael Giagnocavo <mgg@packetrino.com>
|
* Michael Giagnocavo <mgg@giagnocavo.net>
|
||||||
* Portions created by the Initial Developer are Copyright (C)
|
* Portions created by the Initial Developer are Copyright (C)
|
||||||
* the Initial Developer. All Rights Reserved.
|
* the Initial Developer. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Contributor(s):
|
* Contributor(s):
|
||||||
*
|
*
|
||||||
* Michael Giagnocavo <mgg@packetrino.com>
|
* Michael Giagnocavo <mgg@giagnocavo.net>
|
||||||
*
|
*
|
||||||
* Log.cs -- Log wrappers
|
* Log.cs -- Log wrappers
|
||||||
*
|
*
|
||||||
|
|
|
@ -3,8 +3,8 @@ all: FreeSWITCH.Managed.dll
|
||||||
clean:
|
clean:
|
||||||
rm -fr FreeSWITCH.Managed.dll
|
rm -fr FreeSWITCH.Managed.dll
|
||||||
|
|
||||||
FreeSWITCH.Managed.dll: Loader.cs ManagedSession.cs ApiFunction.cs AppFunction.cs Extensions.cs Log.cs Demo.cs swig.cs
|
FreeSWITCH.Managed.dll: AssemblyInfo.cs Extensions.cs Loader.cs Log.cs ManagedSession.cs PluginInterfaces.cs PluginManager.cs ScriptPluginManager.cs swig.cs
|
||||||
gmcs -target:library -out:FreeSWITCH.Managed.dll -d:DEBUG Loader.cs ManagedSession.cs ApiFunction.cs AppFunction.cs Extensions.cs Log.cs Demo.cs swig.cs
|
gmcs -target:library -out:FreeSWITCH.Managed.dll AssemblyInfo.cs Extensions.cs Loader.cs Log.cs ManagedSession.cs PluginInterfaces.cs PluginManager.cs ScriptPluginManager.cs swig.cs
|
||||||
|
|
||||||
install: FreeSWITCH.Managed.dll
|
install: FreeSWITCH.Managed.dll
|
||||||
$(INSTALL) FreeSWITCH.Managed.dll $(DESTDIR)$(MODINSTDIR)
|
$(INSTALL) FreeSWITCH.Managed.dll $(DESTDIR)$(MODINSTDIR)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_cli
|
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_managed
|
||||||
* Copyright (C) 2008, Michael Giagnocavo <mgg@packetrino.com>
|
* Copyright (C) 2008, Michael Giagnocavo <mgg@giagnocavo.net>
|
||||||
*
|
*
|
||||||
* Version: MPL 1.1
|
* Version: MPL 1.1
|
||||||
*
|
*
|
||||||
|
@ -14,16 +14,16 @@
|
||||||
* for the specific language governing rights and limitations under the
|
* for the specific language governing rights and limitations under the
|
||||||
* License.
|
* License.
|
||||||
*
|
*
|
||||||
* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_cli
|
* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_managed
|
||||||
*
|
*
|
||||||
* The Initial Developer of the Original Code is
|
* The Initial Developer of the Original Code is
|
||||||
* Michael Giagnocavo <mgg@packetrino.com>
|
* Michael Giagnocavo <mgg@giagnocavo.net>
|
||||||
* Portions created by the Initial Developer are Copyright (C)
|
* Portions created by the Initial Developer are Copyright (C)
|
||||||
* the Initial Developer. All Rights Reserved.
|
* the Initial Developer. All Rights Reserved.
|
||||||
*
|
*
|
||||||
* Contributor(s):
|
* Contributor(s):
|
||||||
*
|
*
|
||||||
* Michael Giagnocavo <mgg@packetrino.com>
|
* Michael Giagnocavo <mgg@giagnocavo.net>
|
||||||
*
|
*
|
||||||
* ManagedSession.cs -- ManagedSession additional functions
|
* ManagedSession.cs -- ManagedSession additional functions
|
||||||
*
|
*
|
||||||
|
@ -66,14 +66,10 @@ namespace FreeSWITCH.Native
|
||||||
/// <summary>Function to execute when this session hangs up.</summary>
|
/// <summary>Function to execute when this session hangs up.</summary>
|
||||||
public Action HangupFunction { get; set; }
|
public Action HangupFunction { get; set; }
|
||||||
|
|
||||||
/// <summary>Sets the application that should have it's run thread aborted (if enabled) when this session is hungup.</summary>
|
|
||||||
internal AppFunction AppToAbort { get; set; }
|
|
||||||
|
|
||||||
void hangupCallback()
|
void hangupCallback()
|
||||||
{
|
{
|
||||||
Log.WriteLine(LogLevel.Debug, "AppFunction is in hangupCallback.");
|
Log.WriteLine(LogLevel.Debug, "AppFunction is in hangupCallback.");
|
||||||
try {
|
try {
|
||||||
if (AppToAbort != null) AppToAbort.AbortRun();
|
|
||||||
var f = HangupFunction;
|
var f = HangupFunction;
|
||||||
if (f != null) f();
|
if (f != null) f();
|
||||||
}
|
}
|
||||||
|
@ -121,5 +117,21 @@ namespace FreeSWITCH.Native
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convenience
|
||||||
|
public bool IsAvailable {
|
||||||
|
get { return this.Ready(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
Guid _uuid;
|
||||||
|
bool _uuidSet;
|
||||||
|
public Guid Uuid {
|
||||||
|
get {
|
||||||
|
if (!_uuidSet) {
|
||||||
|
_uuid = new Guid(this.GetUuid());
|
||||||
|
_uuidSet = true;
|
||||||
|
}
|
||||||
|
return _uuid;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -54,7 +54,7 @@ SWITCH_MODULE_DEFINITION_EX(mod_managed, mod_managed_load, NULL, NULL, SMODF_GLO
|
||||||
SWITCH_STANDARD_API(managedrun_api_function); /* ExecuteBackground */
|
SWITCH_STANDARD_API(managedrun_api_function); /* ExecuteBackground */
|
||||||
SWITCH_STANDARD_API(managed_api_function); /* Execute */
|
SWITCH_STANDARD_API(managed_api_function); /* Execute */
|
||||||
SWITCH_STANDARD_APP(managed_app_function); /* Run */
|
SWITCH_STANDARD_APP(managed_app_function); /* Run */
|
||||||
SWITCH_STANDARD_API(managed_loadassembly); /* Load assembly */
|
SWITCH_STANDARD_API(managedreload_api_function); /* Reload */
|
||||||
|
|
||||||
#define MOD_MANAGED_ASM_NAME "FreeSWITCH.Managed"
|
#define MOD_MANAGED_ASM_NAME "FreeSWITCH.Managed"
|
||||||
#define MOD_MANAGED_ASM_V1 1
|
#define MOD_MANAGED_ASM_V1 1
|
||||||
|
@ -71,18 +71,18 @@ mod_managed_globals globals = { 0 };
|
||||||
typedef int (*runFunction)(const char *data, void *sessionPtr);
|
typedef int (*runFunction)(const char *data, void *sessionPtr);
|
||||||
typedef int (*executeFunction)(const char *cmd, void *stream, void *Event);
|
typedef int (*executeFunction)(const char *cmd, void *stream, void *Event);
|
||||||
typedef int (*executeBackgroundFunction)(const char* cmd);
|
typedef int (*executeBackgroundFunction)(const char* cmd);
|
||||||
typedef int (*loadAssemblyFunction)(const char* filename);
|
typedef int (*reloadFunction)(const char* cmd);
|
||||||
static runFunction runDelegate;
|
static runFunction runDelegate;
|
||||||
static executeFunction executeDelegate;
|
static executeFunction executeDelegate;
|
||||||
static executeBackgroundFunction executeBackgroundDelegate;
|
static executeBackgroundFunction executeBackgroundDelegate;
|
||||||
static loadAssemblyFunction loadAssemblyDelegate;
|
static reloadFunction reloadDelegate;
|
||||||
|
|
||||||
SWITCH_MOD_DECLARE_NONSTD(void) InitManagedDelegates(runFunction run, executeFunction execute, executeBackgroundFunction executeBackground, loadAssemblyFunction loadAssembly)
|
SWITCH_MOD_DECLARE_NONSTD(void) InitManagedDelegates(runFunction run, executeFunction execute, executeBackgroundFunction executeBackground, reloadFunction reload)
|
||||||
{
|
{
|
||||||
runDelegate = run;
|
runDelegate = run;
|
||||||
executeDelegate = execute;
|
executeDelegate = execute;
|
||||||
executeBackgroundDelegate = executeBackground;
|
executeBackgroundDelegate = executeBackground;
|
||||||
loadAssemblyDelegate = loadAssembly;
|
reloadDelegate = reload;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets up delegates (and anything else needed) on the ManagedSession object
|
// Sets up delegates (and anything else needed) on the ManagedSession object
|
||||||
|
@ -362,7 +362,7 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_managed_load)
|
||||||
SWITCH_ADD_API(api_interface, "managedrun", "Run a module (ExecuteBackground)", managedrun_api_function, "<module> [<args>]");
|
SWITCH_ADD_API(api_interface, "managedrun", "Run a module (ExecuteBackground)", managedrun_api_function, "<module> [<args>]");
|
||||||
SWITCH_ADD_API(api_interface, "managed", "Run a module as an API function (Execute)", managed_api_function, "<module> [<args>]");
|
SWITCH_ADD_API(api_interface, "managed", "Run a module as an API function (Execute)", managed_api_function, "<module> [<args>]");
|
||||||
SWITCH_ADD_APP(app_interface, "managed", "Run CLI App", "Run an App on a channel", managed_app_function, "<modulename> [<args>]", SAF_NONE);
|
SWITCH_ADD_APP(app_interface, "managed", "Run CLI App", "Run an App on a channel", managed_app_function, "<modulename> [<args>]", SAF_NONE);
|
||||||
SWITCH_ADD_API(api_interface, "managedload", "Load assembly", managed_loadassembly, "<filename>");
|
SWITCH_ADD_API(api_interface, "managedreload", "Force [re]load of a file", managedreload_api_function, "<filename>");
|
||||||
return SWITCH_STATUS_SUCCESS;
|
return SWITCH_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -406,16 +406,14 @@ SWITCH_STANDARD_APP(managed_app_function)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SWITCH_STANDARD_API(managed_loadassembly)
|
SWITCH_STANDARD_API(managedreload_api_function)
|
||||||
{
|
{
|
||||||
if (switch_strlen_zero(cmd)) {
|
if (switch_strlen_zero(cmd)) {
|
||||||
stream->write_function(stream, "-ERR no args specified!\n");
|
stream->write_function(stream, "-ERR no args specified!\n");
|
||||||
return SWITCH_STATUS_SUCCESS;
|
return SWITCH_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
if (loadAssemblyDelegate(cmd)) {
|
if (!(reloadDelegate(cmd))) {
|
||||||
stream->write_function(stream, "+OK\n");
|
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Execute failed for %s (unknown module or exception).\n", cmd);
|
||||||
} else {
|
|
||||||
stream->write_function(stream, "-ERR LoadAssembly returned false (invalid file or exception).\n");
|
|
||||||
}
|
}
|
||||||
return SWITCH_STATUS_SUCCESS;
|
return SWITCH_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
\dev\swig\swig.exe -I..\..\..\include -v -O -c++ -csharp -namespace FreeSWITCH.Native -dllimport mod_managed freeswitch.i
|
\dev\swig\swig.exe -I..\..\..\include -v -O -c++ -csharp -namespace FreeSWITCH.Native -dllimport mod_managed -DSWIG_CSHARP_NO_STRING_HELPER freeswitch.i
|
||||||
del swig.csx
|
del swig.csx
|
||||||
@ECHO OFF
|
@ECHO OFF
|
||||||
for %%X in (*.cs) do type %%X >> swig.csx
|
for %%X in (*.cs) do type %%X >> swig.csx
|
||||||
|
|
Loading…
Reference in New Issue