Remove OLE interop dependency.

This commit is contained in:
Ottermandias 2023-05-25 21:16:52 +02:00
parent 96bb94b9d5
commit 0c53741b1d
6 changed files with 76 additions and 39 deletions

View file

@ -8,6 +8,7 @@ using System.Threading.Tasks;
using Dalamud.Configuration.Internal; using Dalamud.Configuration.Internal;
using Dalamud.Game; using Dalamud.Game;
using Dalamud.Game.Gui.Internal; using Dalamud.Game.Gui.Internal;
using Dalamud.Interface.DragDrop;
using Dalamud.Interface.Internal; using Dalamud.Interface.Internal;
using Dalamud.Plugin.Internal; using Dalamud.Plugin.Internal;
using Dalamud.Utility; using Dalamud.Utility;
@ -135,6 +136,9 @@ internal sealed class Dalamud : IServiceType
// will not receive any windows messages // will not receive any windows messages
Service<DalamudIME>.GetNullable()?.Dispose(); Service<DalamudIME>.GetNullable()?.Dispose();
// this must be done before unloading interface manager, since it relies on the window handle members.
Service<DragDropManager>.GetNullable()?.Dispose();
// this must be done before unloading plugins, or it can cause a race condition // this must be done before unloading plugins, or it can cause a race condition
// due to rendering happening on another thread, where a plugin might receive // due to rendering happening on another thread, where a plugin might receive
// a render call after it has been disposed, which can crash if it attempts to // a render call after it has been disposed, which can crash if it attempts to

View file

@ -82,7 +82,6 @@
</PackageReference> </PackageReference>
<PackageReference Include="System.Collections.Immutable" Version="7.0.0" /> <PackageReference Include="System.Collections.Immutable" Version="7.0.0" />
<PackageReference Include="System.Drawing.Common" Version="7.0.0" /> <PackageReference Include="System.Drawing.Common" Version="7.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Interop" Version="17.0" />
<PackageReference Include="System.Reactive" Version="5.0.0" /> <PackageReference Include="System.Reactive" Version="5.0.0" />
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="7.0.0" /> <PackageReference Include="System.Reflection.MetadataLoadContext" Version="7.0.0" />
<PackageReference Include="System.Resources.Extensions" Version="7.0.0" /> <PackageReference Include="System.Resources.Extensions" Version="7.0.0" />

View file

