Skip to content

Commit 3dbc995

Browse files
authored
Merge pull request #781 from Excel-DNA/DynamicExtendedRegistration
Added extended registration transformations pipeline for explicit/dynamic functions
2 parents 950da5e + 98220a2 commit 3dbc995

File tree

10 files changed

+174
-33
lines changed

10 files changed

+174
-33
lines changed

Source/ExcelDna.Integration/AssemblyLoader.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ public static void ProcessAssemblies(
3636
List<Type> rtdServerTypes,
3737
List<ExcelComClassType> comClassTypes)
3838
{
39-
bool loadRibbons = true;
40-
4139
foreach (ExportedAssembly assembly in assemblies)
4240
{
4341
int initialObjectsCount = methods.Count +

Source/ExcelDna.Integration/DnaLibrary.cs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -257,16 +257,10 @@ internal List<ExportedAssembly> GetAssemblies(string pathResolveRoot)
257257
[XmlIgnore]
258258
private List<MethodInfo> _methods = new List<MethodInfo>();
259259
[XmlIgnore]
260-
List<ExtendedRegistration.ExcelParameterConversion> _excelParameterConversions = new List<ExtendedRegistration.ExcelParameterConversion>();
261-
[XmlIgnore]
262-
List<ExtendedRegistration.ExcelReturnConversion> _excelReturnConversions = new List<ExtendedRegistration.ExcelReturnConversion>();
260+
private ExtendedRegistration.Registration.Configuration _extendedRegistrationConfiguration;
263261
[XmlIgnore]
264262
private List<Registration.ExcelFunctionRegistration> _excelFunctionsExtendedRegistration = new List<Registration.ExcelFunctionRegistration>();
265263
[XmlIgnore]
266-
private List<Registration.FunctionExecutionHandlerSelector> _excelFunctionExecutionHandlerSelectors = new List<Registration.FunctionExecutionHandlerSelector>();
267-
[XmlIgnore]
268-
private List<ExtendedRegistration.ExcelFunctionProcessor> _excelFunctionProcessors = new List<ExtendedRegistration.ExcelFunctionProcessor>();
269-
[XmlIgnore]
270264
private List<ExportedAssembly> _exportedAssemblies;
271265

272266
// The idea is that Initialize compiles, loads and sorts out the assemblies,
@@ -283,7 +277,14 @@ internal void Initialize()
283277

284278
// Recursively get assemblies down .dna tree.
285279
_exportedAssemblies = GetAssemblies(dnaResolveRoot);
286-
AssemblyLoader.ProcessAssemblies(_exportedAssemblies, _methods, _excelParameterConversions, _excelReturnConversions, _excelFunctionProcessors, _excelFunctionsExtendedRegistration, _excelFunctionExecutionHandlerSelectors, _addIns, rtdServerTypes, comClassTypes);
280+
281+
var excelParameterConversions = new List<ExtendedRegistration.ExcelParameterConversion>();
282+
var excelReturnConversions = new List<ExtendedRegistration.ExcelReturnConversion>();
283+
var excelFunctionExecutionHandlerSelectors = new List<Registration.FunctionExecutionHandlerSelector>();
284+
var excelFunctionProcessors = new List<ExtendedRegistration.ExcelFunctionProcessor>();
285+
AssemblyLoader.ProcessAssemblies(_exportedAssemblies, _methods, excelParameterConversions, excelReturnConversions, excelFunctionProcessors, _excelFunctionsExtendedRegistration, excelFunctionExecutionHandlerSelectors, _addIns, rtdServerTypes, comClassTypes);
286+
_extendedRegistrationConfiguration = new ExtendedRegistration.Registration.Configuration() { ParameterConversions = excelParameterConversions, ReturnConversions = excelReturnConversions, ExcelFunctionProcessors = excelFunctionProcessors, ExcelFunctionExecutionHandlerSelectors = excelFunctionExecutionHandlerSelectors };
287+
287288
NativeAOT.ExcelAddIns.ForEach(i => AssemblyLoader.GetExcelAddIns(null, i, _addIns));
288289

289290
// 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()
333334
ExcelIntegration.RegisterMethods(commands);
334335

335336
var functions = _methods.Except(commands).Select(i => new Registration.ExcelFunctionRegistration(i)).Concat(_excelFunctionsExtendedRegistration);
336-
ExtendedRegistration.Registration.RegisterExtended(functions, _excelParameterConversions, _excelReturnConversions, _excelFunctionProcessors, _excelFunctionExecutionHandlerSelectors);
337+
ExtendedRegistration.Registration.Register(functions, _extendedRegistrationConfiguration);
337338

338339
// Invoke AutoOpen in all assemblies
339340
foreach (AssemblyLoader.ExcelAddInInfo addIn in _addIns)
@@ -624,6 +625,14 @@ internal static string ExecutingDirectory
624625
}
625626
}
626627

