Skip to main content

.NET Native AOT support

Excel-DNA can produce native 64-bit Excel add-ins, that can run on machines that don't have the .NET runtime installed, using .NET 8.0 Native AOT deployment and ExcelDna.AddIn.NativeAOT package.

Publishing the following MyAddin.csproj C# project produces native 64-bit MyAddin-AddIn64.xll Excel add-in:

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PublishAOT>true</PublishAOT>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="ExcelDna.AddIn.NativeAOT" Version="0.1.0" />
</ItemGroup>

</Project>

Supported functionality in native add-ins

Function

internal class Functions
{
[ExcelFunction]
public static string NativeHello(string name)
{
return $"Hello {name}!";
}

[ExcelFunction]
public static int NativeSum(int i1, int i2)
{
return i1 + i2;
}
}
CellFormulaResult
A1=NativeHello("AOT")Hello AOT!
A2=NativeSum(2, 3)5

Async functions and tasks

[ExcelAsyncFunction]
public static async Task<string> NativeAsyncTaskHello(string name, int msDelay)
{
await Task.Delay(msDelay);
return $"Hello native async task {name}";
}

[ExcelAsyncFunction]
public static string NativeAsyncHello(string name, int msToSleep)
{
Thread.Sleep(msToSleep);
return $"Hello native async {name}";
}

[ExcelFunction]
public static Task<string> NativeTaskHello(string name)
{
return Task.FromResult($"Hello native task {name}");
}
CellFormulaImmediate ResultFinal Result
A1=NativeAsyncTaskHello("Test", 5000)#N/AHello native async task Test
B1=NativeAsyncHello("Test", 5000)#N/AHello native async Test
C1=NativeTaskHello("Test")Hello native task TestHello native task Test

AddIn

public class AddIn : IExcelAddIn
{
public void AutoOpen()
{
var thisAddInName = Path.GetFileName((string)XlCall.Excel(XlCall.xlGetName));
var message = string.Format("Excel-DNA Native AOT Add-In '{0}' loaded!", thisAddInName);

MessageBox.Show(message, thisAddInName, MessageBoxButtons.OK, MessageBoxIcon.Information);
}

public void AutoClose()
{
}
}

AutoOpen

Command

[ExcelCommand(MenuText = "MyNativeCommand")]
public static void NativeCommand()
{
MessageBox.Show("My NativeCommand");
}

Ribbon

public class RibbonController : IExcelRibbon
{
public string GetCustomUI(string RibbonID)
{
return @"
<customUI xmlns='http://schemas.microsoft.com/office/2006/01/customui'>
<ribbon>
<tabs>
<tab id='tab1' label='My Native Tab'>
<group id='group1' label='My Native Group'>
<button id='button1' tag='tagdata1' label='My Native Button1' onAction='OnButtonPressed1'/>
<button id='button2' tag='tagdata2' label='My Native Button2' onAction='OnButtonPressed2'/>
</group >
</tab>
</tabs>
</ribbon>
</customUI>";
}

public void OnButtonPressed1(RibbonControl control)
{
MessageBox.Show($"Hello1 from native control id={control.Id} tag={control.Tag}");
}

public void OnButtonPressed2(RibbonControl control)
{
MessageBox.Show($"Hello2 from native control id={control.Id} tag={control.Tag}");
}
}

DynamicApplication

To use the Excel COM object model from your command, function or ribbon handler, call ExcelDnaUtil.DynamicApplication. It returns an Excel.Application object as IDynamic, providing Get, Set, [] and Invoke methods:

[ExcelFunction]
public static string NativeApplicationName()
{
return (string)ExcelDnaUtil.DynamicApplication.Get("Name")!;
}

[ExcelFunction]
public static double NativeApplicationGetCellValue(string cell)
{
var workbook = (IDynamic)ExcelDnaUtil.DynamicApplication.Get("ActiveWorkbook")!;
var sheets = (IDynamic)workbook.Get("Sheets")!;
var sheet = (IDynamic)sheets[1]!;
var range = (IDynamic)sheet.Get("Range", [cell])!;
return (double)range.Get("Value")!;
}

[ExcelFunction]
public static double NativeApplicationGetCellValueT(string cell)
{
var workbook = ExcelDnaUtil.DynamicApplication.Get<IDynamic>("ActiveWorkbook");
var sheets = workbook.Get<IDynamic>("Sheets");
var sheet = (IDynamic)sheets[1]!;
var range = sheet.Get<IDynamic>("Range", [cell]);
return range.Get<double>("Value");
}

[ExcelFunction]
public static int NativeApplicationAlignCellRight(string cell)
{
var workbook = ExcelDnaUtil.DynamicApplication.Get<IDynamic>("ActiveWorkbook");
var sheets = workbook.Get<IDynamic>("Sheets");
var sheet = (IDynamic)sheets[1]!;
var range = sheet.Get<IDynamic>("Range", [cell]);
range.Set("HorizontalAlignment", -4152);
return range.Get<int>("HorizontalAlignment");
}

