Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
33a67c1
Created a separate Registration.StaticRegistration class for compile …
Sergey-Vlasov Sep 6, 2025
81d7ed9
Removed Loader dependency on the NativeAOT class.
Sergey-Vlasov Sep 6, 2025
defcb1e
Removed the NativeAOT class.
Sergey-Vlasov Sep 6, 2025
44dc0a0
Isolated WinForms code with #if USE_WINDOWS_FORMS.
Sergey-Vlasov Sep 7, 2025
67b2d31
Added Win32 definitions.
Sergey-Vlasov Sep 13, 2025
d132dce
Enabled SynchronizationManager.
Sergey-Vlasov Sep 13, 2025
04f26ea
Added access to hModuleXll to ExcelDna.Integration.
Sergey-Vlasov Sep 14, 2025
952415f
Implemented SafeHandle wrapper for HINSTANCE.
Sergey-Vlasov Sep 14, 2025
43bbcae
Re-enabled FormulaEditMode detection.
Sergey-Vlasov Sep 14, 2025
77747b9
Implemented NativeWindow using Win32 API.
Sergey-Vlasov Sep 14, 2025
e5cb858
Merge pull request #797 from Excel-DNA/Win32
Sergey-Vlasov Sep 14, 2025
9b19774
Implemented minimal LogDisplay using Win32 API.
Sergey-Vlasov Sep 21, 2025
f3dfd2b
Added read only for log display text.
Sergey-Vlasov Sep 21, 2025
50dd51c
Added the ES_READONLY constant.
Sergey-Vlasov Sep 27, 2025
8c23786
Removed WindowsForms dependency.
Sergey-Vlasov Sep 27, 2025
82df310
Removed all LogDisplayForm files from AOT compilation.
Sergey-Vlasov Sep 27, 2025
ecff238
Added minimal native LogDisplay WriteLine support.
Sergey-Vlasov Sep 27, 2025
dd36c18
Merge pull request #800 from Excel-DNA/NativeLogView
Sergey-Vlasov Sep 27, 2025
8a27aaf
Removed WindowsForms dependency from the package.
Sergey-Vlasov Sep 28, 2025
2ae6714
Fixed command menu registration.
Sergey-Vlasov Sep 28, 2025
d57be91
Created ExcelDna.TestAOT project for manual tests.
Sergey-Vlasov Sep 28, 2025
4dbbca7
Added support for log message formatting.
Sergey-Vlasov Oct 4, 2025
42ea564
Created a common WndClassRegistration class.
Sergey-Vlasov Oct 4, 2025
1433491
Implemented support for custom ribbon images using Win32 API.
Sergey-Vlasov Oct 5, 2025
176bffa
Merge pull request #803 from Excel-DNA/RibbonPicture
Sergey-Vlasov Oct 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
<Project>
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<UseWindowsForms>true</UseWindowsForms>
<SelfContained>true</SelfContained>
</PropertyGroup>
</Project>
21 changes: 20 additions & 1 deletion Source/ExcelDna.Integration/ComInterop/Generator/ExcelRibbon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

using ExcelDna.Integration.ComInterop.Generator.Interfaces;
using ExcelDna.Integration.Extensibility;
using ExcelDna.Integration.Win32;
using System;
using System.Linq;
using System.Reflection;
Expand Down Expand Up @@ -36,7 +37,7 @@ public int GetTypeInfo(uint iTInfo, uint lcid, out nint ppTInfo)
public int GetIDsOfNames(Guid riid, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 2)] string[] rgszNames, uint cNames, uint lcid, [In][Out][MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] int[] rgDispId)
{
for (int i = 0; i < cNames; ++i)
rgDispId[i] = Array.FindIndex(methods, m => m.Name == rgszNames[i]);
rgDispId[i] = (rgszNames[i] == "LoadImage") ? methods.Length : Array.FindIndex(methods, m => m?.Name == rgszNames[i]);

return 0;
}
Expand All @@ -49,6 +50,12 @@ public int Invoke(int dispIdMember, Guid riid, uint lcid, INVOKEKIND wFlags, [Ma
methods[dispIdMember].Invoke(customRibbon, [ribbonControl]);
}

if (dispIdMember == methods.Length && pDispParams.cArgs == 1)
{
string resourceName = pDispParams.rgvarg[0].Value as string;
Dispatcher.SetResult(pVarResult, new DispatchObject(Picture.LoadAsIPictureDisp(LoadCustomRibbonResource(resourceName))));
}

return 0;
}

