Excel-DNA doesn't support for AOT Native

164 views
Skip to first unread message

Laurent Beaumer

unread,
Jun 20, 2025, 2:46:25 AM6/20/25
to Excel-DNA
Dear Govert van Drimmelen

I wrote a minimalist project to show that ExcelDNA doesn't support AOT Native (.net 8).


There is no error message, the Excel function #VALUE instead of "Say Hello".

AOT Native is essential for me to protect the code against reverse engineering.

Thank you :)

Govert van Drimmelen

unread,
Jun 20, 2025, 3:37:25 AM6/20/25
to exce...@googlegroups.com

Hi Laurent,

 

Excel-DNA does not support NativeAOT yet.

We do plan to start testing support for this soon, and quite a bit of work has already been done.

 

If you want to encourage ongoing work on Excel-DNA, please sign up to Sponsor @Excel-DNA on GitHub Sponsors.

We also offer direct corporate support agreements, for those using Excel-DNA in a mission critical setting, or with specific requirements.

You can contact me directly for more details.

 

-Govert

--
You received this message because you are subscribed to the Google Groups "Excel-DNA" group.
To unsubscribe from this group and stop receiving emails from it, send an email to exceldna+u...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/exceldna/3efd6f98-438d-47f7-a7a5-94047743963cn%40googlegroups.com.

Laurent Beaumer

unread,
Jun 20, 2025, 6:42:57 AM6/20/25
to Excel-DNA
Thanks Govert :)

I look forward to it.

Govert van Drimmelen

unread,
Feb 16, 2026, 5:34:41 AM (7 days ago) Feb 16
to Excel-DNA
Hi Laurent,

We've now published to NuGet the first package versions with .NET 10 NativeAOT support, as version 1.10.0-preview1.
There are some documents to get started with here:  .NET Native AOT support | Excel-DNA

Please post to the Google group or add an issue in GitHub if you have any feedback.

Regards,
Govert

Snake David

unread,
Feb 18, 2026, 9:32:58 AM (5 days ago) Feb 18
to Excel-DNA

Thanks so much! I've waited this day for too long! However when I try to run the test code it shows "Loading ExcelDna.ManagedHost failed: 0x80131513" and the demo can't be working,I'm not sure if my setting is correct: 

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

  <PropertyGroup>
    <TargetFramework>net10.0-windows</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
   <PublishAOT>true</PublishAOT>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
  </PropertyGroup>

<PropertyGroup>
<ExcelDnaToolsPath>$(PkgExcelDna_AddIn)\\tools\\</ExcelDnaToolsPath>
</PropertyGroup>

  <ItemGroup>
    <PackageReference Include="ExcelDna.AddIn" Version=" 1.10.0-preview1 " />
    <PackageReference Include="ExcelDna.AddIn.NativeAOT" Version="1.10.0-preview1" />
    <PackageReference Include="ExcelDna.IntelliSense" Version="1.10.0-preview1" />
    </ItemGroup>

  
</Project>

Govert van Drimmelen

unread,
Feb 18, 2026, 10:11:28 AM (5 days ago) Feb 18
to Excel-DNA
I'll check against the published Nuget packages a bit later and confirm whether I see anything.

-Govert

Govert van Drimmelen

unread,
Feb 18, 2026, 4:24:15 PM (5 days ago) Feb 18
to Excel-DNA
OK - my docs might not be helping much, but it looks like it can be made to work. Let me explain what I did.

Firstly, the IntelliSense will not work on NativeAOT yet - that will be a whole other project and depends on the level of Windows Forms support for NativeAOT, which I am struggling to pin down. This issue seems to be the best tracking one, and you can judge progress from there:  Epic - Make WinForms trim compatible · Issue #4649 · dotnet/winforms
Don't hold your breath - this is not likely this year.

Next, you should not put both ExcelDna.AddIn and ExcelDna.AddIn.NativeAOT in the same project.
I've added an issue to clarify the intended layout, but for now, just test it with the NativeAOT package only (or set up some conditional compilation).
After that you can remove the ExcelDnaToolsPath story too. We'll track this and figure out how to make better from this issue: NativeAOT preview: Review NuGet package usage · Issue #833 · Excel-DNA/ExcelDna