628+
internal static ExtendedRegistration.Registration.Configuration ExtendedRegistrationConfiguration
629+
{
630+
get
631+
{
632+
return CurrentLibrary._extendedRegistrationConfiguration;
633+
}
634+
}
635+
627636
public string ResolvePath(string path)
628637
{
629638
return ResolvePath(path, dnaResolveRoot);

Source/ExcelDna.Integration/ExcelAttributes.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,22 @@ public ExcelFunctionAttribute(string description)
3838
{
3939
Description = description;
4040
}
41+
42+
public ExcelFunctionAttribute(ExcelFunctionAttribute src)
43+
{
44+
Category = src.Category;
45+
Name = src.Name;
46+
Description = src.Description;
47+
HelpTopic = src.HelpTopic;
48+
IsVolatile = src.IsVolatile;
49+
IsHidden = src.IsHidden;
50+
IsExceptionSafe = src.IsExceptionSafe;
51+
IsMacroType = src.IsMacroType;
52+
IsThreadSafe = src.IsThreadSafe;
53+
IsClusterSafe = src.IsClusterSafe;
54+
ExplicitRegistration = src.ExplicitRegistration;
55+
SuppressOverwriteError = src.SuppressOverwriteError;
56+
}
4157
}
4258

4359
/// <summary>