[ExcelFunction]
public static string NativeApplicationAddCellComment(string cell, string comment)
{
var workbook = ExcelDnaUtil.DynamicApplication.Get<IDynamic>("ActiveWorkbook");
var sheets = workbook.Get<IDynamic>("Sheets");
var sheet = (IDynamic)sheets[1]!;
var range = sheet.Get<IDynamic>("Range", [cell]);
var newComment = (IDynamic)range.Invoke("AddComment", [comment])!;
return newComment.Invoke<string>("Text", []);
}
CellFormulaResult
C1123.45
A1=NativeApplicationName()Microsoft Excel
A2=NativeApplicationGetCellValue("C1")123.45
A3=NativeApplicationGetCellValueT("C1")123.45
A4=NativeApplicationAlignCellRight("C1")-4152
A5=NativeApplicationAddCellComment("C1", "My comment.")My comment.
[ExcelCommand(MenuName = "Test", MenuText = "Range Set")]
public static void RangeSet()
{
IDynamic xlApp = ExcelDnaUtil.DynamicApplication;

xlApp.Get<IDynamic>("Range", ["F1"]).Set("Value", "Testing 1... 2... 3... 4");
}

IntelliSense

It is possible to get limited information regarding the UDF. The information can be seen by writing the name of the function in Excel's Forumla Bar and clicking on the fx button.

[ExcelFunction(Description = "A useful test function that adds two numbers, and returns the sum.")]
public static double AddThem(
[ExcelArgument(Name = "Augend", Description = "is the first number, to which will be added")]
double v1,
[ExcelArgument(Name = "Addend", Description = "is the second number that will be added")]
double v2)
{
return v1 + v2;
}

Accepting range parameters in UDFs

[ExcelFunction]
public static object NativeRangeConcat2(object[,] values)
{
string result = "";
int rows = values.GetLength(0);
int cols = values.GetLength(1);
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
object value = values[i, j];
result += value.ToString();
}
}
return result;
}
CellFormulaResult
A1str
A21
B1TRUE
B23.5
C1=NativeRangeConcat2(A1:B2)strTrue13.5

Extended Registration

Nullable parameter

[ExcelFunction]
public static string NativeNullableDouble(double? d)
{
return "Native Nullable VAL: " + (d.HasValue ? d : "NULL");
}
CellFormulaResult
A1=NativeNullableDouble(1.2)Native Nullable VAL: 1.2
A2=NativeNullableDouble()Native Nullable VAL: NULL

Optional parameter

[ExcelFunction]
public static string NativeOptionalDouble(double d = 1.23)
{
return "Native Optional VAL: " + d.ToString();
}
CellFormulaResult
A1=NativeOptionalDouble(2.3)Native Optional VAL: 2.3
A2=NativeOptionalDouble()Native Optional VAL: 1.23

Range parameter

[ExcelFunction]
public static string NativeRangeAddress(IRange r)
{
return "Native Address: " + r.Get<string>("Address");
}
CellFormulaResult
A1=NativeRangeAddress(B2)Native Address: $B$2
A2=NativeRangeAddress(B2:C4)Native Address: $B$2:$C$4
A3=NativeRangeAddress((B2,D5:E6))Native Address: $B$2,$D$5:$E$6

Enums parameter and return value

[ExcelFunction]
public static string NativeEnum(DateTimeKind e)
{
return "Native Enum VAL: " + e.ToString();
}

[ExcelFunction]
public static DateTimeKind NativeEnumReturn(string s)
{
return Enum.Parse<DateTimeKind>(s);
}
CellFormulaResult
A1=NativeEnum("Unspecified")Native Enum VAL: Unspecified
A2=NativeEnum("Local")Native Enum VAL: Local
A3=NativeEnum(1)Native Enum VAL: Utc
A4=NativeEnumReturn("Unspecified")Unspecified
A5=NativeEnumReturn("Local")Local

String array parameter

[ExcelFunction]
public static string NativeStringArray(string[] s)
{
return "Native StringArray VALS: " + string.Concat(s);
}
CellFormulaResult
A101
A22.30
A3World
B1=NativeStringArray(A1:A3)Native StringArray VALS: 12.3World

String array 2D parameter

[ExcelFunction]
public static string NativeStringArray2D(string[,] s)
{
string result = "";
for (int i = 0; i < s.GetLength(0); i++)
{
for (int j = 0; j < s.GetLength(1); j++)
{
result += s[i, j];
}

result += " ";
}

return $"Native StringArray2D VALS: {result}";
}
CellFormulaResult
A101
A22.30
A3Hello
B15
B26.7
B3World
C1=NativeStringArray2D(A1:B3)Native StringArray2D VALS: 15 2.36.7 HelloWorld

params parameter (up to 16 arguments)

