diff --git a/Source/ExcelDna.Integration/AssemblyLoader.cs b/Source/ExcelDna.Integration/AssemblyLoader.cs index 2677178e..adacc34d 100644 --- a/Source/ExcelDna.Integration/AssemblyLoader.cs +++ b/Source/ExcelDna.Integration/AssemblyLoader.cs @@ -36,8 +36,6 @@ public static void ProcessAssemblies( List rtdServerTypes, List comClassTypes) { - bool loadRibbons = true; - foreach (ExportedAssembly assembly in assemblies) { int initialObjectsCount = methods.Count + diff --git a/Source/ExcelDna.Integration/DnaLibrary.cs b/Source/ExcelDna.Integration/DnaLibrary.cs index 1445826c..ef266e48 100644 --- a/Source/ExcelDna.Integration/DnaLibrary.cs +++ b/Source/ExcelDna.Integration/DnaLibrary.cs @@ -257,16 +257,10 @@ internal List GetAssemblies(string pathResolveRoot) [XmlIgnore] private List _methods = new List(); [XmlIgnore] - List _excelParameterConversions = new List(); - [XmlIgnore] - List _excelReturnConversions = new List(); + private ExtendedRegistration.Registration.Configuration _extendedRegistrationConfiguration; [XmlIgnore] private List _excelFunctionsExtendedRegistration = new List(); [XmlIgnore] - private List _excelFunctionExecutionHandlerSelectors = new List(); - [XmlIgnore] - private List _excelFunctionProcessors = new List(); - [XmlIgnore] private List _exportedAssemblies; // The idea is that Initialize compiles, loads and sorts out the assemblies, @@ -283,7 +277,14 @@ internal void Initialize() // Recursively get assemblies down .dna tree. _exportedAssemblies = GetAssemblies(dnaResolveRoot); - AssemblyLoader.ProcessAssemblies(_exportedAssemblies, _methods, _excelParameterConversions, _excelReturnConversions, _excelFunctionProcessors, _excelFunctionsExtendedRegistration, _excelFunctionExecutionHandlerSelectors, _addIns, rtdServerTypes, comClassTypes); + + var excelParameterConversions = new List(); + var excelReturnConversions = new List(); + var excelFunctionExecutionHandlerSelectors = new List(); + var excelFunctionProcessors = new List(); + AssemblyLoader.ProcessAssemblies(_exportedAssemblies, _methods, excelParameterConversions, excelReturnConversions, excelFunctionProcessors, _excelFunctionsExtendedRegistration, excelFunctionExecutionHandlerSelectors, _addIns, rtdServerTypes, comClassTypes); + _extendedRegistrationConfiguration = new ExtendedRegistration.Registration.Configuration() { ParameterConversions = excelParameterConversions, ReturnConversions = excelReturnConversions, ExcelFunctionProcessors = excelFunctionProcessors, ExcelFunctionExecutionHandlerSelectors = excelFunctionExecutionHandlerSelectors }; + NativeAOT.ExcelAddIns.ForEach(i => AssemblyLoader.GetExcelAddIns(null, i, _addIns)); // Register RTD Server Types (i.e. remember that these types are available as RTD servers, with relevant ProgId etc.) @@ -333,7 +334,7 @@ internal void AutoOpen() ExcelIntegration.RegisterMethods(commands); var functions = _methods.Except(commands).Select(i => new Registration.ExcelFunctionRegistration(i)).Concat(_excelFunctionsExtendedRegistration); - ExtendedRegistration.Registration.RegisterExtended(functions, _excelParameterConversions, _excelReturnConversions, _excelFunctionProcessors, _excelFunctionExecutionHandlerSelectors); + ExtendedRegistration.Registration.Register(functions, _extendedRegistrationConfiguration); // Invoke AutoOpen in all assemblies foreach (AssemblyLoader.ExcelAddInInfo addIn in _addIns) @@ -624,6 +625,14 @@ internal static string ExecutingDirectory } } + internal static ExtendedRegistration.Registration.Configuration ExtendedRegistrationConfiguration + { + get + { + return CurrentLibrary._extendedRegistrationConfiguration; + } + } + public string ResolvePath(string path) { return ResolvePath(path, dnaResolveRoot); diff --git a/Source/ExcelDna.Integration/ExcelAttributes.cs b/Source/ExcelDna.Integration/ExcelAttributes.cs index b729ef5a..1806d920 100644 --- a/Source/ExcelDna.Integration/ExcelAttributes.cs +++ b/Source/ExcelDna.Integration/ExcelAttributes.cs @@ -38,6 +38,22 @@ public ExcelFunctionAttribute(string description) { Description = description; } + + public ExcelFunctionAttribute(ExcelFunctionAttribute src) + { + Category = src.Category; + Name = src.Name; + Description = src.Description; + HelpTopic = src.HelpTopic; + IsVolatile = src.IsVolatile; + IsHidden = src.IsHidden; + IsExceptionSafe = src.IsExceptionSafe; + IsMacroType = src.IsMacroType; + IsThreadSafe = src.IsThreadSafe; + IsClusterSafe = src.IsClusterSafe; + ExplicitRegistration = src.ExplicitRegistration; + SuppressOverwriteError = src.SuppressOverwriteError; + } } /// diff --git a/Source/ExcelDna.Integration/ExtendedRegistration/Registration.cs b/Source/ExcelDna.Integration/ExtendedRegistration/Registration.cs index a14dcc23..c17e30ad 100644 --- a/Source/ExcelDna.Integration/ExtendedRegistration/Registration.cs +++ b/Source/ExcelDna.Integration/ExtendedRegistration/Registration.cs @@ -10,35 +10,48 @@ namespace ExcelDna.Integration.ExtendedRegistration { internal class Registration { - public static void RegisterExtended(IEnumerable functions, IEnumerable parameterConversions, IEnumerable returnConversions, IEnumerable excelFunctionProcessors, IEnumerable excelFunctionExecutionHandlerSelectors) + public class Configuration + { + public IEnumerable ParameterConversions { get; set; } + public IEnumerable ReturnConversions { get; set; } + public IEnumerable ExcelFunctionProcessors { get; set; } + public IEnumerable ExcelFunctionExecutionHandlerSelectors { get; set; } + } + + public static void Register(IEnumerable functions, Configuration configuration) + { + Register(Process(functions, configuration)); + } + + public static void Register(IEnumerable functions) + { + functions = functions.ToList(); + var lambdas = functions.Select(reg => reg.FunctionLambda).ToList(); + var attribs = functions.Select(reg => reg.FunctionAttribute).ToList(); + var argAttribs = functions.Select(reg => reg.ParameterRegistrations.Select(pr => pr.ArgumentAttribute).ToList()).ToList(); + ExcelIntegration.RegisterLambdaExpressions(lambdas, attribs, argAttribs); + } + + public static IEnumerable Process(IEnumerable functions, Configuration configuration) { // Set the Parameter Conversions before they are applied by the ProcessParameterConversions call below. // CONSIDER: We might change the registration to be an object...? - var conversionConfig = GetParameterConversionConfig(parameterConversions, returnConversions); + var conversionConfig = GetParameterConversionConfig(configuration.ParameterConversions, configuration.ReturnConversions); - var functionHandlerConfig = GetFunctionExecutionHandlerConfig(excelFunctionExecutionHandlerSelectors); + var functionHandlerConfig = GetFunctionExecutionHandlerConfig(configuration.ExcelFunctionExecutionHandlerSelectors); - Register(functions + return functions .UpdateRegistrationsForRangeParameters() - .ProcessFunctionProcessors(excelFunctionProcessors, conversionConfig) + .ProcessFunctionProcessors(configuration.ExcelFunctionProcessors, conversionConfig) .ProcessParameterConversions(conversionConfig) .ProcessAsyncRegistrations(nativeAsyncIfAvailable: false) .ProcessParamsRegistrations() .ProcessObjectHandles() .ProcessFunctionExecutionHandlers(functionHandlerConfig) - ); - } - - internal static void Register(IEnumerable functions) - { - functions = functions.ToList(); - var lambdas = functions.Select(reg => reg.FunctionLambda).ToList(); - var attribs = functions.Select(reg => reg.FunctionAttribute).ToList(); - var argAttribs = functions.Select(reg => reg.ParameterRegistrations.Select(pr => pr.ArgumentAttribute).ToList()).ToList(); - ExcelIntegration.RegisterLambdaExpressions(lambdas, attribs, argAttribs); + ; } - static ParameterConversionConfiguration GetParameterConversionConfig(IEnumerable parameterConversions, IEnumerable returnConversions) + private static ParameterConversionConfiguration GetParameterConversionConfig(IEnumerable parameterConversions, IEnumerable returnConversions) { // NOTE: The parameter conversion list is processed once per parameter. // Parameter conversions will apply from most inside, to most outside. @@ -93,7 +106,7 @@ static ParameterConversionConfiguration GetParameterConversionConfig(IEnumerable return paramConversionConfig; } - static FunctionExecutionConfiguration GetFunctionExecutionHandlerConfig(IEnumerable excelFunctionExecutionHandlerSelectors) + private static FunctionExecutionConfiguration GetFunctionExecutionHandlerConfig(IEnumerable excelFunctionExecutionHandlerSelectors) { FunctionExecutionConfiguration result = new FunctionExecutionConfiguration(); diff --git a/Source/ExcelDna.Integration/Registration/AsyncRegistration.cs b/Source/ExcelDna.Integration/Registration/AsyncRegistration.cs index f9057a58..ff304661 100644 --- a/Source/ExcelDna.Integration/Registration/AsyncRegistration.cs +++ b/Source/ExcelDna.Integration/Registration/AsyncRegistration.cs @@ -43,7 +43,7 @@ public static class AsyncRegistration ParameterConversionRegistration.ApplyParameterConversions(reg, ObjectHandleRegistration.GetParameterConversionConfiguration()); reg.FunctionLambda = WrapMethodObservable(reg.FunctionLambda, reg.Return.CustomAttributes); } - else if (ReturnsTask(reg.FunctionLambda) || reg.FunctionAttribute is ExcelDna.Registration.ExcelAsyncFunctionAttribute) + else if (ReturnsTask(reg.FunctionLambda) || reg.FunctionAttribute is ExcelAsyncFunctionAttribute) { ParameterConversionRegistration.ApplyParameterConversions(reg, ObjectHandleRegistration.GetParameterConversionConfiguration()); if (HasCancellationToken(reg.FunctionLambda)) @@ -58,6 +58,9 @@ public static class AsyncRegistration reg.FunctionLambda = useNativeAsync ? WrapMethodNativeAsyncTask(reg.FunctionLambda) : WrapMethodRunTask(reg.FunctionLambda, reg.Return.CustomAttributes); } + + if (reg.FunctionAttribute is ExcelAsyncFunctionAttribute) + reg.FunctionAttribute = new ExcelFunctionAttribute(reg.FunctionAttribute); } // else do nothing to this registration } diff --git a/Source/ExcelDna.Integration/Registration/ExcelRegistration.cs b/Source/ExcelDna.Integration/Registration/ExcelRegistration.cs index 8b9def36..ac2a121f 100644 --- a/Source/ExcelDna.Integration/Registration/ExcelRegistration.cs +++ b/Source/ExcelDna.Integration/Registration/ExcelRegistration.cs @@ -1,10 +1,7 @@ using ExcelDna.Integration; -using System; using System.Collections.Generic; using System.Linq; using System.Reflection; -using System.Text; -using System.Threading.Tasks; namespace ExcelDna.Registration { @@ -33,13 +30,22 @@ where mi.GetCustomAttribute() != null select new ExcelFunctionRegistration(mi); } + /// + /// Prepares the given functions for registration with Excel-DNA. + /// + /// + public static IEnumerable ProcessFunctions(this IEnumerable registrationEntries) + { + return Integration.ExtendedRegistration.Registration.Process(registrationEntries, DnaLibrary.ExtendedRegistrationConfiguration); + } + /// /// Registers the given functions with Excel-DNA. /// /// public static void RegisterFunctions(this IEnumerable registrationEntries) { - ExcelDna.Integration.ExtendedRegistration.Registration.Register(registrationEntries); + Integration.ExtendedRegistration.Registration.Register(registrationEntries); } /// diff --git a/Source/Tests/ExcelDna.AddIn.RuntimeTests/AddIn.cs b/Source/Tests/ExcelDna.AddIn.RuntimeTests/AddIn.cs new file mode 100644 index 00000000..05891e16 --- /dev/null +++ b/Source/Tests/ExcelDna.AddIn.RuntimeTests/AddIn.cs @@ -0,0 +1,16 @@ +using ExcelDna.Integration; + +namespace ExcelDna.AddIn.RuntimeTests +{ + public class AddIn : IExcelAddIn + { + public void AutoOpen() + { + DynamicFunctions.Register(); + } + + public void AutoClose() + { + } + } +} diff --git a/Source/Tests/ExcelDna.AddIn.RuntimeTests/DynamicFunctions.cs b/Source/Tests/ExcelDna.AddIn.RuntimeTests/DynamicFunctions.cs new file mode 100644 index 00000000..caa94edb --- /dev/null +++ b/Source/Tests/ExcelDna.AddIn.RuntimeTests/DynamicFunctions.cs @@ -0,0 +1,53 @@ +using ExcelDna.Registration; + +namespace ExcelDna.AddIn.RuntimeTests +{ + internal class DynamicFunctions + { + public static void Register() + { + { + ExcelFunctionRegistration[] functions = { + CreateRegistration(nameof(DynamicSayHello)), + CreateRegistration(nameof(DynamicOptionalDouble)), + ChangeName(CreateRegistration(nameof(ChangeMe)), "DynamicFunctionName"), + }; + + ExcelRegistration.RegisterFunctions(ExcelRegistration.ProcessFunctions(functions)); + } + { + ExcelFunctionRegistration[] functions = { + ChangeName(CreateRegistration(nameof(DynamicOptionalDouble)), "DynamicOptionalDoubleUnprocessed"), + }; + + ExcelRegistration.RegisterFunctions(functions); + } + } + + private static string DynamicSayHello(string name) + { + return $"Dynamic Hello {name}"; + } + + private static string DynamicOptionalDouble(double d = 4.56) + { + return "Dynamic Optional VAL: " + d.ToString(); + } + + private static string ChangeMe() + { + return $"Function {nameof(ChangeMe)}"; + } + + private static ExcelFunctionRegistration ChangeName(ExcelFunctionRegistration reg, string name) + { + reg.FunctionAttribute.Name = name; + return reg; + } + + private static ExcelFunctionRegistration CreateRegistration(string name) + { + return new ExcelFunctionRegistration(typeof(DynamicFunctions).GetMethod(name, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)); + } + } +} diff --git a/Source/Tests/ExcelDna.AddIn.RuntimeTests/MapArrayFunctionRegistration.cs b/Source/Tests/ExcelDna.AddIn.RuntimeTests/MapArrayFunctionRegistration.cs index 48320cd4..fcf85bf2 100644 --- a/Source/Tests/ExcelDna.AddIn.RuntimeTests/MapArrayFunctionRegistration.cs +++ b/Source/Tests/ExcelDna.AddIn.RuntimeTests/MapArrayFunctionRegistration.cs @@ -7,6 +7,8 @@ using System.Linq.Expressions; using System.Reflection; +#nullable disable + namespace ExcelDna.AddIn.RuntimeTests { /// diff --git a/Source/Tests/ExcelDna.RuntimeTests/Registration.cs b/Source/Tests/ExcelDna.RuntimeTests/Registration.cs index 2bd7e4c7..f34a4ef8 100644 --- a/Source/Tests/ExcelDna.RuntimeTests/Registration.cs +++ b/Source/Tests/ExcelDna.RuntimeTests/Registration.cs @@ -625,5 +625,30 @@ public void Params() Assert.Equal("5//4//3", functionRange.Value.ToString()); } } + + [ExcelFact(Workbook = "", AddIn = AddInPath.RuntimeTests)] + public void DynamicFunctions() + { + { + Range functionRange = ((Worksheet)ExcelDna.Testing.Util.Workbook.Sheets[1]).Range["B1"]; + functionRange.Formula = "=DynamicSayHello(\"world\")"; + Assert.Equal("Dynamic Hello world", functionRange.Value.ToString()); + } + { + Range functionRange = ((Worksheet)ExcelDna.Testing.Util.Workbook.Sheets[1]).Range["C1"]; + functionRange.Formula = "=DynamicOptionalDouble()"; + Assert.Equal("Dynamic Optional VAL: 4.56", functionRange.Value.ToString()); + } + { + Range functionRange = ((Worksheet)ExcelDna.Testing.Util.Workbook.Sheets[1]).Range["D1"]; + functionRange.Formula = "=DynamicFunctionName()"; + Assert.Equal("Function ChangeMe", functionRange.Value.ToString()); + } + { + Range functionRange = ((Worksheet)ExcelDna.Testing.Util.Workbook.Sheets[1]).Range["E1"]; + functionRange.Formula = "=DynamicOptionalDoubleUnprocessed()"; + Assert.Equal("Dynamic Optional VAL: 0", functionRange.Value.ToString()); + } + } } }