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.Game;
using Dalamud.Game.Gui.Internal;
using Dalamud.Interface.DragDrop;
using Dalamud.Interface.Internal;
using Dalamud.Plugin.Internal;
using Dalamud.Utility;
@ -135,6 +136,9 @@ internal sealed class Dalamud : IServiceType
// will not receive any windows messages
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
// 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

View file

@ -82,7 +82,6 @@
</PackageReference>
<PackageReference Include="System.Collections.Immutable" 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.Reflection.MetadataLoadContext" Version="7.0.0" />
<PackageReference Include="System.Resources.Extensions" Version="7.0.0" />

View file

@ -1,9 +1,9 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using Microsoft.VisualStudio.OLE.Interop;
// ReSharper disable UnusedMember.Local
// ReSharper disable IdentifierTypo
// 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>
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
{
[Flags]

View file

@ -1,7 +1,8 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Dalamud.Interface.Internal;
using ImGuiNET;
using Serilog;
@ -11,17 +12,22 @@ namespace Dalamud.Interface.DragDrop;
/// 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.
/// </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 lastTooltipFrame = -1;
/// <summary> Initializes a new instance of the <see cref="DragDropManager"/> class.</summary>
/// <param name="uiBuilder">The parent <see cref="UiBuilder"/> instance.</param>
public DragDropManager(UiBuilder uiBuilder)
=> this.uiBuilder = uiBuilder;
[ServiceManager.ServiceConstructor]
private DragDropManager()
{
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>
public bool ServiceAvailable { get; private set; }
@ -44,14 +50,15 @@ internal partial class DragDropManager : IDisposable, IDragDropManager
/// <summary> Enable external drag and drop. </summary>
public void Enable()
{
if (this.ServiceAvailable)
if (this.ServiceAvailable || this.interfaceManager == null)
{
return;
}
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);
this.ServiceAvailable = true;
}
@ -71,7 +78,8 @@ internal partial class DragDropManager : IDisposable, IDragDropManager
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)
{

View file

@ -2,29 +2,29 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using Dalamud.Utility;
using ImGuiNET;
using Microsoft.VisualStudio.OLE.Interop;
using Serilog;
namespace Dalamud.Interface.DragDrop;
/// <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>
private static readonly FORMATETC[] FormatEtc =
{
private static FORMATETC FormatEtc =
new()
{
cfFormat = (ushort)DragDropInterop.ClipboardFormat.CF_HDROP,
cfFormat = (short)DragDropInterop.ClipboardFormat.CF_HDROP,
ptd = nint.Zero,
dwAspect = (uint)DragDropInterop.DVAspect.DVASPECT_CONTENT,
dwAspect = DVASPECT.DVASPECT_CONTENT,
lindex = -1,
tymed = (uint)DragDropInterop.TYMED.TYMED_HGLOBAL,
},
tymed = TYMED.TYMED_HGLOBAL,
};
/// <summary>
@ -39,7 +39,7 @@ internal partial class DragDropManager : IDropTarget
this.IsDragging = true;
UpdateIo((DragDropInterop.ModifierKeys)grfKeyState, true);
if (pDataObj.QueryGetData(FormatEtc) != 0)
if (pDataObj.QueryGetData(ref FormatEtc) != 0)
{
pdwEffect = 0;
}
@ -50,17 +50,24 @@ internal partial class DragDropManager : IDropTarget
this.HasPaths = this.Files.Count + this.Directories.Count > 0;
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>
/// <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="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)
{
var frame = ImGui.GetFrameCount();
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>
@ -70,6 +77,7 @@ internal partial class DragDropManager : IDropTarget
this.Files = Array.Empty<string>();
this.Directories = Array.Empty<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>
@ -90,6 +98,8 @@ internal partial class DragDropManager : IDropTarget
{
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)
@ -189,12 +199,8 @@ internal partial class DragDropManager : IDropTarget
try
{
var stgMedium = new STGMEDIUM[]
{
default,
};
data.GetData(FormatEtc, stgMedium);
var numFiles = DragDropInterop.DragQueryFile(stgMedium[0].unionmember, uint.MaxValue, new StringBuilder(), 0);
data.GetData(ref FormatEtc, out var stgMedium);
var numFiles = DragDropInterop.DragQueryFile(stgMedium.unionmember, uint.MaxValue, new StringBuilder(), 0);
var files = new string[numFiles];
var sb = new StringBuilder(1024);
var directoryCount = 0;
@ -202,11 +208,11 @@ internal partial class DragDropManager : IDropTarget
for (var i = 0u; i < numFiles; ++i)
{
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)
{
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)

View file

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