Skip to content
2 changes: 0 additions & 2 deletions Source/ExcelDna.Integration/AssemblyLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ public static void ProcessAssemblies(
List<Type> rtdServerTypes,
List<ExcelComClassType> comClassTypes)
{
bool loadRibbons = true;

foreach (ExportedAssembly assembly in assemblies)
{
int initialObjectsCount = methods.Count +
Expand Down
27 changes: 18 additions & 9 deletions Source/ExcelDna.Integration/DnaLibrary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -257,16 +257,10 @@ internal List<ExportedAssembly> GetAssemblies(string pathResolveRoot)
[XmlIgnore]
private List<MethodInfo> _methods = new List<MethodInfo>();
[XmlIgnore]
List<ExtendedRegistration.ExcelParameterConversion> _excelParameterConversions = new List<ExtendedRegistration.ExcelParameterConversion>();
[XmlIgnore]
List<ExtendedRegistration.ExcelReturnConversion> _excelReturnConversions = new List<ExtendedRegistration.ExcelReturnConversion>();
private ExtendedRegistration.Registration.Configuration _extendedRegistrationConfiguration;
[XmlIgnore]
private List<Registration.ExcelFunctionRegistration> _excelFunctionsExtendedRegistration = new List<Registration.ExcelFunctionRegistration>();
[XmlIgnore]
private List<Registration.FunctionExecutionHandlerSelector> _excelFunctionExecutionHandlerSelectors = new List<Registration.FunctionExecutionHandlerSelector>();
[XmlIgnore]
private List<ExtendedRegistration.ExcelFunctionProcessor> _excelFunctionProcessors = new List<ExtendedRegistration.ExcelFunctionProcessor>();
[XmlIgnore]
private List<ExportedAssembly> _exportedAssemblies;

// The idea is that Initialize compiles, loads and sorts out the assemblies,
Expand All @@ -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<ExtendedRegistration.ExcelParameterConversion>();
var excelReturnConversions = new List<ExtendedRegistration.ExcelReturnConversion>();
var excelFunctionExecutionHandlerSelectors = new List<Registration.FunctionExecutionHandlerSelector>();
var excelFunctionProcessors = new List<ExtendedRegistration.ExcelFunctionProcessor>();
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.)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down
16 changes: 16 additions & 0 deletions Source/ExcelDna.Integration/ExcelAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

/// <summary>
Expand Down
47 changes: 30 additions & 17 deletions Source/ExcelDna.Integration/ExtendedRegistration/Registration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,48 @@ namespace ExcelDna.Integration.ExtendedRegistration
{
internal class Registration
{
public static void RegisterExtended(IEnumerable<ExcelDna.Registration.ExcelFunctionRegistration> functions, IEnumerable<ExcelParameterConversion> parameterConversions, IEnumerable<ExcelReturnConversion> returnConversions, IEnumerable<ExcelFunctionProcessor> excelFunctionProcessors, IEnumerable<FunctionExecutionHandlerSelector> excelFunctionExecutionHandlerSelectors)
public class Configuration
{
public IEnumerable<ExcelParameterConversion> ParameterConversions { get; set; }
public IEnumerable<ExcelReturnConversion> ReturnConversions { get; set; }
public IEnumerable<ExcelFunctionProcessor> ExcelFunctionProcessors { get; set; }
public IEnumerable<FunctionExecutionHandlerSelector> ExcelFunctionExecutionHandlerSelectors { get; set; }
}

public static void Register(IEnumerable<ExcelFunctionRegistration> functions, Configuration configuration)
{
Register(Process(functions, configuration));
}

public static void Register(IEnumerable<ExcelFunctionRegistration> functions)
{
functions = functions.ToList();
var lambdas = functions.Select(reg => reg.FunctionLambda).ToList();
var attribs = functions.Select(reg => reg.FunctionAttribute).ToList<object>();
var argAttribs = functions.Select(reg => reg.ParameterRegistrations.Select(pr => pr.ArgumentAttribute).ToList<object>()).ToList();
ExcelIntegration.RegisterLambdaExpressions(lambdas, attribs, argAttribs);
}

public static IEnumerable<ExcelFunctionRegistration> Process(IEnumerable<ExcelFunctionRegistration> 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<ExcelDna.Registration.ExcelFunctionRegistration> functions)
{
functions = functions.ToList();
var lambdas = functions.Select(reg => reg.FunctionLambda).ToList();
var attribs = functions.Select(reg => reg.FunctionAttribute).ToList<object>();
var argAttribs = functions.Select(reg => reg.ParameterRegistrations.Select(pr => pr.ArgumentAttribute).ToList<object>()).ToList();
ExcelIntegration.RegisterLambdaExpressions(lambdas, attribs, argAttribs);
;
}

static ParameterConversionConfiguration GetParameterConversionConfig(IEnumerable<ExcelParameterConversion> parameterConversions, IEnumerable<ExcelReturnConversion> returnConversions)
private static ParameterConversionConfiguration GetParameterConversionConfig(IEnumerable<ExcelParameterConversion> parameterConversions, IEnumerable<ExcelReturnConversion> returnConversions)
{
// NOTE: The parameter conversion list is processed once per parameter.
// Parameter conversions will apply from most inside, to most outside.
Expand Down Expand Up @@ -93,7 +106,7 @@ static ParameterConversionConfiguration GetParameterConversionConfig(IEnumerable
return paramConversionConfig;
}

static FunctionExecutionConfiguration GetFunctionExecutionHandlerConfig(IEnumerable<FunctionExecutionHandlerSelector> excelFunctionExecutionHandlerSelectors)
private static FunctionExecutionConfiguration GetFunctionExecutionHandlerConfig(IEnumerable<FunctionExecutionHandlerSelector> excelFunctionExecutionHandlerSelectors)
{
FunctionExecutionConfiguration result = new FunctionExecutionConfiguration();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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
}
Expand Down
14 changes: 10 additions & 4 deletions Source/ExcelDna.Integration/Registration/ExcelRegistration.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down Expand Up @@ -33,13 +30,22 @@ where mi.GetCustomAttribute<ExcelFunctionAttribute>() != null
select new ExcelFunctionRegistration(mi);
}

/// <summary>
/// Prepares the given functions for registration with Excel-DNA.
/// </summary>
/// <param name="registrationEntries"></param>
public static IEnumerable<ExcelFunctionRegistration> ProcessFunctions(this IEnumerable<ExcelFunctionRegistration> registrationEntries)
{
return Integration.ExtendedRegistration.Registration.Process(registrationEntries, DnaLibrary.ExtendedRegistrationConfiguration);
}

/// <summary>
/// Registers the given functions with Excel-DNA.
/// </summary>
/// <param name="registrationEntries"></param>
public static void RegisterFunctions(this IEnumerable<ExcelFunctionRegistration> registrationEntries)
{
ExcelDna.Integration.ExtendedRegistration.Registration.Register(registrationEntries);
Integration.ExtendedRegistration.Registration.Register(registrationEntries);
}

/// <summary>
Expand Down
16 changes: 16 additions & 0 deletions Source/Tests/ExcelDna.AddIn.RuntimeTests/AddIn.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using ExcelDna.Integration;

namespace ExcelDna.AddIn.RuntimeTests
{
public class AddIn : IExcelAddIn
{
public void AutoOpen()
{
DynamicFunctions.Register();
}

public void AutoClose()
{
}
}
}
53 changes: 53 additions & 0 deletions Source/Tests/ExcelDna.AddIn.RuntimeTests/DynamicFunctions.cs
Original file line number Diff line number Diff line change
@@ -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));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
using System.Linq.Expressions;
using System.Reflection;

#nullable disable

namespace ExcelDna.AddIn.RuntimeTests
{
/// <summary>
Expand Down
25 changes: 25 additions & 0 deletions Source/Tests/ExcelDna.RuntimeTests/Registration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
}
}