Expand Down Expand Up @@ -80,6 +87,18 @@ public int GetCustomUI([MarshalAs(UnmanagedType.BStr)] string RibbonID, [Marshal
return 0;
}
#endregion

private byte[] LoadCustomRibbonResource(string name)
{
using (var stream = customRibbon.GetType().Assembly.GetManifestResourceStream(name))
{
using (System.IO.MemoryStream memoryStream = new())
{
stream.CopyTo(memoryStream);
return memoryStream.ToArray();
}
}
}
}
}

Expand Down
22 changes: 20 additions & 2 deletions Source/ExcelDna.Integration/CustomUI/ExcelCommandBars.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,19 @@
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Serialization;
using ExcelDna.Serialization;

#if USE_WINDOWS_FORMS
using System.Windows.Forms;
#endif

namespace ExcelDna.Integration.CustomUI
{
#if USE_WINDOWS_FORMS
public delegate Bitmap GetImageDelegate(string imageName);
#endif

//public class ExcelCommandBars
//{
Expand Down Expand Up @@ -54,9 +59,12 @@ public static CommandBars GetCommandBars()

public static void LoadCommandBars(string xmlCustomUI)
{
#if USE_WINDOWS_FORMS
LoadCommandBars(xmlCustomUI, delegate (string imageName) { return null; });
#endif
}

#if USE_WINDOWS_FORMS
public static void LoadCommandBars(string xmlCustomUI, GetImageDelegate getImage)
{
string dnaLibraryWrapper = string.Format(@"<DnaLibrary><CustomUI>{0}</CustomUI></DnaLibrary>", xmlCustomUI);
Expand Down Expand Up @@ -92,6 +100,7 @@ internal static void LoadCommandBars(XmlNode xmlCustomUI, GetImageDelegate getIm
Debug.Print("ExcelCommandBars: Error adding controls: {0}", e);
}
}
#endif

public static void UnloadCommandBars()
{
Expand All @@ -117,6 +126,7 @@ public static void UnloadCommandBars()
loadedCustomUIs.Clear();
}

#if USE_WINDOWS_FORMS
private static void AddCommandBarControls(Application excelApp, XmlNodeList xmlNodes, GetImageDelegate getImage)
{
foreach (XmlNode childNode in xmlNodes)
Expand Down Expand Up @@ -151,6 +161,7 @@ private static void AddCommandBarControls(Application excelApp, XmlNodeList xmlN
}
}
}
#endif

