using System; using System.Linq; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Text; using Avalonia.Controls.DragDrop; using Avalonia.Win32.Interop; using IDataObject = Avalonia.Controls.DragDrop.IDataObject; using IOleDataObject = System.Runtime.InteropServices.ComTypes.IDataObject; using System.IO; using System.Runtime.Serialization.Formatters.Binary; namespace Avalonia.Win32 { class DataObject : IDataObject, IOleDataObject { // Compatibility with WinForms + WPF... internal static readonly byte[] SerializedObjectGUID = new Guid("FD9EA796-3B13-4370-A679-56106BB288FB").ToByteArray(); class FormatEnumerator : IEnumFORMATETC { private FORMATETC[] _formats; private int _current; private FormatEnumerator(FORMATETC[] formats, int current) { _formats = formats; _current = current; } public FormatEnumerator(IDataObject dataobj) { _formats = dataobj.GetDataFormats().Select(ConvertToFormatEtc).ToArray(); _current = 0; } private FORMATETC ConvertToFormatEtc(string aFormatName) { FORMATETC result = default(FORMATETC); result.cfFormat = ClipboardFormats.GetFormat(aFormatName); result.dwAspect = DVASPECT.DVASPECT_CONTENT; result.ptd = IntPtr.Zero; result.lindex = -1; result.tymed = TYMED.TYMED_HGLOBAL; return result; } public void Clone(out IEnumFORMATETC newEnum) { newEnum = new FormatEnumerator(_formats, _current); } public int Next(int celt, FORMATETC[] rgelt, int[] pceltFetched) { if (rgelt == null) return unchecked((int)UnmanagedMethods.HRESULT.E_INVALIDARG); int i = 0; while (i < celt && _current < _formats.Length) { rgelt[i] = _formats[_current]; _current++; i++; } if (pceltFetched != null) pceltFetched[0] = i; if (i != celt) return unchecked((int)UnmanagedMethods.HRESULT.S_FALSE); return unchecked((int)UnmanagedMethods.HRESULT.S_OK); } public int Reset() { _current = 0; return unchecked((int)UnmanagedMethods.HRESULT.S_OK); } public int Skip(int celt) { _current += Math.Min(celt, int.MaxValue - _current); if (_current >= _formats.Length) return unchecked((int)UnmanagedMethods.HRESULT.S_FALSE); return unchecked((int)UnmanagedMethods.HRESULT.S_OK); } } private const int DV_E_TYMED = unchecked((int)0x80040069); private const int DV_E_DVASPECT = unchecked((int)0x8004006B); private const int DV_E_FORMATETC = unchecked((int)0x80040064); private const int OLE_E_ADVISENOTSUPPORTED = unchecked((int)0x80040003); private const int STG_E_MEDIUMFULL = unchecked((int)0x80030070); private const int GMEM_ZEROINIT = 0x0040; private const int GMEM_MOVEABLE = 0x0002; IDataObject _wrapped; public DataObject(IDataObject wrapped) { _wrapped = wrapped; } #region IDataObject bool IDataObject.Contains(string dataFormat) { return _wrapped.Contains(dataFormat); } IEnumerable IDataObject.GetDataFormats() { return _wrapped.GetDataFormats(); } IEnumerable IDataObject.GetFileNames() { return _wrapped.GetFileNames(); } string IDataObject.GetText() { return _wrapped.GetText(); } object IDataObject.Get(string dataFormat) { return _wrapped.Get(dataFormat); } #endregion #region IOleDataObject int IOleDataObject.DAdvise(ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection) { if (_wrapped is IOleDataObject ole) return ole.DAdvise(ref pFormatetc, advf, adviseSink, out connection); connection = 0; return OLE_E_ADVISENOTSUPPORTED; } void IOleDataObject.DUnadvise(int connection) { if (_wrapped is IOleDataObject ole) ole.DUnadvise(connection); Marshal.ThrowExceptionForHR(OLE_E_ADVISENOTSUPPORTED); } int IOleDataObject.EnumDAdvise(out IEnumSTATDATA enumAdvise) { if (_wrapped is IOleDataObject ole) return ole.EnumDAdvise(out enumAdvise); enumAdvise = null; return OLE_E_ADVISENOTSUPPORTED; } IEnumFORMATETC IOleDataObject.EnumFormatEtc(DATADIR direction) { if (_wrapped is IOleDataObject ole) return ole.EnumFormatEtc(direction); if (direction == DATADIR.DATADIR_GET) return new FormatEnumerator(_wrapped); throw new NotSupportedException(); } int IOleDataObject.GetCanonicalFormatEtc(ref FORMATETC formatIn, out FORMATETC formatOut) { if (_wrapped is IOleDataObject ole) return ole.GetCanonicalFormatEtc(ref formatIn, out formatOut); formatOut = new FORMATETC(); formatOut.ptd = IntPtr.Zero; return unchecked((int)UnmanagedMethods.HRESULT.E_NOTIMPL); } void IOleDataObject.GetData(ref FORMATETC format, out STGMEDIUM medium) { if (_wrapped is IOleDataObject ole) { ole.GetData(ref format, out medium); return; } if(!format.tymed.HasFlag(TYMED.TYMED_HGLOBAL)) Marshal.ThrowExceptionForHR(DV_E_TYMED); if (format.dwAspect != DVASPECT.DVASPECT_CONTENT) Marshal.ThrowExceptionForHR(DV_E_DVASPECT); string fmt = ClipboardFormats.GetFormat(format.cfFormat); if (string.IsNullOrEmpty(fmt) || !_wrapped.Contains(fmt)) Marshal.ThrowExceptionForHR(DV_E_FORMATETC); medium = default(STGMEDIUM); medium.tymed = TYMED.TYMED_HGLOBAL; int result = WriteDataToHGlobal(fmt, ref medium.unionmember); Marshal.ThrowExceptionForHR(result); } void IOleDataObject.GetDataHere(ref FORMATETC format, ref STGMEDIUM medium) { if (_wrapped is IOleDataObject ole) { ole.GetDataHere(ref format, ref medium); return; } if (medium.tymed != TYMED.TYMED_HGLOBAL || !format.tymed.HasFlag(TYMED.TYMED_HGLOBAL)) Marshal.ThrowExceptionForHR(DV_E_TYMED); if (format.dwAspect != DVASPECT.DVASPECT_CONTENT) Marshal.ThrowExceptionForHR(DV_E_DVASPECT); string fmt = ClipboardFormats.GetFormat(format.cfFormat); if (string.IsNullOrEmpty(fmt) || !_wrapped.Contains(fmt)) Marshal.ThrowExceptionForHR(DV_E_FORMATETC); if (medium.unionmember == IntPtr.Zero) Marshal.ThrowExceptionForHR(STG_E_MEDIUMFULL); int result = WriteDataToHGlobal(fmt, ref medium.unionmember); Marshal.ThrowExceptionForHR(result); } int IOleDataObject.QueryGetData(ref FORMATETC format) { if (_wrapped is IOleDataObject ole) return ole.QueryGetData(ref format); if (format.dwAspect != DVASPECT.DVASPECT_CONTENT) return DV_E_DVASPECT; if (!format.tymed.HasFlag(TYMED.TYMED_HGLOBAL)) return DV_E_TYMED; string dataFormat = ClipboardFormats.GetFormat(format.cfFormat); if (!string.IsNullOrEmpty(dataFormat) && _wrapped.Contains(dataFormat)) return unchecked((int)UnmanagedMethods.HRESULT.S_OK); return DV_E_FORMATETC; } void IOleDataObject.SetData(ref FORMATETC formatIn, ref STGMEDIUM medium, bool release) { if (_wrapped is IOleDataObject ole) { ole.SetData(ref formatIn, ref medium, release); return; } Marshal.ThrowExceptionForHR(unchecked((int)UnmanagedMethods.HRESULT.E_NOTIMPL)); } private int WriteDataToHGlobal(string dataFormat, ref IntPtr hGlobal) { object data = _wrapped.Get(dataFormat); if (dataFormat == DataFormats.Text || data is string) return WriteStringToHGlobal(ref hGlobal, Convert.ToString(data)); if (dataFormat == DataFormats.FileNames && data is IEnumerable files) return WriteFileListToHGlobal(ref hGlobal, files); if (data is Stream stream) { byte[] buffer = new byte[stream.Length - stream.Position]; stream.Read(buffer, 0, buffer.Length); return WriteBytesToHGlobal(ref hGlobal, buffer); } if (data is IEnumerable bytes) { var byteArr = bytes is byte[] ? (byte[])bytes : bytes.ToArray(); return WriteBytesToHGlobal(ref hGlobal, byteArr); } return WriteBytesToHGlobal(ref hGlobal, SerializeObject(data)); } private byte[] SerializeObject(object data) { using (var ms = new MemoryStream()) { ms.Write(SerializedObjectGUID, 0, SerializedObjectGUID.Length); BinaryFormatter binaryFormatter = new BinaryFormatter(); binaryFormatter.Serialize(ms, data); return ms.ToArray(); } } private int WriteBytesToHGlobal(ref IntPtr hGlobal, byte[] data) { int required = data.Length; if (hGlobal == IntPtr.Zero) hGlobal = UnmanagedMethods.GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, required); long available = UnmanagedMethods.GlobalSize(hGlobal).ToInt64(); if (required > available) return STG_E_MEDIUMFULL; IntPtr ptr = UnmanagedMethods.GlobalLock(hGlobal); try { Marshal.Copy(data, 0, ptr, data.Length); return unchecked((int)UnmanagedMethods.HRESULT.S_OK); } finally { UnmanagedMethods.GlobalUnlock(hGlobal); } } private int WriteFileListToHGlobal(ref IntPtr hGlobal, IEnumerable files) { if (!files?.Any() ?? false) return unchecked((int)UnmanagedMethods.HRESULT.S_OK); char[] filesStr = (string.Join("\0", files) + "\0\0").ToCharArray(); _DROPFILES df = new _DROPFILES(); df.pFiles = Marshal.SizeOf<_DROPFILES>(); df.fWide = true; int required = (filesStr.Length * sizeof(char)) + Marshal.SizeOf<_DROPFILES>(); if (hGlobal == IntPtr.Zero) hGlobal = UnmanagedMethods.GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, required); long available = UnmanagedMethods.GlobalSize(hGlobal).ToInt64(); if (required > available) return STG_E_MEDIUMFULL; IntPtr ptr = UnmanagedMethods.GlobalLock(hGlobal); try { Marshal.StructureToPtr(df, ptr, false); Marshal.Copy(filesStr, 0, ptr + Marshal.SizeOf<_DROPFILES>(), filesStr.Length); return unchecked((int)UnmanagedMethods.HRESULT.S_OK); } finally { UnmanagedMethods.GlobalUnlock(hGlobal); } } private int WriteStringToHGlobal(ref IntPtr hGlobal, string data) { int required = (data.Length + 1) * sizeof(char); if (hGlobal == IntPtr.Zero) hGlobal = UnmanagedMethods.GlobalAlloc(GMEM_MOVEABLE|GMEM_ZEROINIT, required); long available = UnmanagedMethods.GlobalSize(hGlobal).ToInt64(); if (required > available) return STG_E_MEDIUMFULL; IntPtr ptr = UnmanagedMethods.GlobalLock(hGlobal); try { char[] chars = (data + '\0').ToCharArray(); Marshal.Copy(chars, 0, ptr, chars.Length); return unchecked((int)UnmanagedMethods.HRESULT.S_OK); } finally { UnmanagedMethods.GlobalUnlock(hGlobal); } } #endregion } }