Source/ExcelDna.Integration/ExtendedRegistration/Registration.cs

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,35 +10,48 @@ namespace ExcelDna.Integration.ExtendedRegistration
1010
{
1111
internal class Registration
1212
{
13-
public static void RegisterExtended(IEnumerable<ExcelDna.Registration.ExcelFunctionRegistration> functions, IEnumerable<ExcelParameterConversion> parameterConversions, IEnumerable<ExcelReturnConversion> returnConversions, IEnumerable<ExcelFunctionProcessor> excelFunctionProcessors, IEnumerable<FunctionExecutionHandlerSelector> excelFunctionExecutionHandlerSelectors)
13+
public class Configuration
14+
{
15+
public IEnumerable<ExcelParameterConversion> ParameterConversions { get; set; }
16+
public IEnumerable<ExcelReturnConversion> ReturnConversions { get; set; }
17+
public IEnumerable<ExcelFunctionProcessor> ExcelFunctionProcessors { get; set; }
18+
public IEnumerable<FunctionExecutionHandlerSelector> ExcelFunctionExecutionHandlerSelectors { get; set; }
19+
}
20+
21+
public static void Register(IEnumerable<ExcelFunctionRegistration> functions, Configuration configuration)
22+
{
23+
Register(Process(functions, configuration));
24+
}
25+
26+
public static void Register(IEnumerable<ExcelFunctionRegistration> functions)
27+
{
28+
functions = functions.ToList();
29+
var lambdas = functions.Select(reg => reg.FunctionLambda).ToList();
30+
var attribs = functions.Select(reg => reg.FunctionAttribute).ToList<object>();
31+
var argAttribs = functions.Select(reg => reg.ParameterRegistrations.Select(pr => pr.ArgumentAttribute).ToList<object>()).ToList();
32+
ExcelIntegration.RegisterLambdaExpressions(lambdas, attribs, argAttribs);
33+
}
34+
35+
public static IEnumerable<ExcelFunctionRegistration> Process(IEnumerable<ExcelFunctionRegistration> functions, Configuration configuration)
1436
{
1537
// Set the Parameter Conversions before they are applied by the ProcessParameterConversions call below.
1638
// CONSIDER: We might change the registration to be an object...?
17-
var conversionConfig = GetParameterConversionConfig(parameterConversions, returnConversions);
39+
var conversionConfig = GetParameterConversionConfig(configuration.ParameterConversions, configuration.ReturnConversions);
1840

19-
var functionHandlerConfig = GetFunctionExecutionHandlerConfig(excelFunctionExecutionHandlerSelectors);
41+
var functionHandlerConfig = GetFunctionExecutionHandlerConfig(configuration.ExcelFunctionExecutionHandlerSelectors);
2042

21-
Register(functions
43+
return functions
2244
.UpdateRegistrationsForRangeParameters()
23-
.ProcessFunctionProcessors(excelFunctionProcessors, conversionConfig)
45+
.ProcessFunctionProcessors(configuration.ExcelFunctionProcessors, conversionConfig)
2446
.ProcessParameterConversions(conversionConfig)
2547
.ProcessAsyncRegistrations(nativeAsyncIfAvailable: false)
2648
.ProcessParamsRegistrations()
2749
.ProcessObjectHandles()
2850
.ProcessFunctionExecutionHandlers(functionHandlerConfig)
29-
);
30-
}
31-
32-
internal static void Register(IEnumerable<ExcelDna.Registration.ExcelFunctionRegistration> functions)
33-
{
34-
functions = functions.ToList();
35-
var lambdas = functions.Select(reg => reg.FunctionLambda).ToList();
36-
var attribs = functions.Select(reg => reg.FunctionAttribute).ToList<object>();
37-
var argAttribs = functions.Select(reg => reg.ParameterRegistrations.Select(pr => pr.ArgumentAttribute).ToList<object>()).ToList();
38-
ExcelIntegration.RegisterLambdaExpressions(lambdas, attribs, argAttribs);
51+
;
3952
}
4053

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

96-
static FunctionExecutionConfiguration GetFunctionExecutionHandlerConfig(IEnumerable<FunctionExecutionHandlerSelector> excelFunctionExecutionHandlerSelectors)
109+
private static FunctionExecutionConfiguration GetFunctionExecutionHandlerConfig(IEnumerable<FunctionExecutionHandlerSelector> excelFunctionExecutionHandlerSelectors)
97110
{
98111
FunctionExecutionConfiguration result = new FunctionExecutionConfiguration();
99112

Source/ExcelDna.Integration/Registration/AsyncRegistration.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public static class AsyncRegistration
4343
ParameterConversionRegistration.ApplyParameterConversions(reg, ObjectHandleRegistration.GetParameterConversionConfiguration());
4444
reg.FunctionLambda = WrapMethodObservable(reg.FunctionLambda, reg.Return.CustomAttributes);
4545
}
46-
else if (ReturnsTask(reg.FunctionLambda) || reg.FunctionAttribute is ExcelDna.Registration.ExcelAsyncFunctionAttribute)
46+
else if (ReturnsTask(reg.FunctionLambda) || reg.FunctionAttribute is ExcelAsyncFunctionAttribute)
4747
{
4848
ParameterConversionRegistration.ApplyParameterConversions(reg, ObjectHandleRegistration.GetParameterConversionConfiguration());
4949
if (HasCancellationToken(reg.FunctionLambda))
@@ -58,6 +58,9 @@ public static class AsyncRegistration
5858
reg.FunctionLambda = useNativeAsync ? WrapMethodNativeAsyncTask(reg.FunctionLambda)
5959
: WrapMethodRunTask(reg.FunctionLambda, reg.Return.CustomAttributes);
6060
}
61+
62+
if (reg.FunctionAttribute is ExcelAsyncFunctionAttribute)
63+
reg.FunctionAttribute = new ExcelFunctionAttribute(reg.FunctionAttribute);
6164
}
6265
// else do nothing to this registration
6366
}

Source/ExcelDna.Integration/Registration/ExcelRegistration.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
using ExcelDna.Integration;
2-
using System;
32
using System.Collections.Generic;
43
using System.Linq;
54
using System.Reflection;
6-
using System.Text;
7-
using System.Threading.Tasks;
85

96
namespace ExcelDna.Registration
107
{
@@ -33,13 +30,22 @@ where mi.GetCustomAttribute<ExcelFunctionAttribute>() != null
3330
select new ExcelFunctionRegistration(mi);
3431
}
3532

33+
/// <summary>
34+
/// Prepares the given functions for registration with Excel-DNA.
35+
/// </summary>
36+
/// <param name="registrationEntries"></param>
37+
public static IEnumerable<ExcelFunctionRegistration> ProcessFunctions(this IEnumerable<ExcelFunctionRegistration> registrationEntries)
38+
{
39+
return Integration.ExtendedRegistration.Registration.Process(registrationEntries, DnaLibrary.ExtendedRegistrationConfiguration);
40+
}
41+
3642
/// <summary>
3743
/// Registers the given functions with Excel-DNA.
3844
/// </summary>
3945
/// <param name="registrationEntries"></param>
4046
public static void RegisterFunctions(this IEnumerable<ExcelFunctionRegistration> registrationEntries)
4147
{
42-
ExcelDna.Integration.ExtendedRegistration.Registration.Register(registrationEntries);
48+
Integration.ExtendedRegistration.Registration.Register(registrationEntries);
4349
}
4450

