diff --git a/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs b/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs index 16833480c6..eed46265ff 100644 --- a/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs +++ b/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)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; + ClipboardContent.Text = fiels != null ? string.Join(Environment.NewLine, fiels) : string.Empty; } private async void GetFormats(object sender, RoutedEventArgs args) diff --git a/src/Windows/Avalonia.Win32/DataObject.cs b/src/Windows/Avalonia.Win32/DataObject.cs index af11381ae2..7a96ca9ee0 100644 --- a/src/Windows/Avalonia.Win32/DataObject.cs +++ b/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 } diff --git a/src/Windows/Avalonia.Win32/DragSource.cs b/src/Windows/Avalonia.Win32/DragSource.cs index 7eb23cfa13..1159c5bfc9 100644 --- a/src/Windows/Avalonia.Win32/DragSource.cs +++ b/src/Windows/Avalonia.Win32/DragSource.cs @@ -23,6 +23,10 @@ namespace Avalonia.Win32 var srcPtr = MicroCom.MicroComRuntime.GetNativeIntPtr(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)); } } diff --git a/src/Windows/Avalonia.Win32/OleDataObject.cs b/src/Windows/Avalonia.Win32/OleDataObject.cs index 81058b8229..ba17177473 100644 --- a/src/Windows/Avalonia.Win32/OleDataObject.cs +++ b/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; diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs index dbcac124ee..3d0d35228c 100644 --- a/src/Windows/Avalonia.Win32/OleDropTarget.cs +++ b/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); } } }