@ -1,9 +1,9 @@
using System; using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text; using System.Text;
using Microsoft.VisualStudio.OLE.Interop;
// ReSharper disable UnusedMember.Local // ReSharper disable UnusedMember.Local
// ReSharper disable IdentifierTypo // ReSharper disable IdentifierTypo
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
@ -12,6 +12,29 @@ namespace Dalamud.Interface.DragDrop;
/// <summary> Implements interop enums and function calls to interact with external drag and drop. </summary> /// <summary> Implements interop enums and function calls to interact with external drag and drop. </summary>
internal partial class DragDropManager internal partial class DragDropManager
{ {
internal struct POINTL
{
[ComAliasName("Microsoft.VisualStudio.OLE.Interop.LONG")]
public int x;
[ComAliasName("Microsoft.VisualStudio.OLE.Interop.LONG")]
public int y;
}
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("00000122-0000-0000-C000-000000000046")]
[ComImport]
public interface IDropTarget
{
[MethodImpl(MethodImplOptions.InternalCall)]
void DragEnter([MarshalAs(UnmanagedType.Interface), In] IDataObject pDataObj, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD"), In] uint grfKeyState, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.POINTL"), In] POINTL pt, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD"), In, Out] ref uint pdwEffect);
[MethodImpl(MethodImplOptions.InternalCall)]
void DragOver([ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD"), In] uint grfKeyState, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.POINTL"), In] POINTL pt, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD"), In, Out] ref uint pdwEffect);
[MethodImpl(MethodImplOptions.InternalCall)]
void DragLeave();
[MethodImpl(MethodImplOptions.InternalCall)]
void Drop([MarshalAs(UnmanagedType.Interface), In] IDataObject pDataObj, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD"), In] uint grfKeyState, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.POINTL"), In] POINTL pt, [ComAliasName("Microsoft.VisualStudio.OLE.Interop.DWORD"), In, Out] ref uint pdwEffect);
}
private static class DragDropInterop private static class DragDropInterop
{ {
[Flags] [Flags]

View file

@ -1,7 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Dalamud.Interface.Internal;
using ImGuiNET; using ImGuiNET;
using Serilog; using Serilog;
@ -11,17 +12,22 @@ namespace Dalamud.Interface.DragDrop;
/// A manager that keeps state of external windows drag and drop events, /// A manager that keeps state of external windows drag and drop events,
/// and can be used to create ImGui drag and drop sources and targets for those external events. /// and can be used to create ImGui drag and drop sources and targets for those external events.
/// </summary> /// </summary>
internal partial class DragDropManager : IDisposable, IDragDropManager [ServiceManager.EarlyLoadedService]
internal partial class DragDropManager : IDisposable, IDragDropManager, IServiceType
{ {
private readonly UiBuilder uiBuilder; private InterfaceManager? interfaceManager;
private int lastDropFrame = -2; private int lastDropFrame = -2;
private int lastTooltipFrame = -1; private int lastTooltipFrame = -1;
/// <summary> Initializes a new instance of the <see cref="DragDropManager"/> class.</summary> [ServiceManager.ServiceConstructor]
/// <param name="uiBuilder">The parent <see cref="UiBuilder"/> instance.</param> private DragDropManager()
public DragDropManager(UiBuilder uiBuilder) {
=> this.uiBuilder = uiBuilder; Service<InterfaceManager.InterfaceManagerWithScene>.GetAsync().ContinueWith(task =>
{
this.interfaceManager = task.Result.Manager;
this.Enable();
});
}
/// <summary> Gets a value indicating whether external drag and drop is available at all. </summary> /// <summary> Gets a value indicating whether external drag and drop is available at all. </summary>
public bool ServiceAvailable { get; private set; } public bool ServiceAvailable { get; private set; }
@ -44,14 +50,15 @@ internal partial class DragDropManager : IDisposable, IDragDropManager
/// <summary> Enable external drag and drop. </summary> /// <summary> Enable external drag and drop. </summary>
public void Enable() public void Enable()
{ {
if (this.ServiceAvailable) if (this.ServiceAvailable || this.interfaceManager == null)
{ {
return; return;
} }
try try
{ {
var ret2 = DragDropInterop.RegisterDragDrop(this.uiBuilder.WindowHandlePtr, this); var ret2 = DragDropInterop.RegisterDragDrop(this.interfaceManager.WindowHandlePtr, this);
Log.Information($"[DragDrop] Registered window {this.interfaceManager.WindowHandlePtr} for external drag and drop operations. ({ret2})");
Marshal.ThrowExceptionForHR(ret2); Marshal.ThrowExceptionForHR(ret2);
this.ServiceAvailable = true; this.ServiceAvailable = true;
} }
@ -71,7 +78,8 @@ internal partial class DragDropManager : IDisposable, IDragDropManager
try try
{ {
DragDropInterop.RevokeDragDrop(this.uiBuilder.WindowHandlePtr); DragDropInterop.RevokeDragDrop(this.interfaceManager!.WindowHandlePtr);
Log.Information($"[DragDrop] Disabled external drag and drop operations for window {this.interfaceManager.WindowHandlePtr}.");
} }
catch (Exception ex) catch (Exception ex)
{ {

View file

@ -2,30 +2,30 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices.ComTypes;
using System.Text; using System.Text;
using Dalamud.Utility; using Dalamud.Utility;
using ImGuiNET; using ImGuiNET;
using Microsoft.VisualStudio.OLE.Interop;
using Serilog; using Serilog;
namespace Dalamud.Interface.DragDrop; namespace Dalamud.Interface.DragDrop;
/// <summary> Implements the IDropTarget interface to interact with external drag and dropping. </summary> /// <summary> Implements the IDropTarget interface to interact with external drag and dropping. </summary>
internal partial class DragDropManager : IDropTarget internal partial class DragDropManager : DragDropManager.IDropTarget
{ {
private int lastUpdateFrame = -1;
/// <summary> Create the drag and drop formats we accept. </summary> /// <summary> Create the drag and drop formats we accept. </summary>
private static readonly FORMATETC[] FormatEtc = private static FORMATETC FormatEtc =
{
new() new()
{ {
cfFormat = (ushort)DragDropInterop.ClipboardFormat.CF_HDROP, cfFormat = (short)DragDropInterop.ClipboardFormat.CF_HDROP,
ptd = nint.Zero, ptd = nint.Zero,
dwAspect = (uint)DragDropInterop.DVAspect.DVASPECT_CONTENT, dwAspect = DVASPECT.DVASPECT_CONTENT,
lindex = -1, lindex = -1,
tymed = (uint)DragDropInterop.TYMED.TYMED_HGLOBAL, tymed = TYMED.TYMED_HGLOBAL,
}, };
};
/// <summary> /// <summary>
/// Invoked whenever a drag and drop process drags files into any FFXIV-related viewport. /// Invoked whenever a drag and drop process drags files into any FFXIV-related viewport.
@ -39,7 +39,7 @@ internal partial class DragDropManager : IDropTarget
this.IsDragging = true; this.IsDragging = true;
UpdateIo((DragDropInterop.ModifierKeys)grfKeyState, true); UpdateIo((DragDropInterop.ModifierKeys)grfKeyState, true);
if (pDataObj.QueryGetData(FormatEtc) != 0) if (pDataObj.QueryGetData(ref FormatEtc) != 0)
{ {
pdwEffect = 0; pdwEffect = 0;
} }
@ -50,17 +50,24 @@ internal partial class DragDropManager : IDropTarget
this.HasPaths = this.Files.Count + this.Directories.Count > 0; this.HasPaths = this.Files.Count + this.Directories.Count > 0;
this.Extensions = this.Files.Select(Path.GetExtension).Where(p => !p.IsNullOrEmpty()).Distinct().ToHashSet(); this.Extensions = this.Files.Select(Path.GetExtension).Where(p => !p.IsNullOrEmpty()).Distinct().ToHashSet();
} }
Log.Debug("[DragDrop] Entering external Drag and Drop with {KeyState} at {PtX}, {PtY} and with {N} files.", (DragDropInterop.ModifierKeys)grfKeyState, pt.x, pt.y, this.Files.Count + this.Directories.Count);
} }
/// <summary> Invoked every windows update-frame as long as the drag and drop process keeps hovering over an FFXIV-related viewport. </summary> /// <summary> Invoked every windows update-frame as long as the drag and drop process keeps hovering over an FFXIV-related viewport. </summary>
/// <param name="grfKeyState"> The mouse button used to drag as well as key modifiers. </param> /// <param name="grfKeyState"> The mouse button used to drag as well as key modifiers. </param>
/// <param name="pt"> The global cursor position. </param> /// <param name="pt"> The global cursor position. </param>
/// <param name="pdwEffect"> Effects that can be used with this drag and drop process. </param> /// <param name="pdwEffect"> Effects that can be used with this drag and drop process. </param>
/// <remarks> Can be invoked more often than once a XIV frame, can also be less often (?). </remarks> /// <remarks> Can be invoked more often than once a XIV frame, so we are keeping track of frames to skip unnecessary updates. </remarks>
public void DragOver(uint grfKeyState, POINTL pt, ref uint pdwEffect) public void DragOver(uint grfKeyState, POINTL pt, ref uint pdwEffect)
{ {
UpdateIo((DragDropInterop.ModifierKeys)grfKeyState, false); var frame = ImGui.GetFrameCount();
pdwEffect &= (uint)DragDropInterop.DropEffects.Copy; if (frame != this.lastUpdateFrame)
{
this.lastUpdateFrame = frame;
UpdateIo((DragDropInterop.ModifierKeys)grfKeyState, false);
pdwEffect &= (uint)DragDropInterop.DropEffects.Copy;
Log.Verbose("[DragDrop] External Drag and Drop with {KeyState} at {PtX}, {PtY}.", (DragDropInterop.ModifierKeys)grfKeyState, pt.x, pt.y);
}
} }
/// <summary> Invoked whenever a drag and drop process that hovered over any FFXIV-related viewport leaves all FFXIV-related viewports. </summary> /// <summary> Invoked whenever a drag and drop process that hovered over any FFXIV-related viewport leaves all FFXIV-related viewports. </summary>
@ -70,6 +77,7 @@ internal partial class DragDropManager : IDropTarget
this.Files = Array.Empty<string>(); this.Files = Array.Empty<string>();
this.Directories = Array.Empty<string>(); this.Directories = Array.Empty<string>();
this.Extensions = new HashSet<string>(); this.Extensions = new HashSet<string>();
Log.Debug("[DragDrop] Leaving external Drag and Drop.");
} }
/// <summary> Invoked whenever a drag process ends by dropping over any FFXIV-related viewport. </summary> /// <summary> Invoked whenever a drag process ends by dropping over any FFXIV-related viewport. </summary>
@ -90,6 +98,8 @@ internal partial class DragDropManager : IDropTarget
{ {
pdwEffect = 0; pdwEffect = 0;
} }
Log.Debug("[DragDrop] Dropping {N} files with {KeyState} at {PtX}, {PtY}.", this.Files.Count + this.Directories.Count, (DragDropInterop.ModifierKeys)grfKeyState, pt.x, pt.y);
} }
private static void UpdateIo(DragDropInterop.ModifierKeys keys, bool entering) private static void UpdateIo(DragDropInterop.ModifierKeys keys, bool entering)
@ -189,12 +199,8 @@ internal partial class DragDropManager : IDropTarget
try try
{ {
var stgMedium = new STGMEDIUM[] data.GetData(ref FormatEtc, out var stgMedium);
{ var numFiles = DragDropInterop.DragQueryFile(stgMedium.unionmember, uint.MaxValue, new StringBuilder(), 0);
default,
};
data.GetData(FormatEtc, stgMedium);
var numFiles = DragDropInterop.DragQueryFile(stgMedium[0].unionmember, uint.MaxValue, new StringBuilder(), 0);
var files = new string[numFiles]; var files = new string[numFiles];
var sb = new StringBuilder(1024); var sb = new StringBuilder(1024);
var directoryCount = 0; var directoryCount = 0;
@ -202,11 +208,11 @@ internal partial class DragDropManager : IDropTarget
for (var i = 0u; i < numFiles; ++i) for (var i = 0u; i < numFiles; ++i)
{ {
sb.Clear(); sb.Clear();
var ret = DragDropInterop.DragQueryFile(stgMedium[0].unionmember, i, sb, sb.Capacity); var ret = DragDropInterop.DragQueryFile(stgMedium.unionmember, i, sb, sb.Capacity);
if (ret >= sb.Capacity) if (ret >= sb.Capacity)
{ {
sb.Capacity = ret + 1; sb.Capacity = ret + 1;
ret = DragDropInterop.DragQueryFile(stgMedium[0].unionmember, i, sb, sb.Capacity); ret = DragDropInterop.DragQueryFile(stgMedium.unionmember, i, sb, sb.Capacity);
} }
if (ret > 0 && ret < sb.Capacity) if (ret > 0 && ret < sb.Capacity)

View file

@ -31,7 +31,7 @@ public sealed class UiBuilder : IDisposable
private readonly string namespaceName; private readonly string namespaceName;
private readonly InterfaceManager interfaceManager = Service<InterfaceManager>.Get(); private readonly InterfaceManager interfaceManager = Service<InterfaceManager>.Get();
private readonly GameFontManager gameFontManager = Service<GameFontManager>.Get(); private readonly GameFontManager gameFontManager = Service<GameFontManager>.Get();
private readonly DragDropManager dragDropManager; private readonly DragDropManager dragDropManager = Service<DragDropManager>.Get();
private bool hasErrorWindow = false; private bool hasErrorWindow = false;
private bool lastFrameUiHideState = false; private bool lastFrameUiHideState = false;
@ -49,8 +49,6 @@ public sealed class UiBuilder : IDisposable
this.stopwatch = new Stopwatch(); this.stopwatch = new Stopwatch();
this.hitchDetector = new HitchDetector($"UiBuilder({namespaceName})", this.configuration.UiBuilderHitch); this.hitchDetector = new HitchDetector($"UiBuilder({namespaceName})", this.configuration.UiBuilderHitch);
this.namespaceName = namespaceName; this.namespaceName = namespaceName;
this.dragDropManager = new DragDropManager(this);
this.dragDropManager.Enable();
this.interfaceManager.Draw += this.OnDraw; this.interfaceManager.Draw += this.OnDraw;
this.interfaceManager.BuildFonts += this.OnBuildFonts; this.interfaceManager.BuildFonts += this.OnBuildFonts;
@ -406,7 +404,6 @@ public sealed class UiBuilder : IDisposable
/// </summary> /// </summary>
void IDisposable.Dispose() void IDisposable.Dispose()
{ {
this.dragDropManager.Dispose();
this.interfaceManager.Draw -= this.OnDraw; this.interfaceManager.Draw -= this.OnDraw;
this.interfaceManager.BuildFonts -= this.OnBuildFonts; this.interfaceManager.BuildFonts -= this.OnBuildFonts;
this.interfaceManager.ResizeBuffers -= this.OnResizeBuffers; this.interfaceManager.ResizeBuffers -= this.OnResizeBuffers;