Browse Source

Fix memory leak DoDragDrop never releasing object

pull/7506/head
Max Katz 5 years ago
parent
commit
7dcb6e553d
  1. 15
      samples/ControlCatalog/Pages/ClipboardPage.xaml.cs
  2. 39
      src/Windows/Avalonia.Win32/DataObject.cs
  3. 4
      src/Windows/Avalonia.Win32/DragSource.cs
  4. 4
      src/Windows/Avalonia.Win32/OleDataObject.cs
  5. 65
      src/Windows/Avalonia.Win32/OleDropTarget.cs

15
samples/ControlCatalog/Pages/ClipboardPage.xaml.cs

@ -36,26 +36,31 @@ namespace ControlCatalog.Pages
private async void CopyTextDataObject(object sender, RoutedEventArgs args)
{
var dataObject = new DataObject();
dataObject.Set(DataFormats.Text, ClipboardContent.Text);
dataObject.Set(DataFormats.Text, ClipboardContent.Text ?? string.Empty);
await Application.Current.Clipboard.SetDataObjectAsync(dataObject);
}
private async void PasteTextDataObject(object sender, RoutedEventArgs args)
{
ClipboardContent.Text = (string)await Application.Current.Clipboard.GetDataAsync(DataFormats.Text);
ClipboardContent.Text = await Application.Current.Clipboard.GetDataAsync(DataFormats.Text) as string ?? string.Empty;
}
private async void CopyFilesDataObject(object sender, RoutedEventArgs args)
{
var files = ClipboardContent.Text.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
if (files.Length == 0)
{
return;
}
var dataObject = new DataObject();
dataObject.Set(DataFormats.FileNames, ClipboardContent.Text.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries));
dataObject.Set(DataFormats.FileNames, files);
await Application.Current.Clipboard.SetDataObjectAsync(dataObject);
}
private async void PasteFilesDataObject(object sender, RoutedEventArgs args)
{
var fiels = (IEnumerable<string>)await Application.Current.Clipboard.GetDataAsync(DataFormats.FileNames);
ClipboardContent.Text = string.Join(Environment.NewLine, fiels);
var fiels = await Application.Current.Clipboard.GetDataAsync(DataFormats.FileNames) as IEnumerable<string>;
ClipboardContent.Text = fiels != null ? string.Join(Environment.NewLine, fiels) : string.Empty;
}
private async void GetFormats(object sender, RoutedEventArgs args)

39
src/Windows/Avalonia.Win32/DataObject.cs