So now you have only something like this

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

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

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

  <ItemGroup>

    <PackageReference Include="ExcelDna.AddIn.NativeAOT" Version="1.10.0-preview1" />
  </ItemGroup>

</Project>

To publish as NativeAOT you have to add a publish step, via Build -> Publish Selection:

publish.png

You can make a similar one for the Debug output.

Then when you compile and the publish, you should get three files like this

"C:\Temp\TestDnaNative110p1\bin\Release\net10.0-windows\win-x64\publish\win-x64\TestDnaNative110p1.pdb"
"C:\Temp\TestDnaNative110p1\bin\Release\net10.0-windows\win-x64\publish\win-x64\TestDnaNative110p1-AddIn64.xll"
"C:\Temp\TestDnaNative110p1\bin\Release\net10.0-windows\win-x64\publish\win-x64\TestDnaNative110p1.dll"

The AddIn64-xll is your single file self-contained add-in.
You can copy it to another directory or machine and it should work.

Let me know if you can get it to work or if you're still stuck.

-Govert

Snake David

unread,
Feb 18, 2026, 10:02:37 PM (5 days ago) Feb 18
to Excel-DNA
thanks for your quickly response,but I found if I remove  ExcelDna.AddIn and left  ExcelDna.AddIn.NativeAOT only ,the attributes of exceldna like  [ExcelFunction]  can't work anymore,the IDE warns me "can't find namespace ExcelFunction,lack of using....."

Govert van Drimmelen

unread,
Feb 19, 2026, 12:39:33 AM (4 days ago) Feb 19
to exce...@googlegroups.com

That’s strange – I have an ExcelFunction attribute on the function and no problem.

 

My code file (matching the project file I posted) looks like this:

 

using ExcelDna.Integration;

 

namespace TestDnaNative110p1

{

    public static class MyFunctions

    {

        [ExcelFunction(Description = "My first .NET function")]

        public static string SayHello(string name)

        {

            return "Hello from NativeAOT " + name;

        }

    }

}

 

 

You can also try the simple project as I have it, from GitHub: govert/TestDnaNative110p1

Snake David

unread,
Feb 19, 2026, 2:45:33 AM (4 days ago) Feb 19
to Excel-DNA
I tried your sample and it worked with the demo UDF sayHello.But when I try to tranfer one of my UDF from 1.9.0 to AOT version,there's a warning messagewindow showed me :


 Initialization [Error] Integration.DnaLibraryAutoOpen Error : NotSupportedException - 'System.Linq.Expressions.Expression`1[System.Func`15[System.Object,System.Double,System.Object,System.Object,System.Object,System.Object,System.Object,System.Object,System.Object,System.Object,System.Object,System.Object,System.Object,System.Object,System.Threading.Tasks.Task`1[System.Object]]]' is missing native code or metadata. This can happen for code that is not compatible with trimming or AOT. Inspect and fix trimming and AOT related warnings that were generated when the app was published. For more information see https://aka.ms/nativeaot-compatibility 

warning.jpg

and here's my using namespaces:

using ExcelDna.Integration;
using ExcelDna.Registration;
using System.Buffers;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Runtime.CompilerServices;

actually ,my test UDF is used for calculating SubsetSUM question,so it's typically Compute Intensive,I think it's a good sample for comparing the difference between AOT and JIT

Govert van Drimmelen

unread,
Feb 19, 2026, 2:54:25 AM (4 days ago) Feb 19
to exce...@googlegroups.com

It looks like it’s async –can you make a small UDF that fails and post the code here – then I can try easily?

 

Thanks,

Govert

 

 

From: exce...@googlegroups.com <exce...@googlegroups.com> On Behalf Of Snake David
Sent: 19 February 2026 09:46
To: Excel-DNA <exce...@googlegroups.com>
Subject: Re: [ExcelDna] Excel-DNA doesn't support for AOT Native

 

