diff --git a/DiscordBotCore/Interfaces/Modules/IModule.cs b/DiscordBotCore/Interfaces/Modules/IModule.cs index 4471e2e..95e9c28 100644 --- a/DiscordBotCore/Interfaces/Modules/IModule.cs +++ b/DiscordBotCore/Interfaces/Modules/IModule.cs @@ -7,6 +7,7 @@ namespace DiscordBotCore.Interfaces.Modules public enum ModuleType { Logger, + Compatibility, Other } diff --git a/DiscordBotCore/Others/JsonManager.cs b/DiscordBotCore/Others/JsonManager.cs index 29b10d6..0aa71cb 100644 --- a/DiscordBotCore/Others/JsonManager.cs +++ b/DiscordBotCore/Others/JsonManager.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace DiscordBotCore.Others; -public class JsonManager +public static class JsonManager { /// /// Save to JSON file diff --git a/DiscordBotCore/Others/Result.cs b/DiscordBotCore/Others/Result.cs index 7e94b09..23af056 100644 --- a/DiscordBotCore/Others/Result.cs +++ b/DiscordBotCore/Others/Result.cs @@ -19,6 +19,18 @@ public class Result _Result = result; Exception = null; } + + public bool IsSuccess => _Result.HasValue && _Result.Value; + + public void HandleException(Action action) + { + if(IsSuccess) + { + return; + } + + action(Exception!); + } public static Result Success() => new Result(true); public static Result Failure(Exception ex) => new Result(ex); @@ -36,6 +48,12 @@ public class Result } } + + public TResult Match(Func successAction, Func errorAction) + { + return IsSuccess ? successAction() : errorAction(Exception!); + } + } public class Result diff --git a/Modules/CppCompatibilityModule/CppCompatibilityModule.csproj b/Modules/CppCompatibilityModule/CppCompatibilityModule.csproj new file mode 100644 index 0000000..0b25164 --- /dev/null +++ b/Modules/CppCompatibilityModule/CppCompatibilityModule.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/Modules/CppCompatibilityModule/Entry.cs b/Modules/CppCompatibilityModule/Entry.cs new file mode 100644 index 0000000..2dbb23a --- /dev/null +++ b/Modules/CppCompatibilityModule/Entry.cs @@ -0,0 +1,75 @@ +using DiscordBotCore; +using DiscordBotCore.Interfaces.Modules; +using DiscordBotCore.Others; + +namespace CppCompatibilityModule; + +public class Entry : IModule +{ + public ModuleType ModuleType => ModuleType.Compatibility; + public string Name => "CppCompatibility"; + public IDictionary MethodMapping => new Dictionary() + { + {"create_application", "CreateApplication"}, + {"stop_application", "StopApplication"}, + {"execute_function_with_parameter", "CallFunctionWithParameter"}, + {"execute_function_without_parameter", "CallFunctionWithoutParameter"} + }; + + private ExternalApplicationManager? _ExternalApplicationManager; + + public Task Initialize() + { + _ExternalApplicationManager = new ExternalApplicationManager(); + return Task.CompletedTask; + } + + public Guid CreateApplication(string dllFilePath) + { + if(_ExternalApplicationManager is null) + { + Application.Logger.Log("Failed to create application because the manager is not initialized. This should have never happened in the first place !!!", this, LogType.Critical); + return Guid.Empty; + } + + if(_ExternalApplicationManager.TryCreateApplication(dllFilePath, out Guid appId)) + { + return appId; + } + + return Guid.Empty; + } + + public void StopApplication(Guid applicationId) + { + if(_ExternalApplicationManager is null) + { + Application.Logger.Log("Failed to stop application because the manager is not initialized. This should have never happened in the first place!!!", this, LogType.Critical); + return; + } + + _ExternalApplicationManager.FreeApplication(applicationId); + } + + public void CallFunctionWithParameter(Guid appId, string functionName, ref object parameter) + { + if(_ExternalApplicationManager is null) + { + Application.Logger.Log("Failed to call function because the manager is not initialized. This should have never happened in the first place!!!", this, LogType.Critical); + return; + } + + _ExternalApplicationManager.ExecuteApplicationFunctionWithParameter(appId, functionName, ref parameter); + } + + public void CallFunctionWithoutParameter(Guid appId, string functionName) + { + if(_ExternalApplicationManager is null) + { + Application.Logger.Log("Failed to call function because the manager is not initialized. This should have never happened in the first place!!!", this, LogType.Critical); + return; + } + + _ExternalApplicationManager.ExecuteApplicationFunctionWithoutParameter(appId, functionName); + } +} diff --git a/Modules/CppCompatibilityModule/Extern/Delegates.cs b/Modules/CppCompatibilityModule/Extern/Delegates.cs new file mode 100644 index 0000000..ca51375 --- /dev/null +++ b/Modules/CppCompatibilityModule/Extern/Delegates.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace CppCompatibilityModule.Extern; + +public static class Delegates +{ + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void ProcessObject(ref object obj); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void ExecuteDelegateFunction(); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void SetExternFunctionPointerDelegate(IntPtr funcPtr); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void CsharpFunctionDelegate(); +} diff --git a/Modules/CppCompatibilityModule/Extern/ExternLibrary.cs b/Modules/CppCompatibilityModule/Extern/ExternLibrary.cs new file mode 100644 index 0000000..f431df1 --- /dev/null +++ b/Modules/CppCompatibilityModule/Extern/ExternLibrary.cs @@ -0,0 +1,133 @@ +using System.Runtime.InteropServices; +using DiscordBotCore; +using DiscordBotCore.Others; + +namespace CppCompatibilityModule.Extern +{ + public sealed class ExternLibrary + { + public string LibraryPath { get; init; } + public IntPtr LibraryHandle { get; private set; } + + public ExternLibrary(string libraryPath) + { + LibraryPath = libraryPath; + LibraryHandle = IntPtr.Zero; + } + + public Result InitializeLibrary() + { + if(LibraryHandle != IntPtr.Zero) + { + return Result.Success(); + } + + Application.Logger.Log($"Loading library {LibraryPath}"); + + + if(!NativeLibrary.TryLoad(LibraryPath, out IntPtr hModule)) + { + return Result.Failure(new DllNotFoundException($"Unable to load library {LibraryPath}")); + } + + Application.Logger.Log($"Library {LibraryPath} loaded successfully [{hModule}]"); + + LibraryHandle = hModule; + + return Result.Success(); + } + + public void FreeLibrary() + { + if(LibraryHandle == IntPtr.Zero) + { + return; + } + + NativeLibrary.Free(LibraryHandle); + LibraryHandle = IntPtr.Zero; + + Application.Logger.Log($"Library {LibraryPath} freed successfully"); + } + + private IntPtr GetFunctionPointer(string functionName) + { + if(LibraryHandle == IntPtr.Zero) + { + throw new InvalidOperationException("Library is not loaded"); + } + + if(!NativeLibrary.TryGetExport(LibraryHandle, functionName, out IntPtr functionPointer)) + { + throw new EntryPointNotFoundException($"Unable to find function {functionName}"); + } + + return functionPointer; + } + + public T GetDelegateForFunctionPointer(string methodName) where T : Delegate + { + IntPtr functionPointer = GetFunctionPointer(methodName); + + Application.Logger.Log($"Function pointer for {methodName} obtained successfully [address: {functionPointer}]"); + + T result = (T)Marshal.GetDelegateForFunctionPointer(functionPointer, typeof(T)); + + Application.Logger.Log($"Delegate for {methodName} created successfully"); + + return result; + } + + private IntPtr GetFunctionPointerForDelegate(T functionDelegate) where T : Delegate + { + IntPtr functionPointer = Marshal.GetFunctionPointerForDelegate(functionDelegate); + + Application.Logger.Log($"Function pointer for delegate {functionDelegate.Method.Name} obtained successfully [address: {functionPointer}]"); + + return functionPointer; + } + + /// + /// Tells the extern setter function to point its function to this C# function instead. + /// This function takes the name of the extern setter function and the C# function to be executed. + /// How it works: + /// Find the external setter method by its name. It should take one parameter, which is the pointer to the function to be executed. + /// Take the delegate function that should be executed and get its function pointer. + /// Call the external setter with the new function memory address. This should replace the old C++ function with the new C# function. + /// + /// The setter function name + /// The function that the C++ setter will make its internal function to point to + /// A delegate that reflects the executable function structure + /// The Setter delegate + /// A response if it exists as an object + public object? SetExternFunctionSetterPointerToCustomDelegate(string setterExternFunctionName, ExecuteDelegate executableFunction) where ExecuteDelegate : Delegate where SetDelegate : Delegate + { + SetDelegate setterDelegate = GetDelegateForFunctionPointer(setterExternFunctionName); + IntPtr executableFunctionPtr = GetFunctionPointerForDelegate(executableFunction); + + var result = setterDelegate.DynamicInvoke(executableFunctionPtr); + + Application.Logger.Log($"Function {setterExternFunctionName} bound to local action successfully"); + + return result; + } + + public void CallFunction(string methodName, ref object parameter) + { + var functionDelegate = GetDelegateForFunctionPointer(methodName); + + functionDelegate(ref parameter); + + Application.Logger.Log($"Function {methodName} called successfully with parameter"); + } + + public void CallFunction(string methodName) + { + var functionDelegate = GetDelegateForFunctionPointer(methodName); + + functionDelegate(); + + Application.Logger.Log($"Function {methodName} called successfully"); + } + } +} diff --git a/Modules/CppCompatibilityModule/Extern/ExternalApplication.cs b/Modules/CppCompatibilityModule/Extern/ExternalApplication.cs new file mode 100644 index 0000000..bca0d6a --- /dev/null +++ b/Modules/CppCompatibilityModule/Extern/ExternalApplication.cs @@ -0,0 +1,55 @@ +using DiscordBotCore; +using DiscordBotCore.Others; + +namespace CppCompatibilityModule.Extern; + +public class ExternalApplication +{ + public Guid ApplicationId { get; private set; } + private readonly ExternLibrary _ExternLibrary; + + private ExternalApplication(Guid applicationGuid, ExternLibrary library) + { + this.ApplicationId = applicationGuid; + this._ExternLibrary = library; + } + + internal void CallFunction(string methodName, ref object parameter) + { + _ExternLibrary.CallFunction(methodName, ref parameter); + } + + internal void CallFunction(string methodName) + { + _ExternLibrary.CallFunction(methodName); + } + + internal T GetDelegateForFunctionPointer(string methodName) where T : Delegate + { + return _ExternLibrary.GetDelegateForFunctionPointer(methodName); + } + + internal void SetExternFunctionToPointToFunction(string externalFunctionName, Delegates.CsharpFunctionDelegate localFunction) + { + _ExternLibrary.SetExternFunctionSetterPointerToCustomDelegate(externalFunctionName, localFunction); + } + + internal void FreeLibrary() + { + _ExternLibrary.FreeLibrary(); + } + + public static ExternalApplication? CreateFromDllFile(string dllFilePath) + { + ExternLibrary library = new ExternLibrary(dllFilePath); + var result = library.InitializeLibrary(); + + return result.Match( + () => new ExternalApplication(Guid.NewGuid(), library), + (ex) => { + Application.Logger.Log(ex.Message, LogType.Error); + library.FreeLibrary(); + return null; + }); + } +} diff --git a/Modules/CppCompatibilityModule/ExternalApplicationManager.cs b/Modules/CppCompatibilityModule/ExternalApplicationManager.cs new file mode 100644 index 0000000..ea8c899 --- /dev/null +++ b/Modules/CppCompatibilityModule/ExternalApplicationManager.cs @@ -0,0 +1,72 @@ +using CppCompatibilityModule.Extern; +using DiscordBotCore; + +namespace CppCompatibilityModule; + +public class ExternalApplicationManager +{ + private List _ExternalApplications; + + public ExternalApplicationManager() + { + _ExternalApplications = new List(); + } + + public bool TryCreateApplication(string applicationFileName, out Guid applicationId) + { + ExternalApplication? externalApplication = ExternalApplication.CreateFromDllFile(applicationFileName); + + if(externalApplication is null) + { + applicationId = Guid.Empty; + return false; + } + + _ExternalApplications.Add(externalApplication); + + applicationId = externalApplication.ApplicationId; + return true; + } + + public void FreeApplication(Guid applicationId) + { + var application = _ExternalApplications.FirstOrDefault(app => app.ApplicationId == applicationId, null); + if(application is null) + { + Application.Logger.Log($"Couldn't find application with id {applicationId}"); + return; + } + + application.FreeLibrary(); + _ExternalApplications.Remove(application); + + Application.Logger.Log($"Application with id {applicationId} freed successfully"); + } + + public void ExecuteApplicationFunctionWithParameter(Guid appId, string functionName, ref object parameter) + { + var application = _ExternalApplications.FirstOrDefault(app => app.ApplicationId == appId); + if(application is null) + { + Application.Logger.Log($"Couldn't find application with id {appId}"); + return; + } + + application.CallFunction(functionName, ref parameter); + } + + public void ExecuteApplicationFunctionWithoutParameter(Guid appId, string functionName) + { + var application = _ExternalApplications.FirstOrDefault(app => app.ApplicationId == appId); + if(application is null) + { + Application.Logger.Log($"Couldn't find application with id {appId}"); + return; + } + + application.CallFunction(functionName); + + } + + +} diff --git a/SethDiscordBot.sln b/SethDiscordBot.sln index 3ebd752..9f513c1 100644 --- a/SethDiscordBot.sln +++ b/SethDiscordBot.sln @@ -30,6 +30,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebUI", "Plugins\WebUI\WebU EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscordBotWebUI", "DiscordBotWebUI\DiscordBotWebUI.csproj", "{362161EF-531F-4413-B200-19ACD0FC355B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CppCompatibilityModule", "Modules\CppCompatibilityModule\CppCompatibilityModule.csproj", "{C67908F9-4A55-4DD8-B993-C26C648226F1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -72,6 +74,10 @@ Global {362161EF-531F-4413-B200-19ACD0FC355B}.Debug|Any CPU.Build.0 = Debug|Any CPU {362161EF-531F-4413-B200-19ACD0FC355B}.Release|Any CPU.ActiveCfg = Release|Any CPU {362161EF-531F-4413-B200-19ACD0FC355B}.Release|Any CPU.Build.0 = Release|Any CPU + {C67908F9-4A55-4DD8-B993-C26C648226F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C67908F9-4A55-4DD8-B993-C26C648226F1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C67908F9-4A55-4DD8-B993-C26C648226F1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C67908F9-4A55-4DD8-B993-C26C648226F1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -83,6 +89,7 @@ Global {F3C61A47-F758-4145-B496-E3ECCF979D89} = {5CF9AD7B-6BF0-4035-835F-722F989C01E1} {367F3197-8B9E-4BDC-A6DE-226E721F9ED1} = {EA4FA308-7B2C-458E-8485-8747D745DD59} {962A4815-6FAE-4DEF-976E-5622253A5E3B} = {5CF9AD7B-6BF0-4035-835F-722F989C01E1} + {C67908F9-4A55-4DD8-B993-C26C648226F1} = {EA4FA308-7B2C-458E-8485-8747D745DD59} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3FB3C5DE-ED21-4D2E-ABDD-3A00EE4A2FFF}