private static void RemoveCommandBarControls(Application excelApp, XmlNodeList xmlNodes)
{
Expand Down Expand Up @@ -234,14 +245,15 @@ private static CommandBar GetCommandBarFromIdOrName(Application excelApp, XmlAtt
// }
//}


#if USE_WINDOWS_FORMS
private static void AddControls(CommandBarControls parentControls, XmlNodeList xmlNodes, GetImageDelegate getImage)
{
foreach (XmlNode childNode in xmlNodes)
{
AddControl(parentControls, childNode, getImage);
}
}
#endif

private static void RemoveControls(CommandBarControls parentControls, XmlNodeList xmlNodes)
{
Expand All @@ -251,6 +263,7 @@ private static void RemoveControls(CommandBarControls parentControls, XmlNodeLis
}
}

#if USE_WINDOWS_FORMS
private static void AddControl(CommandBarControls parentControls, XmlNode xmlNode, GetImageDelegate getImage)
{
if (xmlNode.Name == "popup")
Expand All @@ -268,6 +281,7 @@ private static void AddControl(CommandBarControls parentControls, XmlNode xmlNod
ApplyControlAttributes(newButton, xmlNode, getImage);
}
}
#endif

private static void RemoveControl(CommandBarControls parentControls, XmlNode xmlNode)
{
Expand Down Expand Up @@ -313,6 +327,7 @@ private static object ReadControlBeforeAttribute(XmlNode xmlNode)
return before;
}

#if USE_WINDOWS_FORMS
private static void ApplyControlAttributes(CommandBarControl control, XmlNode xmlNode, GetImageDelegate getImage)
{
foreach (XmlAttribute att in xmlNode.Attributes)
Expand Down Expand Up @@ -449,6 +464,7 @@ private static void ApplyControlAttribute(CommandBarControl control, string attr
break;
}
}
#endif

// Some minimal wrappers for the office types.
private class Application
Expand Down Expand Up @@ -951,6 +967,7 @@ internal CommandBarButton(object commandBarCom)
{
}

#if USE_WINDOWS_FORMS
public void SetButtonImage(Bitmap buttonImage)
{
// TODO: Consider using Picture property for Excel 2002+ (and Mask?)
Expand All @@ -964,6 +981,7 @@ public void SetButtonImage(Bitmap buttonImage)
Clipboard.Clear();
// Clipboard.SetDataObject(oldContent);
}
#endif

public int FaceId
{
Expand Down
4 changes: 4 additions & 0 deletions Source/ExcelDna.Integration/CustomUI/ExcelRibbon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,11 @@ public virtual string GetCustomUI(string RibbonID)
public virtual object LoadImage(string imageId)
{
// Default implementation ...
#if USE_WINDOWS_FORMS
return DnaLibrary.GetImage(imageId);
#else
return null;
#endif
}

// RunTagMacro helper function
Expand Down
37 changes: 28 additions & 9 deletions Source/ExcelDna.Integration/DnaLibrary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ internal static FileInfo XllPathInfo
}
}

private static bool _IsNativeAOTActive;
[XmlIgnore]
internal static bool IsNativeAOTActive
{
get
{
return _IsNativeAOTActive;
}
}

private string _Name;
[XmlAttribute]
public string Name
Expand Down Expand Up @@ -236,10 +246,12 @@ internal List<ExportedAssembly> GetAssemblies(string pathResolveRoot)
assemblies.AddRange(lib.GetAssemblies(pathResolveRoot, this));
}
}
#if USE_WINDOWS_FORMS
foreach (Project proj in GetProjects())
{
assemblies.AddRange(proj.GetAssemblies(pathResolveRoot, this));
}
#endif
}
catch (Exception e)
{
Expand Down Expand Up @@ -283,13 +295,13 @@ internal void Initialize()
var excelFunctionExecutionHandlerSelectors = new List<Registration.FunctionExecutionHandlerSelector>();
var excelFunctionProcessors = new List<ExtendedRegistration.ExcelFunctionProcessor>();
AssemblyLoader.ProcessAssemblies(_exportedAssemblies, _methods, excelParameterConversions, excelReturnConversions, excelFunctionProcessors, _excelFunctionsExtendedRegistration, excelFunctionExecutionHandlerSelectors, _addIns, rtdServerTypes, comClassTypes);
AssemblyLoader.GetExcelParameterConversions(NativeAOT.ExcelParameterConversions, excelParameterConversions);
AssemblyLoader.GetExcelReturnConversions(NativeAOT.ExcelReturnConversions, excelReturnConversions);
AssemblyLoader.GetExcelFunctionExecutionHandlerSelectors(NativeAOT.ExcelFunctionExecutionHandlerSelectors, excelFunctionExecutionHandlerSelectors);
AssemblyLoader.GetExcelParameterConversions(Registration.StaticRegistration.ExcelParameterConversions, excelParameterConversions);
AssemblyLoader.GetExcelReturnConversions(Registration.StaticRegistration.ExcelReturnConversions, excelReturnConversions);
AssemblyLoader.GetExcelFunctionExecutionHandlerSelectors(Registration.StaticRegistration.ExcelFunctionExecutionHandlerSelectors, excelFunctionExecutionHandlerSelectors);
_extendedRegistrationConfiguration = new ExtendedRegistration.Registration.Configuration() { ParameterConversions = excelParameterConversions, ReturnConversions = excelReturnConversions, ExcelFunctionProcessors = excelFunctionProcessors, ExcelFunctionExecutionHandlerSelectors = excelFunctionExecutionHandlerSelectors };

NativeAOT.ExcelAddIns.ForEach(i => AssemblyLoader.GetExcelAddIns(null, i, _addIns));
ObjectHandles.ObjectHandleRegistration.ProcessAssemblyAttributes(NativeAOT.AssemblyAttributes);
Registration.StaticRegistration.ExcelAddIns.ForEach(i => AssemblyLoader.GetExcelAddIns(null, i, _addIns));
ObjectHandles.ObjectHandleRegistration.ProcessAssemblyAttributes(Registration.StaticRegistration.AssemblyAttributes);

// Register RTD Server Types (i.e. remember that these types are available as RTD servers, with relevant ProgId etc.)
RtdRegistration.RegisterRtdServerTypes(rtdServerTypes.Select(i => new TypeHelperDynamic(i)));
Expand Down Expand Up @@ -331,7 +343,7 @@ internal void AutoOpen()
RegistrationInfo.Register();
SynchronizationManager.Install(true);

AssemblyLoader.GetExcelMethods(NativeAOT.MethodsForRegistration, true, _methods, _excelFunctionsExtendedRegistration);
AssemblyLoader.GetExcelMethods(Registration.StaticRegistration.MethodsForRegistration, true, _methods, _excelFunctionsExtendedRegistration);

// Register my Methods
List<MethodInfo> commands = _methods.Where(Registration.ExcelCommandRegistration.IsCommand).ToList();
Expand Down Expand Up @@ -451,7 +463,9 @@ internal void LoadCustomUI()
{
if (xmlCustomUI.LocalName == "commandBars")
{
#if USE_WINDOWS_FORMS
ExcelCommandBarUtil.LoadCommandBars(xmlCustomUI, this.GetImage);
#endif
}
}
}
Expand All @@ -476,7 +490,7 @@ internal IEnumerable<Assembly> GetExportedAssemblies()