I tried your sample and it worked with the demo UDF sayHello.But when I try to tranfer one of my UDF from 1.9.0 to AOT version,there's a warning messagewindow showed me :

 

 

 Initialization [Error] Integration.DnaLibraryAutoOpen Error : NotSupportedException - 'System.Linq.Expressions.Expression`1[System.Func`15[System.Object,System.Double,System.Object,System.Object,System.Object,System.Object,System.Object,System.Object,System.Object,System.Object,System.Object,System.Object,System.Object,System.Object,System.Threading.Tasks.Task`1[System.Object]]]' is missing native code or metadata. This can happen for code that is not compatible with trimming or AOT. Inspect and fix trimming and AOT related warnings that were generated when the app was published. For more information see https://aka.ms/nativeaot-compatibility 


image001.jpg

Snake David

unread,
Feb 19, 2026, 4:25:48 AM (4 days ago) Feb 19
to Excel-DNA
here's my whole codes,I've deleted multiThreadings part,but it still warns the same message:

using ExcelDna.Integration;
using ExcelDna.Registration;
using System.Buffers;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace TestDnaNative110p1
{
    public static class MyFunctions
    {
        private static int maxDepthReached = 0;
        private static int _numberCount;
        private static long _longTarget;
        private static int _resultsLimit;
        private static long[] _intNumbersArray = [];
        internal static CancellationTokenSource _cts = new();
        internal static List<List<long>> _results = [];
        private static int _faultRange;
        private static long _fixedTargetAdd;
        private static long _fixedTargetSub;
        private static long _numberSign;
        private static long[] _sourceNumbers = [];
        private static Dictionary<int, List<List<long>>> _resultsCache = [];
        private static Dictionary<int, long> _numberSignCache = [];
        private static int _resultHashCode;
        private static bool _isMultiThreading;
        private static long _recursiveCount = 0;
        private static readonly Lock _lock = new();


        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static bool TryGetNumericValue(object? cell, out double value)
        {
            value = 0;

            
            if (cell is null)
                return false;

            switch (cell)
            {
                case sbyte or byte or short or ushort or int or uint or long or ulong
                    or float or double or decimal:
                    value = Convert.ToDouble(cell);
                    return true;

                case string s:
                    return double.TryParse(s, out value);

                default:
                    return false;
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static int GenerateCombinationHash(long[] combination, int length)
        {
            HashCode hash = new();
            for (int i = 0; i < length; i++)
            {
                hash.Add(combination[i]);
            }
            return hash.ToHashCode();
        }


        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static void PreProcessData(ref object[,] RefArray, double TargetValue, int maxCombinations, int faultTolerance,
        double maxRunTime, int ignoreNegative, int returnMode, int maxDepth, int maxExpension)

        {
            int negativeCount = 0;
            List<long> intNumbers = [];

            _sourceNumbers = new long[RefArray.Length];
            long negetiveTotal = 0;
            long positiveTotal = 0;

            int idx = 0;
            int validCount = 0;

            foreach (var cell in RefArray)
            {
                if (TryGetNumericValue(cell, out double num))
                {
                    validCount++;

                    if (num != 0)
                    {
                        long scaled = (long)Math.Round(num * 100);

                        if (ignoreNegative == 1)
                        {
                            intNumbers.Add(scaled);
                        }
                        else
                        {
                            if (scaled > 0)
                                intNumbers.Add(scaled);
                        }

                        //intNumbers.Add(scaled);
                        _sourceNumbers[idx] = scaled;

                        if (num < 0)
                        {
                            negetiveTotal += scaled;
                            negativeCount++;
                        }
                        else
                        {
                            positiveTotal += scaled;
                        }

                    }
                }

                idx++;
            }

            if (validCount == 0)
                return;

            //候选数字按降序排列
            intNumbers.Sort();
            intNumbers.Reverse();

            _faultRange = Math.Abs(faultTolerance * 100);
            _resultsLimit = maxCombinations;

           
            if (-negetiveTotal > positiveTotal && ignoreNegative == 1)
            //if (negativeCount > intNumbers.Count / 2)
            {
                _numberSign = -1;
                _longTarget = -(long)Math.Round(TargetValue * 100); // 将目标值转换为整数
                for (int i = 0; i < intNumbers.Count; i++)
                {
                    intNumbers[i] = -intNumbers[i];
                }
            }
            else
            {
                _numberSign = 1;
                _longTarget = (long)Math.Round(TargetValue * 100); // 将目标值转换为整数
            }
            //_longTarget = intTargetValue;

            _fixedTargetAdd = _longTarget + _faultRange;
            _fixedTargetSub = _longTarget - _faultRange;

            _numberCount = intNumbers.Count;

            _intNumbersArray = intNumbers.ToArray();

            //根据候选数字、目标和、结果数量上限、容错值和组合包含的最大数字个数生成结果哈希码
            _resultHashCode = GenerateResultsHash(_intNumbersArray, TargetValue, maxCombinations, faultTolerance, maxDepth);
        }

        private static int GenerateResultsHash(long[] ValidNumbers, double target, int resultsCount, int faultTolerance, int maxDepth)
        {
            
            HashCode hash = new();
            foreach (var item in ValidNumbers)
            {
                hash.Add(item);
            }
            hash.Add(target);
            hash.Add(resultsCount);
            hash.Add(faultTolerance);
            hash.Add(maxDepth);
            return hash.ToHashCode();
        }


        [ExcelAsyncFunction]
        public static async Task<object> SubsetSUMbackground(object RefRange, double TargetValue, int maxCombinations = 1, int faultTolerance = 0,
        int displayMode = 0, int enableMultiThreadings = 0, double maxRunTime = 10.0, bool forVBA = false, int ignoreNegative = 0,
        int returnMode = 0, bool isCache = true, bool isByRibbon = false, int maxDepth = 0, int maxExpension = 0)
        {
            _results.Clear();
            const int adaptiveTime = 400; // 自适应模式初始单线程运行时间,单位毫秒
            var finishedEvent = new ManualResetEventSlim(false);// 创建一个信号量,用于通知看门狗“任务已完成”

            _ = Task.Factory.StartNew(() =>
            {
                _cts = new CancellationTokenSource();
                TimeSpan timeLimit = new TimeSpan();

                if (enableMultiThreadings == 0)//自适应模式默认先以单线程运行400毫秒
                    timeLimit = TimeSpan.FromMilliseconds(adaptiveTime);
                else
                    timeLimit = TimeSpan.FromSeconds(maxRunTime);

                if (!finishedEvent.Wait(timeLimit))
                {
                    _cts.Cancel();
                }
                finishedEvent.Dispose();
            }, TaskCreationOptions.LongRunning);


            int currentRow = 0;
            int currentColumn = 0;
                currentRow = 1;
                currentColumn = 1;

            var seenHashSet = new HashSet<int>();



            if (RefRange is not object[,] arr)
                return "#未找到有效的候选数字";

            if (arr.GetLength(1) > 1 && returnMode != 0)
                return "#按位置返回仅支持按列排布的候选数字";

            _resultHashCode = 0;
            
            PreProcessData(ref arr, TargetValue, maxCombinations, faultTolerance, maxRunTime, ignoreNegative, returnMode, maxDepth, maxExpension);


            if (_numberCount == 0)
                return "#未找到有效的候选数字";

            
            maxDepthReached = maxDepth == 0 ? _numberCount : maxDepth - 1;

           
            if (isCache && _resultsCache.TryGetValue(_resultHashCode, out var value2))
            {
                _results = new List<List<long>>(value2); ;
                _numberSign = _numberSignCache[_resultHashCode];
                return ProcessResults(currentRow, currentColumn, maxCombinations, displayMode, forVBA, returnMode, ignoreNegative, isByRibbon);
            }


            
            var remainingPositiveSum = new long[_numberCount];
            var remainingNegativeSum = new long[_numberCount];

            long positiveSum = 0;
            long negativeSum = 0;

            for (int i = _numberCount - 1; i >= 0; i--)
            {
                if (_intNumbersArray[i] > 0)
                    positiveSum += _intNumbersArray[i];
                else if (_intNumbersArray[i] < 0)
                    negativeSum += _intNumbersArray[i]; // 负值

                remainingPositiveSum[i] = _fixedTargetSub - positiveSum;
                remainingNegativeSum[i] = ignoreNegative == 0 ? _fixedTargetAdd : _fixedTargetAdd - negativeSum;
            }

            return await Task.Run(() =>
            {
                var stopwatch = Stopwatch.StartNew();

                Action<int, int, long[], HashSet<int>, long, long[], long[]> searchStrategy;

                if (faultTolerance != 0)
                {
                    searchStrategy = _numberCount > 5000 ? DepthFirstSearchWithFaultLoop : DepthFirstSearchWithFault;
                }
                else
                {
                    searchStrategy = _numberCount > 5000 ? DepthFirstSearchLoop : DepthFirstSearch;
                }

                    _isMultiThreading = false;
                    long[] combination = new long[_numberCount];
                    for (int i = 0; i < _numberCount; i++)
                    {
                        combination[0] = _intNumbersArray[i];
                        searchStrategy(i + 1, 1, combination, seenHashSet, _intNumbersArray[i], remainingPositiveSum, remainingNegativeSum);
                    }
                    finishedEvent.Set();


                if (isCache && _results.Count > 0)
                {
                    int currentHashCode = GenerateResultsHash(_intNumbersArray, TargetValue, maxCombinations, faultTolerance, maxDepth);
                    _resultsCache[currentHashCode] = new List<List<long>>(_results); ; // 深拷贝以防后续修改
                    _numberSignCache[currentHashCode] = _numberSign;
                }

                return ProcessResults(currentRow, currentColumn, maxCombinations, displayMode, forVBA, returnMode, ignoreNegative, isByRibbon);
            });
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static object ProcessResults(int startRow, int startColumn, int maxCombinations, int displayMode, bool isVBA, int returnmode, int ignoreNegative, bool isRibbon)
        {
            if (_results.Count == 0)
            {
                return ignoreNegative == 0 ? "#无解,请尝试计入负数" : "#无解";
            }
            else
            {
                if (returnmode == 0)
                {
                    double[,] resultArray;
                    int resultCount = Math.Min(_results.Count, maxCombinations);
                    int maxSize = 0;

                    if (_results.Count > 0)
                    {
                        foreach (var r in _results)
                        {
                            if (r.Count > maxSize)
                            {
                                maxSize = r.Count;
                            }
                        }
                    }
                    var sortedResults = new List<List<long>>(_results);

                    sortedResults.Sort((a, b) =>
                    {
                        int compareCount = a.Count.CompareTo(b.Count);
                        if (compareCount != 0)
                        {
                            return compareCount;
                        }
                        long firstA = a.Count > 0 ? a[0] : 0; // 等价于 FirstOrDefault
                        long firstB = b.Count > 0 ? b[0] : 0;

                        return firstA.CompareTo(firstB);
                    });

                    // 2. 替换原来的 results
                    _results = sortedResults;
                    int count = 0;
                    if (displayMode == 1)
                    {
                        resultArray = new double[_results.Count, maxSize]; // 按行显示
                        foreach (var item in _results)
                        {
                            for (int j = 0; j < item.Count; j++)
                            {
                                double originalValue = _numberSign * item[j] / 100.0;
                                resultArray[count, j] = originalValue;
                            }
                            count++;
                        }
                    }
                    else // displayMode为2
                    {
                        resultArray = new double[maxSize, _results.Count]; // 按列显示
                        foreach (var item in _results)
                        {
                            for (int j = 0; j < item.Count; j++)
                            {
                                double originalValue = _numberSign * item[j] / 100.0;
                                resultArray[j, count] = originalValue;
                            }
                            count++;
                        }

                    }
                    return resultArray;
                }
                else
                {
                    string[,] resultArray = new string[_sourceNumbers.Length, _results.Count];
                    bool[] used = new bool[_sourceNumbers.Length];

                    var sortedResults = new List<List<long>>(_results);

                    sortedResults.Sort((a, b) =>
                    {
                        int compareCount = a.Count.CompareTo(b.Count);
                        if (compareCount != 0)
                        {
                            return compareCount;
                        }
                        long firstA = a.Count > 0 ? a[0] : 0; // 等价于 FirstOrDefault
                        long firstB = b.Count > 0 ? b[0] : 0;

                        return firstA.CompareTo(firstB);
                    });
                    _results = sortedResults;

                    int count = 0;
                    foreach (var combination in _results)
                    {
                        Array.Clear(used, 0, used.Length); // 每组重置
                        foreach (var value in combination)
                        {
                            long key = value; //* _numberSign;
                            for (int i = 0; i < _sourceNumbers.Length; i++)
                            {
                                if (!used[i] && _sourceNumbers[i] * _numberSign == key)
                                {
                                    resultArray[i, count] = "●";
                                    used[i] = true;
                                    break;
                                }
                            }
                        }
                        count++;
                    }

                    return resultArray;
                }
            }
        }



        [MethodImpl(MethodImplOptions.AggressiveInlining)]

        private static void DepthFirstSearchWithFaultLoop(int startIndex, int depth, long[] combination,
        HashSet<int> seenCombinations, long currentSum, long[] remainingPositiveSum, long[] remainingNegativeSum)
        {
            var indexStack = new Stack<int>();

            int i = startIndex;

            while (true)
            {
                if (_cts.Token.IsCancellationRequested || _results.Count >= _resultsLimit || depth > maxDepthReached)
                {
                    return;
                }

                if (i < _numberCount)
                {
                    if (currentSum < remainingPositiveSum[i] || currentSum > remainingNegativeSum[i])
                    {
                        i = _numberCount;
                        continue;
                    }

                    combination[depth] = _intNumbersArray[i];
                    currentSum += _intNumbersArray[i];

                    long temSum = Math.Abs(currentSum - _longTarget);
                    if (temSum <= _faultRange)
                    {
                        int combinationHash = GenerateCombinationHash(combination, depth);
                        if (_isMultiThreading)
                        {
                            lock (_lock)
                            {
                                TryAddResult(GenerateCombinationHash(combination, depth), combination, depth, seenCombinations);
                            }
                        }
                        else
                            TryAddResult(GenerateCombinationHash(combination, depth), combination, depth, seenCombinations);

                        currentSum -= _intNumbersArray[i];
                        i++;
                    }
                    else
                    {
                        indexStack.Push(i);

                        depth++;
                        i = i + 1;
                    }
                }
                else
                {
                    if (indexStack.Count == 0)
                    {
                        return;
                    }

                    int last_i = indexStack.Pop();
                    depth--;
                    currentSum -= _intNumbersArray[last_i];
                    i = last_i + 1;
                }
            }
        }


        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static void DepthFirstSearchWithFault(int startIndex, int depth, long[] combination,
        HashSet<int> seenCombinations, long currentSum, long[] remainingPositiveSum, long[] remainingNegativeSum)
        {

            long temSum = Math.Abs(currentSum - _longTarget);
            if (temSum <= _faultRange)
            {
                int combinationHash = GenerateCombinationHash(combination, depth);
                if (_isMultiThreading)
                {
                    lock (_lock)
                    {
                        TryAddResult(GenerateCombinationHash(combination, depth), combination, depth, seenCombinations);
                    }
                }
                else
                    TryAddResult(GenerateCombinationHash(combination, depth), combination, depth, seenCombinations);

                return;
            }


            for (int i = startIndex; i < _numberCount; i++)
            {

                if (_cts.Token.IsCancellationRequested || _results.Count >= _resultsLimit || depth > maxDepthReached)
                    return;

                if (currentSum < remainingPositiveSum[i])
                    return;
                if (currentSum > remainingNegativeSum[i])
                    return;

                combination[depth] = _intNumbersArray[i];
                DepthFirstSearchWithFault(i + 1, depth + 1, combination, seenCombinations, currentSum + _intNumbersArray[i], remainingPositiveSum, remainingNegativeSum);
            }

        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]

        private static void DepthFirstSearchLoop(int startIndex, int depth, long[] combination,
        HashSet<int> seenCombinations, long currentSum, long[] remainingPositiveSum, long[] remainingNegativeSum)
        {
            var indexStack = new Stack<int>();

            int i = startIndex;

            while (true)
            {
                if (_cts.Token.IsCancellationRequested || _results.Count >= _resultsLimit || depth > maxDepthReached)
                {
                    return;
                }

                if (i < _numberCount)
                {
                    if (currentSum < remainingPositiveSum[i] || currentSum > remainingNegativeSum[i])
                    {
                        i = _numberCount;
                        continue;
                    }

                    combination[depth] = _intNumbersArray[i];
                    currentSum += _intNumbersArray[i];

                    if (currentSum == _longTarget)
                    {
                        int combinationHash = GenerateCombinationHash(combination, depth + 1);

                        if (_isMultiThreading)
                        {
                            lock (_lock)
                            {
                                TryAddResult(GenerateCombinationHash(combination, depth), combination, depth, seenCombinations);
                            }
                        }
                        else
                            TryAddResult(GenerateCombinationHash(combination, depth), combination, depth, seenCombinations);

                        currentSum -= _intNumbersArray[i];
                        i++;
                    }
                    else
                    {
                        indexStack.Push(i);

                        depth++;
                        i = i + 1;
                    }
                }
                else
                {
                    if (indexStack.Count == 0)
                    {
                        return;
                    }
                    int last_i = indexStack.Pop();
                    depth--;
                    currentSum -= _intNumbersArray[last_i];
                    i = last_i + 1;
                }
            }
        }


        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static void DepthFirstSearch(int startIndex, int depth, long[] combination,
        HashSet<int> seenCombinations, long currentSum, long[] remainingPositiveSum, long[] remainingNegativeSum)
        {

            if (currentSum == _longTarget)
            {
                if (_isMultiThreading)
                {
                    lock (_lock)
                    {
                        TryAddResult(GenerateCombinationHash(combination, depth), combination, depth, seenCombinations);
                    }
                }
                else
                    TryAddResult(GenerateCombinationHash(combination, depth), combination, depth, seenCombinations);

                return;
            }


            for (int i = startIndex; i < _numberCount; i++)
            {
                if (_cts.Token.IsCancellationRequested || _results.Count >= _resultsLimit || depth > maxDepthReached)
                    return;


                if (currentSum < remainingPositiveSum[i])
                    return;

                if (currentSum > remainingNegativeSum[i])
                    return;

                combination[depth] = _intNumbersArray[i];
                DepthFirstSearch(i + 1, depth + 1, combination, seenCombinations, currentSum + _intNumbersArray[i], remainingPositiveSum, remainingNegativeSum);

            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static void TryAddResult(int hash, long[] combination, int depth, HashSet<int> seenCombinations)
        {
            if (seenCombinations.Add(hash))
            {
                var list = new List<long>(depth);
                for (int i = 0; i < depth; i++)
                {
                    list.Add(combination[i]);
                }
                _results.Add(list);
            }
        }


    }
}

Govert van Drimmelen

unread,
Feb 19, 2026, 4:54:38 AM (4 days ago) Feb 19
to exce...@googlegroups.com

OK – I can compile your code and I get the same error you report when I run it.

 

I will investigate further.

Snake David

unread,
Feb 19, 2026, 5:12:52 AM (4 days ago) Feb 19
to Excel-DNA
Thanks for your help,I think I can get much fun during my lunar new year holidays now😁

Govert van Drimmelen

unread,
Feb 19, 2026, 4:06:13 PM (4 days ago) Feb 19
to exce...@googlegroups.com

I understand the issue and will try to fix over the weekend.
But functions with fewer than 16 arguments, and simpler arguments, so the function does not need to be re-written during registration, will probably work.

Snake David

unread,
Feb 19, 2026, 11:00:43 PM (4 days ago) Feb 19
to Excel-DNA
I'm really looking forward to your follow-up updates. 

You know, for that program above, I'd previously tried using AI to translate it into C++ code. When I compared them, the latter's performance was only about 10% better than the C# version, but it skips that annoying JIT warm-up stutter on the first run. I figure that's a common pain point for these kinds of compute-intensive functions.

Govert van Drimmelen

unread,
Feb 22, 2026, 8:36:15 AM (yesterday) Feb 22
to Excel-DNA
Hi David,

I've now published an update - v1.10.0-preview4, which should address your error.
Please let me know if you get a chance to try it, and if you run into any other issues with the Native AOT preview.

Regards,
Govert

Snake David

unread,
Feb 22, 2026, 9:21:53 AM (yesterday) Feb 22
to Excel-DNA

I tested the new version with great enthusiasm. For the code snippet I previously shared, it is now fully functional. Both the computation speed and the results are exactly as expected.

However, once I switched back to the original full implementation—the version that supports multi-threading and worked correctly under JIT mode—the issue resurfaced. The UDF immediately returns “No solution” as soon as I press Enter.

Since the AOT build cannot be debugged through the usual approach, I attached Visual Studio to the running published add-in process for debugging. When execution reaches the line:

var tasks = new List<PartialTask>();

and here's the struct be like 


private struct PartialTask

{
    public int StartIndex;     
    public int Depth;     
    public long CurrentSum;  
    public long[] Prefix;
}

Visual Studio consistently displays the message:


can't find AllocFast.asm to view the source for the current call stack frame.(it's tranlated from chinese ,so i'm not sure the exact version in english)

After that, the breakpoint is effectively skipped and debugging cannot proceed any further.


Overall, I believe this is a very promising start. Even in single-threaded mode, the fast cold-start characteristics of AOT are already becoming apparent. 


Thank you for your hard work.


Govert van Drimmelen

unread,
Feb 22, 2026, 10:49:24 AM (yesterday) Feb 22
to exce...@googlegroups.com

That sounds like progress.

If you could make a small example that fails, that I can run easily, I can get us a step further.

Snake David

unread,
5:06 AM (6 hours ago) 5:06 AM
to Excel-DNA

Later, I removed the branching logic for the single-threaded and adaptive-threaded modes from the code, leaving only the multi-threaded implementation. Interestingly, in this configuration the code runs correctly.

Given that the current AOT build cannot be debugged using the standard Visual Studio debugging workflow, and that the publish + attach-to-process approach fails to capture certain execution steps, it is difficult for me to precisely isolate the root cause of the issue.

As a side note, I benchmarked the multi-threaded subset-sum computation under both JIT and AOT modes. Using the same test dataset, the JIT version took 1250 ms for the first run, and between 822–893 ms for the subsequent four runs. In contrast, the AOT version consistently completed in the 1030–1085 ms range.

Govert van Drimmelen

unread,
5:22 AM (6 hours ago) 5:22 AM
to exce...@googlegroups.com

Hi David,

 

Thanks for the great feedback.

We’ll work on the debugging a bit to see what configuration works best.

 

I’m not sure what to expect from a performance perspective.
It might be possible to make the startup a little faster for large add-ins, but I don’t expect a runtime difference (and am not surprised that you say runtime is a bit slower).

I suppose the main advantage is not having to deal with the runtime distribution.

Snake David

unread,
11:12 AM (9 minutes ago) 11:12 AM
to Excel-DNA

It has been a genuine privilege to contribute, even in a small way, to the progress of the Excel-DNA project. I’m very much looking forward to seeing how far AOT support will have matured by the time the official 1.10 release.

For me, IntelliSense integration is the most immediate priority. Ideally, building on the existing JIT-based experience, it would be even better if parameter-aware dropdown suggestions could appear dynamically as users type—especially for enumerable arguments😁. I’m not sure whether AI-assisted tooling could play a role here, but it would certainly be an exciting direction to explore.

Reply all
Reply to author
Forward
0 new messages