@ -16,31 +16,7 @@ using IDataObject = Avalonia.Input.IDataObject;
namespace Avalonia.Win32
{
public interface IDisposableDataObject : IDataObject, IDisposable { }
internal static class DataObjectEx
{
public static unsafe IDisposableDataObject GetAvaloniaObjectFromCOM(this Win32Com.IDataObject pDataObj)
{
if (pDataObj is null)
{
throw new ArgumentNullException(nameof(pDataObj));
}
if (pDataObj is IDisposableDataObject disposableDataObject)
{
return disposableDataObject;
}
var dataObject = MicroComRuntime.TryUnwrapManagedObject(pDataObj) as DataObject;
if (dataObject is not null)
{
return dataObject;
}
return new OleDataObject(pDataObj);
}
}
internal class DataObject : CallbackBase, IDisposableDataObject, Win32Com.IDataObject
internal sealed class DataObject : CallbackBase, IDataObject, Win32Com.IDataObject
{
// Compatibility with WinForms + WPF...
internal static readonly byte[] SerializedObjectGUID = new Guid("FD9EA796-3B13-4370-A679-56106BB288FB").ToByteArray();
@ -124,10 +100,14 @@ namespace Avalonia.Win32
private const int GMEM_MOVEABLE = 0x0002;
IDataObject _wrapped;
private IDataObject _wrapped;
public DataObject(IDataObject wrapped)
{
if (wrapped == null)
{
throw new ArgumentNullException(nameof(wrapped));
}
if (_wrapped is DataObject || _wrapped is OleDataObject)
{
throw new InvalidOperationException();
@ -403,7 +383,12 @@ namespace Avalonia.Win32
protected override void Destroyed()
{
base.Destroyed();
ReleaseWrapped();
}
public void ReleaseWrapped()
{
_wrapped = null;
}
#endregion
}

4
src/Windows/Avalonia.Win32/DragSource.cs

@ -23,6 +23,10 @@ namespace Avalonia.Win32
var srcPtr = MicroCom.MicroComRuntime.GetNativeIntPtr<Win32Com.IDropSource>(src);
UnmanagedMethods.DoDragDrop(objPtr, srcPtr, (int)allowed, out var finalEffect);
// Force releasing of internal wrapper to avoid memory leak, if drop target keeps com reference.
dataObject.ReleaseWrapped();
return Task.FromResult(OleDropTarget.ConvertDropEffect((Win32Com.DropEffect)finalEffect));
}
}

4
src/Windows/Avalonia.Win32/OleDataObject.cs

@ -11,9 +11,11 @@ using Avalonia.Input;
using Avalonia.MicroCom;
using Avalonia.Win32.Interop;
using IDataObject = Avalonia.Input.IDataObject;
namespace Avalonia.Win32
{
internal class OleDataObject : IDisposableDataObject
internal class OleDataObject : IDataObject, IDisposable
{
private readonly Win32Com.IDataObject _wrapped;

65
src/Windows/Avalonia.Win32/OleDropTarget.cs

@ -1,4 +1,6 @@
using Avalonia.Input;
using System;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.MicroCom;
using Avalonia.Platform;
@ -13,7 +15,7 @@ namespace Avalonia.Win32
private readonly ITopLevelImpl _tl;
private readonly IDragDropDevice _dragDevice;
private IDisposableDataObject _currentDrag = null;
private IDataObject _currentDrag = null;
public OleDropTarget(ITopLevelImpl tl, IInputRoot target)
{
@ -74,12 +76,7 @@ namespace Avalonia.Win32
*pdwEffect= (int)DropEffect.None;
}
var newDrag = pDataObj.GetAvaloniaObjectFromCOM();
if (_currentDrag != newDrag)
{
_currentDrag?.Dispose();
_currentDrag = newDrag;
}
SetDataObject(pDataObj);
var args = new RawDragEvent(
_dragDevice,
@ -131,8 +128,7 @@ namespace Avalonia.Win32
}
finally
{
_currentDrag?.Dispose();
_currentDrag = null;
ReleaseDataObject();
}
}
@ -146,12 +142,7 @@ namespace Avalonia.Win32
*pdwEffect = (int)DropEffect.None;
}
var newDrag = pDataObj.GetAvaloniaObjectFromCOM();
if (_currentDrag != newDrag)
{
_currentDrag?.Dispose();
_currentDrag = newDrag;
}
SetDataObject(pDataObj);
var args = new RawDragEvent(
_dragDevice,
@ -167,7 +158,26 @@ namespace Avalonia.Win32
}
finally
{
_currentDrag?.Dispose();
ReleaseDataObject();
}
}
private void SetDataObject(Win32Com.IDataObject pDataObj)
{
var newDrag = GetAvaloniaObjectFromCOM(pDataObj);
if (_currentDrag != newDrag)
{
ReleaseDataObject();
_currentDrag = newDrag;
}
}
private void ReleaseDataObject()
{
// OleDataObject keeps COM reference, so it should be disposed.
if (_currentDrag is OleDataObject oleDragSource)
{
oleDragSource?.Dispose();
_currentDrag = null;
}
}
@ -180,7 +190,26 @@ namespace Avalonia.Win32
protected override void Destroyed()
{
_currentDrag?.Dispose();
ReleaseDataObject();
}
public static unsafe IDataObject GetAvaloniaObjectFromCOM(Win32Com.IDataObject pDataObj)
{
if (pDataObj is null)
{
throw new ArgumentNullException(nameof(pDataObj));
}
if (pDataObj is IDataObject disposableDataObject)
{
return disposableDataObject;
}
var dataObject = MicroComRuntime.TryUnwrapManagedObject(pDataObj) as DataObject;
if (dataObject is not null)
{
return dataObject;
}
return new OleDataObject(pDataObj);
}
}
}

Loading…
Cancel
Save