// Statics
private static DnaLibrary rootLibrary;
internal static void InitializeRootLibrary(string xllPath)
internal static void InitializeRootLibrary(string xllPath, bool isNativeAOTActive)
{
// Loads the primary .dna library
// Load sequence is:
Expand All @@ -486,7 +500,10 @@ internal static void InitializeRootLibrary(string xllPath)
// CAREFUL: Sequence here is fragile - this is the first place where we start logging
_XllPath = xllPath;
_xllPathPathInfo = new FileInfo(xllPath);
_IsNativeAOTActive = isNativeAOTActive;
#if USE_WINDOWS_FORMS
Logging.LogDisplay.CreateInstance();
#endif
Logger.Initialization.Verbose("Enter DnaLibrary.InitializeRootLibrary");
byte[] dnaBytes = ExcelIntegration.GetDnaFileBytes("__MAIN__");
if (dnaBytes != null)
Expand All @@ -508,7 +525,7 @@ internal static void InitializeRootLibrary(string xllPath)
// If there have been problems, ensure that there is at lease some current library.
if (rootLibrary == null)
{
if (!NativeAOT.IsActive)
if (!IsNativeAOTActive)
Logger.Initialization.Error("No Dna Library found.");
rootLibrary = new DnaLibrary();
}
Expand Down Expand Up @@ -551,7 +568,7 @@ public static DnaLibrary LoadFrom(string fileName)

if (!File.Exists(fileName))
{
if (!NativeAOT.IsActive)
if (!IsNativeAOTActive)
Logger.Initialization.Error("The required .dna script file {0} does not exist.", fileName);
return null;
}
Expand Down Expand Up @@ -724,6 +741,7 @@ public static string ResolvePath(string path, string dnaDirectory)
return null;
}