4551
/// <summary>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using ExcelDna.Integration;
2+
3+
namespace ExcelDna.AddIn.RuntimeTests
4+
{
5+
public class AddIn : IExcelAddIn
6+
{
7+
public void AutoOpen()
8+
{
9+
DynamicFunctions.Register();
10+
}
11+
12+
public void AutoClose()
13+
{
14+
}
15+
}
16+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using ExcelDna.Registration;
2+
3+
namespace ExcelDna.AddIn.RuntimeTests
4+
{
5+
internal class DynamicFunctions
6+
{
7+
public static void Register()
8+
{
9+
{
10+
ExcelFunctionRegistration[] functions = {
11+
CreateRegistration(nameof(DynamicSayHello)),
12+
CreateRegistration(nameof(DynamicOptionalDouble)),
13+
ChangeName(CreateRegistration(nameof(ChangeMe)), "DynamicFunctionName"),
14+
};
15+
16+
ExcelRegistration.RegisterFunctions(ExcelRegistration.ProcessFunctions(functions));
17+
}
18+
{
19+
ExcelFunctionRegistration[] functions = {
20+
ChangeName(CreateRegistration(nameof(DynamicOptionalDouble)), "DynamicOptionalDoubleUnprocessed"),
21+
};
22+
23+
ExcelRegistration.RegisterFunctions(functions);
24+
}
25+
}
26+
27+
private static string DynamicSayHello(string name)
28+
{
29+
return $"Dynamic Hello {name}";
30+
}
31+
32+
private static string DynamicOptionalDouble(double d = 4.56)
33+
{
34+
return "Dynamic Optional VAL: " + d.ToString();
35+
}
36+
37+
private static string ChangeMe()
38+
{
39+
return $"Function {nameof(ChangeMe)}";
40+
}
41+
42+
private static ExcelFunctionRegistration ChangeName(ExcelFunctionRegistration reg, string name)
43+
{
44+
reg.FunctionAttribute.Name = name;
45+
return reg;
46+
}
47+
48+
private static ExcelFunctionRegistration CreateRegistration(string name)
49+
{
50+
return new ExcelFunctionRegistration(typeof(DynamicFunctions).GetMethod(name, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static));
51+
}
52+
}
53+
}

Source/Tests/ExcelDna.AddIn.RuntimeTests/MapArrayFunctionRegistration.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
using System.Linq.Expressions;
88
using System.Reflection;
99

10+
#nullable disable
11+
1012
namespace ExcelDna.AddIn.RuntimeTests
1113
{
1214
/// <summary>

Source/Tests/ExcelDna.RuntimeTests/Registration.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,5 +625,30 @@ public void Params()
625625
Assert.Equal("5//4//3", functionRange.Value.ToString());
626626
}
627627
}
628+
629+
[ExcelFact(Workbook = "", AddIn = AddInPath.RuntimeTests)]
630+
public void DynamicFunctions()
631+
{
632+
{
633+
Range functionRange = ((Worksheet)ExcelDna.Testing.Util.Workbook.Sheets[1]).Range["B1"];
634+
functionRange.Formula = "=DynamicSayHello(\"world\")";
635+
Assert.Equal("Dynamic Hello world", functionRange.Value.ToString());
636+
}
637+
{
638+
Range functionRange = ((Worksheet)ExcelDna.Testing.Util.Workbook.Sheets[1]).Range["C1"];
639+
functionRange.Formula = "=DynamicOptionalDouble()";
640+
Assert.Equal("Dynamic Optional VAL: 4.56", functionRange.Value.ToString());
641+
}
642+
{
643+
Range functionRange = ((Worksheet)ExcelDna.Testing.Util.Workbook.Sheets[1]).Range["D1"];
644+
functionRange.Formula = "=DynamicFunctionName()";
645+
Assert.Equal("Function ChangeMe", functionRange.Value.ToString());
646+
}
647+
{
648+
Range functionRange = ((Worksheet)ExcelDna.Testing.Util.Workbook.Sheets[1]).Range["E1"];
649+
functionRange.Formula = "=DynamicOptionalDoubleUnprocessed()";
650+
Assert.Equal("Dynamic Optional VAL: 0", functionRange.Value.ToString());
651+
}
652+
}
628653
}
629654
}

0 commit comments

Comments
 (0)