[ExcelFunction]
public static string NativeParamsFunc1(
[ExcelArgument(Name = "first.Input", Description = "is a useful start")]
object input,
[ExcelArgument(Description = "is another param start")]
string QtherInpEt,
[ExcelArgument(Name = "Value", Description = "gives the Rest")]
params object[] args)
{
return input + "," + QtherInpEt + ", : " + args.Length;
}

[ExcelFunction]
public static string NativeParamsFunc2(
[ExcelArgument(Name = "first.Input", Description = "is a useful start")]
object input,
[ExcelArgument(Name = "second.Input", Description = "is some more stuff")]
string input2,
[ExcelArgument(Description = "is another param ")]
string QtherInpEt,
[ExcelArgument(Name = "Value", Description = "gives the Rest")]
params object[] args)
{
var content = string.Join(",", args.Select(ValueType => ValueType.ToString()));
return input + "," + input2 + "," + QtherInpEt + ", " + $"[{args.Length}: {content}]";
}

[ExcelFunction]
public static string NativeParamsJoinString(string separator, params string[] values)
{
return String.Join(separator, values);
}
CellFormulaResult
A1=NativeParamsFunc1(1,"2",4,5)1,2, : 2
A2=NativeParamsFunc2("a",,"c","d",,"f")a,,c, [3: d,ExcelDna.Integration.ExcelMissing,f]
A3=NativeParamsJoinString("//","5","4","3")5//4//3

Object handles

Create and reuse .NET objects:

public class Calc
{
private double d1, d2;

public Calc(double d1, double d2)
{
this.d1 = d1;
this.d2 = d2;
}

public double Sum()
{
return d1 + d2;
}
}

[ExcelFunction]
[return: ExcelHandle]
public static Calc NativeCreateCalc(double d1, double d2)
{
return new Calc(d1, d2);
}

[ExcelFunction]
public static double NativeCalcSum([ExcelHandle] Calc c)
{
return c.Sum();
}
CellFormulaResult
A1=NativeCreateCalc(1.2, 3.4)NativeCreateCalc:1
A2=NativeCalcSum(A1)4.6
[ExcelHandle]
public class CalcExcelHandle
{
private double d1, d2;

public CalcExcelHandle(double d1, double d2)
{
this.d1 = d1;
this.d2 = d2;
}

public double Mul()
{
return d1 * d2;
}
}

[ExcelFunction]
public static CalcExcelHandle NativeCreateCalcExcelHandle(double d1, double d2)
{
return new CalcExcelHandle(d1, d2);
}

[ExcelFunction]
public static double NativeCalcExcelHandleMul(CalcExcelHandle c)
{
return c.Mul();
}
CellFormulaResult
A1=NativeCreateCalcExcelHandle(1.4, 0.5)NativeCreateCalcExcelHandle:1
A2=NativeCalcExcelHandleMul(A1)0.7
[assembly: ExcelHandleExternal(typeof(System.Reflection.Assembly))]

[ExcelFunction]
public static System.Reflection.Assembly NativeGetExecutingAssembly()
{
return System.Reflection.Assembly.GetExecutingAssembly();
}

[ExcelFunction]
public static string? NativeGetAssemblyName(System.Reflection.Assembly assembly)
{
return assembly.GetName().Name;
}
CellFormulaResult
A1=NativeGetExecutingAssembly()NativeGetExecutingAssembly:1
A2=NativeGetAssemblyName(A1)ExcelDna.AddIn.RuntimeTestsAOT64

User defined parameter and return value conversions

public class TestType1
{
public string Value;

public TestType1(string value)
{
Value = value;
}
}

public class Conversions
{
[ExcelParameterConversion]
public static Version ToVersion(string s)
{
return new Version(s);
}

[ExcelReturnConversion]
public static string FromTestType1(TestType1 value)
{
return value.Value;
}
}

[ExcelFunction]
public static string NativeVersion2(Version v)
{
return "The Native Version value with field count 2 is " + v.ToString(2);
}

[ExcelFunction]
public static TestType1 NativeReturnTestType1(string s)
{
return new TestType1("The Native TestType1 return value is " + s);
}
CellFormulaResult
A1=NativeVersion2("4.3.2.1")The Native Version value with field count 2 is 4.3
A2=NativeReturnTestType1("world")The Native TestType1 return value is world

Function execution handler

public class FunctionLoggingHandler : FunctionExecutionHandler
{
public override void OnSuccess(FunctionExecutionArgs args)
{
System.Diagnostics.Trace.WriteLine($"OnSuccess - Result: {args.ReturnValue}");
}

[ExcelFunctionExecutionHandlerSelector]
public static IFunctionExecutionHandler LoggingHandlerSelector(IExcelFunctionInfo functionInfo)
{
return new FunctionLoggingHandler();
}
}
CellFormulaResult
A1=NativeHello("world")Hello world!
DebugViewOnSuccess - Result: Hello world!

Not supported functionality in native add-ins

Loading images for ribbon controls.

Copy to clipboard in Diagnostic Display.