#if USE_WINDOWS_FORMS
public Bitmap GetImage(string imageId)
{
// We expect these to be small images.
Expand Down Expand Up @@ -765,6 +783,7 @@ public Bitmap GetImage(string imageId)
}
return null;
}
#endif

}

Expand Down
7 changes: 5 additions & 2 deletions Source/ExcelDna.Integration/Excel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@ private static extern int AccessibleObjectFromWindow(

private static bool checkForIllegalCrossThreadCalls;

internal static void Initialize()
internal static void Initialize(IntPtr hModuleXll)
{
ModuleXll = hModuleXll;

// NOTE: Sequence here is important - Getting the Window Handle sometimes uses the _mainNativeThreadId
_mainManagedThreadId = Thread.CurrentThread.ManagedThreadId;
_mainNativeThreadId = GetCurrentThreadId();
Expand All @@ -76,6 +78,8 @@ internal static bool IsMainThread
}
}

internal static IntPtr ModuleXll { get; private set; }

public static int MainManagedThreadId
{
get { return _mainManagedThreadId; }
Expand Down Expand Up @@ -459,7 +463,6 @@ private static object GetApplicationFromWindow(IntPtr hWndMain, bool allowProtec

// Marshal to .NET, then call .Application
object obj = ComInterop.Util.TypeAdapter.GetObject(pUnk);
Marshal.Release(pUnk);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can we skip the Release here? It seems we end up calling Marshal.GetObjectForIUnknown which increments the reference count.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I understand,

The reference count will be decremented when the runtime performs garbage collection on the managed object that represents the COM object.

https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.getobjectforiunknown?view=net-9.0#remarks

In our case it will be decremented on line 510

                finally
                {
                    ComInterop.Util.TypeAdapter.ReleaseObject(obj);
                }

Calling Marshal.Release(pUnk) manually will over decrement the reference count.


try
{
Expand Down
18 changes: 17 additions & 1 deletion Source/ExcelDna.Integration/ExcelDna.Integration.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
<PropertyGroup>

<TargetFrameworks>net462;net6.0-windows;net8.0-windows</TargetFrameworks>
<UseWindowsForms>true</UseWindowsForms>

<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);CS1591</NoWarn>
Expand All @@ -21,6 +20,16 @@
<DefineConstants>$(DefineConstants);COM_GENERATED</DefineConstants>
</PropertyGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net8.0-windows'">
<Compile Remove="LogDisplay.cs" />
<Compile Remove="LogDisplay.Designer.cs" />
</ItemGroup>

<PropertyGroup Condition="'$(TargetFramework)' != 'net8.0-windows'">
<UseWindowsForms>true</UseWindowsForms>
<DefineConstants>$(DefineConstants);USE_WINDOWS_FORMS</DefineConstants>
</PropertyGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net462' ">
<Reference Include="System.Windows.Forms" />
<Reference Include="Microsoft.VisualBasic" />
Expand All @@ -30,4 +39,11 @@
<PackageReference Include="ExcelDna.Interop" Version="15.0.1" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net8.0-windows'">
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.205">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

</Project>
6 changes: 3 additions & 3 deletions Source/ExcelDna.Integration/ExcelIntegration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,11 +198,11 @@ internal static Assembly LoadFromAssemblyBytes(byte[] assemblyBytes, byte[] pdbB
return _integrationHost.LoadFromAssemblyBytes(assemblyBytes, pdbBytes);
}

internal static void Initialize(string xllPath)
internal static void Initialize(string xllPath, bool isNativeAOTActive, IntPtr hModuleXll)
{
ExcelDnaUtil.Initialize(); // Set up window handle
ExcelDnaUtil.Initialize(hModuleXll); // Set up window handle
Logging.TraceLogger.Initialize();
DnaLibrary.InitializeRootLibrary(xllPath);
DnaLibrary.InitializeRootLibrary(xllPath, isNativeAOTActive);
}

// Called via Reflection from Loader
Expand Down
Loading