diff --git a/.gitignore b/.gitignore index ef93686..e877522 100644 --- a/.gitignore +++ b/.gitignore @@ -459,4 +459,5 @@ $RECYCLE.BIN/ /WebUI_Old/Data /WebUI/bin /WebUI_Old/bin -Data/ \ No newline at end of file +Data/ +/WebUI/Libraries \ No newline at end of file diff --git a/Modules/CppCompatibilityModule/Extern/Delegates.cs b/Modules/CppCompatibilityModule/Extern/Delegates.cs index ca51375..518e4c4 100644 --- a/Modules/CppCompatibilityModule/Extern/Delegates.cs +++ b/Modules/CppCompatibilityModule/Extern/Delegates.cs @@ -4,12 +4,6 @@ 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); diff --git a/Modules/CppCompatibilityModule/Extern/ExternLibrary.cs b/Modules/CppCompatibilityModule/Extern/ExternLibrary.cs index 25ec879..4180c8c 100644 --- a/Modules/CppCompatibilityModule/Extern/ExternLibrary.cs +++ b/Modules/CppCompatibilityModule/Extern/ExternLibrary.cs @@ -116,23 +116,6 @@ namespace CppCompatibilityModule.Extern return result; } - - public void CallFunction(string methodName, ref object parameter) - { - var functionDelegate = GetDelegateForFunctionPointer(methodName); - - functionDelegate(ref parameter); - - _Logger.Log($"Function {methodName} called successfully with parameter"); - } - public void CallFunction(string methodName) - { - var functionDelegate = GetDelegateForFunctionPointer(methodName); - - functionDelegate(); - - _Logger.Log($"Function {methodName} called successfully"); - } } } diff --git a/Modules/CppCompatibilityModule/Extern/ExternalApplication.cs b/Modules/CppCompatibilityModule/Extern/ExternalApplication.cs index fdb7774..d4d964b 100644 --- a/Modules/CppCompatibilityModule/Extern/ExternalApplication.cs +++ b/Modules/CppCompatibilityModule/Extern/ExternalApplication.cs @@ -17,24 +17,14 @@ public class ExternalApplication this._Logger = logger; } - 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) + internal object? SetExternFunctionToPointToFunction(string externalFunctionName, TLocalFunctionDelegate localFunction) where TLocalFunctionDelegate : Delegate { - _ExternLibrary.SetExternFunctionSetterPointerToCustomDelegate(externalFunctionName, localFunction); + return _ExternLibrary.SetExternFunctionSetterPointerToCustomDelegate(externalFunctionName, localFunction); } internal void FreeLibrary() diff --git a/Modules/CppCompatibilityModule/ExternalApplicationHandler.cs b/Modules/CppCompatibilityModule/ExternalApplicationHandler.cs index 3fbc9df..6fc0e2d 100644 --- a/Modules/CppCompatibilityModule/ExternalApplicationHandler.cs +++ b/Modules/CppCompatibilityModule/ExternalApplicationHandler.cs @@ -43,25 +43,26 @@ public class ExternalApplicationHandler _ExternalApplicationManager.FreeApplication(applicationId); } - public void CallFunctionWithParameter(Guid appId, string functionName, ref object parameter) + public T GetFunctionDelegate(Guid applicationId, string functionName) where T : Delegate { if (_ExternalApplicationManager is null) { - _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; + _Logger.Log("Failed to get function delegate because the manager is not initialized. This should have never happened in the first place!!!", this, LogType.Critical); + return default!; } - - _ExternalApplicationManager.ExecuteApplicationFunctionWithParameter(appId, functionName, ref parameter); + + return _ExternalApplicationManager.GetFunctionDelegate(applicationId, functionName); } - public void CallFunctionWithoutParameter(Guid appId, string functionName) + public object? SetExternFunctionToPointToFunction(Guid applicationId, string functionName, + TLocalFunctionDelegate localFunction) where TLocalFunctionDelegate : Delegate { - if (_ExternalApplicationManager is null) + if(_ExternalApplicationManager is null) { - _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; + _Logger.Log("Failed to set external function pointer because the manager is not initialized. This should have never happened in the first place!!!", this, LogType.Critical); + return null; } - - _ExternalApplicationManager.ExecuteApplicationFunctionWithoutParameter(appId, functionName); + + return _ExternalApplicationManager.SetExternFunctionToPointToFunction(applicationId, functionName, localFunction); } } diff --git a/Modules/CppCompatibilityModule/ExternalApplicationManager.cs b/Modules/CppCompatibilityModule/ExternalApplicationManager.cs index 6afe659..e51a13b 100644 --- a/Modules/CppCompatibilityModule/ExternalApplicationManager.cs +++ b/Modules/CppCompatibilityModule/ExternalApplicationManager.cs @@ -46,30 +46,28 @@ internal class ExternalApplicationManager _Logger.Log($"Application with id {applicationId} freed successfully"); } - public void ExecuteApplicationFunctionWithParameter(Guid appId, string functionName, ref object parameter) + public T GetFunctionDelegate(Guid applicationId, string functionName) where T : Delegate { - var application = _ExternalApplications.FirstOrDefault(app => app.ApplicationId == appId); + var application = _ExternalApplications.FirstOrDefault(app => app.ApplicationId == applicationId, null); if(application is null) { - _Logger.Log($"Couldn't find application with id {appId}"); - return; + _Logger.Log($"Couldn't find application with id {applicationId}"); + return default!; } - application.CallFunction(functionName, ref parameter); + return application.GetDelegateForFunctionPointer(functionName); } - public void ExecuteApplicationFunctionWithoutParameter(Guid appId, string functionName) + public object? SetExternFunctionToPointToFunction(Guid applicationId, string externalFunctionName, TLocalFunctionDelegate localFunction) where TLocalFunctionDelegate : Delegate { - var application = _ExternalApplications.FirstOrDefault(app => app.ApplicationId == appId); + var application = _ExternalApplications.FirstOrDefault(app => app.ApplicationId == applicationId, null); if(application is null) { - _Logger.Log($"Couldn't find application with id {appId}"); - return; + _Logger.Log($"Couldn't find application with id {applicationId}"); + return null; } - application.CallFunction(functionName); - + return application.SetExternFunctionToPointToFunction(externalFunctionName, localFunction); } - } diff --git a/Plugins/CppModuleDemo/CppModuleDemo.csproj b/Plugins/CppModuleDemo/CppModuleDemo.csproj new file mode 100644 index 0000000..f52d251 --- /dev/null +++ b/Plugins/CppModuleDemo/CppModuleDemo.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/Plugins/CppModuleDemo/Delegates.cs b/Plugins/CppModuleDemo/Delegates.cs new file mode 100644 index 0000000..3ea6c3f --- /dev/null +++ b/Plugins/CppModuleDemo/Delegates.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +namespace CppModuleDemo; + +public static class Delegates +{ + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void ModifyComplexObject(ref ExampleComplexObject obj); +} \ No newline at end of file diff --git a/Plugins/CppModuleDemo/ExampleComplexObject.cs b/Plugins/CppModuleDemo/ExampleComplexObject.cs new file mode 100644 index 0000000..b6dd0f0 --- /dev/null +++ b/Plugins/CppModuleDemo/ExampleComplexObject.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; + +namespace CppModuleDemo; + +[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] +public struct ExampleComplexObject +{ + public int IntegerValue; + public double DoubleValue; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)] + public string StringValue; + + public ExampleComplexObject(int integerValue, double doubleValue, string stringValue) + { + IntegerValue = integerValue; + DoubleValue = doubleValue; + StringValue = stringValue; + } +} \ No newline at end of file diff --git a/Plugins/CppModuleDemo/InternalSettings.cs b/Plugins/CppModuleDemo/InternalSettings.cs new file mode 100644 index 0000000..46a30c0 --- /dev/null +++ b/Plugins/CppModuleDemo/InternalSettings.cs @@ -0,0 +1,10 @@ +using CppCompatibilityModule; +using CppCompatibilityModule.Extern; + +namespace CppModuleDemo; + +internal static class InternalSettings +{ + internal static ExternalApplicationHandler? ExternalApplicationHandler { get; set; } = null; + internal static Guid DemoModuleInternalId { get; set; } = Guid.Empty; +} \ No newline at end of file diff --git a/Plugins/CppModuleDemo/ModuleSlashCommand.cs b/Plugins/CppModuleDemo/ModuleSlashCommand.cs new file mode 100644 index 0000000..c29747a --- /dev/null +++ b/Plugins/CppModuleDemo/ModuleSlashCommand.cs @@ -0,0 +1,78 @@ +using Discord; +using Discord.WebSocket; +using DiscordBotCore.Logging; +using DiscordBotCore.PluginCore.Interfaces; + +namespace CppModuleDemo; + +public class ModuleSlashCommand : IDbSlashCommand +{ + public string Name => "cpp-module-demo"; + public string Description => "A demo command to showcase the C++ module integration with Discord Bot Core."; + public bool CanUseDm => false; + public bool HasInteraction => false; + + public List Options => new List() + { + new SlashCommandOptionBuilder() + { + Name = "example-integer-value", Description = "An example integer value", + Type = ApplicationCommandOptionType.Integer, IsRequired = true + }, + new SlashCommandOptionBuilder() + { + Name = "example-number-value", Description = "An example number value", + Type = ApplicationCommandOptionType.Number, IsRequired = true + }, + new SlashCommandOptionBuilder() + { + Name = "example-string-value", Description = "An example boolean value", + Type = ApplicationCommandOptionType.String, IsRequired = true + } + }; + + public async void ExecuteServer(ILogger logger, SocketSlashCommand context) + { + long integerValue = (long)context.Data.Options.First(option => option.Name == "example-integer-value").Value; + double numberValue = (double)context.Data.Options.First(option => option.Name == "example-number-value").Value; + string stringValue = (string)context.Data.Options.First(option => option.Name == "example-string-value").Value; + + if(integerValue > int.MaxValue || integerValue < int.MinValue) + { + await context.Channel.SendMessageAsync("The provided integer value is out of range. Please provide a valid integer."); + return; + } + + await context.RespondAsync("Processing your request...", ephemeral: true); + + await context.Channel.SendMessageAsync("CppModuleDemo invoked with: \n" + + $"Integer Value: {integerValue}\n" + + $"Number Value: {numberValue}\n" + + $"String Value: {stringValue}"); + + ExampleComplexObject complexObject = new ExampleComplexObject + { + IntegerValue = (int)integerValue, + DoubleValue = numberValue, + StringValue = stringValue + }; + + Delegates.ModifyComplexObject? modifyComplexObject = + InternalSettings.ExternalApplicationHandler?.GetFunctionDelegate( + InternalSettings.DemoModuleInternalId, "modifyComplexObject"); + + if (modifyComplexObject is null) + { + await context.Channel.SendMessageAsync("Failed to retrieve the C++ function delegate. Please check the C++ module integration."); + return; + } + + modifyComplexObject(ref complexObject); + + await context.Channel.SendMessageAsync("CppModuleDemo command executed successfully! New values are:\n" + + $"Integer Value: {((ExampleComplexObject)complexObject).IntegerValue}\n" + + $"Number Value: {((ExampleComplexObject)complexObject).DoubleValue}\n" + + $"String Value: {((ExampleComplexObject)complexObject).StringValue}"); + + } +} \ No newline at end of file diff --git a/Plugins/CppModuleDemo/StartupEvent.cs b/Plugins/CppModuleDemo/StartupEvent.cs new file mode 100644 index 0000000..4ab18c0 --- /dev/null +++ b/Plugins/CppModuleDemo/StartupEvent.cs @@ -0,0 +1,31 @@ +using CppCompatibilityModule; +using CppCompatibilityModule.Extern; +using DiscordBotCore.PluginCore.Helpers.Execution.DbEvent; +using DiscordBotCore.PluginCore.Interfaces; + +namespace CppModuleDemo; + +public class StartupEvent : IDbEvent +{ + public string Name => "CppModuleDemoStartupEvent"; + public string Description => "A demo event to showcase the C++ module integration with Discord Bot Core on startup."; + + private static string _DllModule = "libCppModuleDemo.dylib"; + + public void Start(IDbEventExecutingArgument args) + { + args.PluginBaseDirectory.Create(); + InternalSettings.ExternalApplicationHandler = new ExternalApplicationHandler(args.Logger); + string fullPath = Path.Combine(args.PluginBaseDirectory.FullName, _DllModule); + Guid id = InternalSettings.ExternalApplicationHandler.CreateApplication(fullPath); + + if (id == Guid.Empty) + { + args.Logger.Log("Failed to create the C++ module application. Please check the DLL path and ensure it is correct.", this); + return; + } + + args.Logger.Log($"CppModuleDemo started successfully with application ID: {id}", this); + InternalSettings.DemoModuleInternalId = id; + } +} \ No newline at end of file diff --git a/Plugins/CppModuleDemo/StopSlashCommand.cs b/Plugins/CppModuleDemo/StopSlashCommand.cs new file mode 100644 index 0000000..f1bc6bd --- /dev/null +++ b/Plugins/CppModuleDemo/StopSlashCommand.cs @@ -0,0 +1,37 @@ +using Discord; +using Discord.WebSocket; +using DiscordBotCore.Logging; +using DiscordBotCore.PluginCore.Interfaces; + +namespace CppModuleDemo; + +public class StopSlashCommand : IDbSlashCommand +{ + public string Name => "stop-cpp-module-demo"; + public string Description => "Stops the C++ module demo and cleans up resources."; + public bool CanUseDm => false; + public bool HasInteraction => false; + public List Options => []; + + public async void ExecuteServer(ILogger logger, SocketSlashCommand context) + { + if (InternalSettings.ExternalApplicationHandler == null) + { + logger.Log("No C++ module is currently running.", this); + return; + } + + Guid id = InternalSettings.DemoModuleInternalId; + if (id == Guid.Empty) + { + logger.Log("No valid C++ module ID found. Cannot stop the module.", this); + return; + } + + InternalSettings.ExternalApplicationHandler.StopApplication(id); + InternalSettings.DemoModuleInternalId = Guid.Empty; + logger.Log("CppModuleDemo stopped successfully.", this); + + await context.Channel.SendMessageAsync("CppModuleDemo has been stopped and resources cleaned up."); + } +} \ No newline at end of file diff --git a/SethDiscordBot.sln b/SethDiscordBot.sln index 357ccbc..526412f 100644 --- a/SethDiscordBot.sln +++ b/SethDiscordBot.sln @@ -5,9 +5,6 @@ MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscordBotCore", "DiscordBotCore\DiscordBotCore.csproj", "{5A99BFC3-EB39-4AEF-8D61-3CE22D013B02}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{5CF9AD7B-6BF0-4035-835F-722F989C01E1}" - ProjectSection(SolutionItems) = preProject - Plugins\README.md = Plugins\README.md - EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{EA4FA308-7B2C-458E-8485-8747D745DD59}" EndProject @@ -41,6 +38,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.DiscordBotCore.Loggin EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.DiscordBotCore.Configuration", "Tests\Tests.DiscordBotCore.Configuration\Tests.DiscordBotCore.Configuration.csproj", "{52C59C73-C23C-4608-8827-383577DEB8D8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CppModuleDemo", "Plugins\CppModuleDemo\CppModuleDemo.csproj", "{55425935-456E-4ED4-9283-2CB6A6C7AC7D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -231,6 +230,18 @@ Global {52C59C73-C23C-4608-8827-383577DEB8D8}.Release|ARM64.Build.0 = Release|Any CPU {52C59C73-C23C-4608-8827-383577DEB8D8}.Release|x64.ActiveCfg = Release|Any CPU {52C59C73-C23C-4608-8827-383577DEB8D8}.Release|x64.Build.0 = Release|Any CPU + {55425935-456E-4ED4-9283-2CB6A6C7AC7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {55425935-456E-4ED4-9283-2CB6A6C7AC7D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {55425935-456E-4ED4-9283-2CB6A6C7AC7D}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {55425935-456E-4ED4-9283-2CB6A6C7AC7D}.Debug|ARM64.Build.0 = Debug|Any CPU + {55425935-456E-4ED4-9283-2CB6A6C7AC7D}.Debug|x64.ActiveCfg = Debug|Any CPU + {55425935-456E-4ED4-9283-2CB6A6C7AC7D}.Debug|x64.Build.0 = Debug|Any CPU + {55425935-456E-4ED4-9283-2CB6A6C7AC7D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {55425935-456E-4ED4-9283-2CB6A6C7AC7D}.Release|Any CPU.Build.0 = Release|Any CPU + {55425935-456E-4ED4-9283-2CB6A6C7AC7D}.Release|ARM64.ActiveCfg = Release|Any CPU + {55425935-456E-4ED4-9283-2CB6A6C7AC7D}.Release|ARM64.Build.0 = Release|Any CPU + {55425935-456E-4ED4-9283-2CB6A6C7AC7D}.Release|x64.ActiveCfg = Release|Any CPU + {55425935-456E-4ED4-9283-2CB6A6C7AC7D}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -241,6 +252,7 @@ Global {9A4B98C1-00AC-481C-BE55-A70C0B9D3BE7} = {5CF9AD7B-6BF0-4035-835F-722F989C01E1} {94238D37-60C6-4E40-80EC-4B4D242F5914} = {8F27B3EA-F292-40DF-B9B3-4B0E6BEA4E70} {52C59C73-C23C-4608-8827-383577DEB8D8} = {8F27B3EA-F292-40DF-B9B3-4B0E6BEA4E70} + {55425935-456E-4ED4-9283-2CB6A6C7AC7D} = {5CF9AD7B-6BF0-4035-835F-722F989C01E1} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3FB3C5DE-ED21-4D2E-ABDD-3A00EE4A2FFF} diff --git a/WebUI/Components/Pages/Plugins/AddLocal.razor b/WebUI/Components/Pages/Plugins/AddLocal.razor index 7242488..984def4 100644 --- a/WebUI/Components/Pages/Plugins/AddLocal.razor +++ b/WebUI/Components/Pages/Plugins/AddLocal.razor @@ -65,6 +65,7 @@ var buffer = new byte[file.Size]; await file.OpenReadStream().ReadAsync(buffer); _message = $"Uploaded file: {file.Name} ({file.Size} bytes)"; + selectedFile = file; } private async Task HandleValidSubmit() diff --git a/WebUI/Program.cs b/WebUI/Program.cs index 34b9434..7dce60e 100644 --- a/WebUI/Program.cs +++ b/WebUI/Program.cs @@ -1,7 +1,71 @@ +using System.Reflection; using WebUI; using WebUI.Services; using WebUI.Components; +#region Load External (Unmanaged) Assemblies + +// This code is used to load external (unmanaged) assemblies from the same folder as the executing assembly. +// It handles the AssemblyResolve event to search for the requested assembly in a specific folder structure. +// The folder structure is expected to be: /Libraries//. +// The extensions to search for are specified in the 'extensions' parameter. + +var currentDomain = AppDomain.CurrentDomain; +currentDomain.AssemblyResolve += (sender, args) => LoadFromSameFolder(sender,args, [".dll", ".so", ".dylib"]); + +static Assembly? LoadFromSameFolder(object? sender, ResolveEventArgs args, string[] extensions) +{ + string? requestingAssemblyName = args.RequestingAssembly?.GetName().Name; + string executingAssemblyLocation = Assembly.GetExecutingAssembly().Location; + string? executingAssemblyDirectory = Path.GetDirectoryName(executingAssemblyLocation); + + if (string.IsNullOrEmpty(executingAssemblyDirectory)) + { + Console.WriteLine($"Error: Could not determine the directory of the executing assembly."); + return null; + } + + string librariesFolder = Path.Combine(executingAssemblyDirectory, "Libraries", requestingAssemblyName ?? ""); + string requestedAssemblyNameWithoutExtension = new AssemblyName(args.Name).Name; + + Console.WriteLine($"Requesting Assembly: {requestingAssemblyName}"); + Console.WriteLine($"Requested Assembly Name (without extension): {requestedAssemblyNameWithoutExtension}"); + Console.WriteLine($"Searching in folder: {librariesFolder}"); + Console.WriteLine($"Searching for extensions: {string.Join(", ", extensions)}"); + + foreach (string extension in extensions) + { + string assemblyFileName = requestedAssemblyNameWithoutExtension + extension; + string assemblyPath = Path.Combine(librariesFolder, assemblyFileName); + + Console.WriteLine($"Attempting to load from: {assemblyPath}"); + + if (File.Exists(assemblyPath)) + { + try + { + var fileAssembly = Assembly.LoadFrom(assemblyPath); + Console.WriteLine($"Successfully loaded Assembly: {fileAssembly.FullName}"); + return fileAssembly; + } + catch (Exception ex) + { + Console.WriteLine($"Error loading assembly from '{assemblyPath}': {ex.Message}"); + // Optionally log the full exception for debugging + } + } + else + { + Console.WriteLine($"File not found: {assemblyPath}"); + } + } + + Console.WriteLine($"Failed to load assembly '{args.Name}' from the specified locations."); + return null; +} +#endregion + + var builder = WebApplication.CreateBuilder(args); // Add services to the container.