DataObject.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. using System;
  2. using System.Linq;
  3. using System.Collections.Generic;
  4. using System.Runtime.InteropServices;
  5. using System.Runtime.InteropServices.ComTypes;
  6. using System.Text;
  7. using Avalonia.Controls.DragDrop;
  8. using Avalonia.Win32.Interop;
  9. using IDataObject = Avalonia.Controls.DragDrop.IDataObject;
  10. using IOleDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
  11. using System.IO;
  12. using System.Runtime.Serialization.Formatters.Binary;
  13. namespace Avalonia.Win32
  14. {
  15. class DataObject : IDataObject, IOleDataObject
  16. {
  17. // Compatibility with WinForms + WPF...
  18. internal static readonly byte[] SerializedObjectGUID = new Guid("FD9EA796-3B13-4370-A679-56106BB288FB").ToByteArray();
  19. class FormatEnumerator : IEnumFORMATETC
  20. {
  21. private FORMATETC[] _formats;
  22. private int _current;
  23. private FormatEnumerator(FORMATETC[] formats, int current)
  24. {
  25. _formats = formats;
  26. _current = current;
  27. }
  28. public FormatEnumerator(IDataObject dataobj)
  29. {
  30. _formats = dataobj.GetDataFormats().Select(ConvertToFormatEtc).ToArray();
  31. _current = 0;
  32. }
  33. private FORMATETC ConvertToFormatEtc(string aFormatName)
  34. {
  35. FORMATETC result = default(FORMATETC);
  36. result.cfFormat = ClipboardFormats.GetFormat(aFormatName);
  37. result.dwAspect = DVASPECT.DVASPECT_CONTENT;
  38. result.ptd = IntPtr.Zero;
  39. result.lindex = -1;
  40. result.tymed = TYMED.TYMED_HGLOBAL;
  41. return result;
  42. }
  43. public void Clone(out IEnumFORMATETC newEnum)
  44. {
  45. newEnum = new FormatEnumerator(_formats, _current);
  46. }
  47. public int Next(int celt, FORMATETC[] rgelt, int[] pceltFetched)
  48. {
  49. if (rgelt == null)
  50. return unchecked((int)UnmanagedMethods.HRESULT.E_INVALIDARG);
  51. int i = 0;
  52. while (i < celt && _current < _formats.Length)
  53. {
  54. rgelt[i] = _formats[_current];
  55. _current++;
  56. i++;
  57. }
  58. if (pceltFetched != null)
  59. pceltFetched[0] = i;
  60. if (i != celt)
  61. return unchecked((int)UnmanagedMethods.HRESULT.S_FALSE);
  62. return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
  63. }
  64. public int Reset()
  65. {
  66. _current = 0;
  67. return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
  68. }
  69. public int Skip(int celt)
  70. {
  71. _current += Math.Min(celt, int.MaxValue - _current);
  72. if (_current >= _formats.Length)
  73. return unchecked((int)UnmanagedMethods.HRESULT.S_FALSE);
  74. return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
  75. }
  76. }
  77. private const int DV_E_TYMED = unchecked((int)0x80040069);
  78. private const int DV_E_DVASPECT = unchecked((int)0x8004006B);
  79. private const int DV_E_FORMATETC = unchecked((int)0x80040064);
  80. private const int OLE_E_ADVISENOTSUPPORTED = unchecked((int)0x80040003);
  81. private const int STG_E_MEDIUMFULL = unchecked((int)0x80030070);
  82. private const int GMEM_ZEROINIT = 0x0040;
  83. private const int GMEM_MOVEABLE = 0x0002;
  84. IDataObject _wrapped;
  85. public DataObject(IDataObject wrapped)
  86. {
  87. _wrapped = wrapped;
  88. }
  89. #region IDataObject
  90. bool IDataObject.Contains(string dataFormat)
  91. {
  92. return _wrapped.Contains(dataFormat);
  93. }
  94. IEnumerable<string> IDataObject.GetDataFormats()
  95. {
  96. return _wrapped.GetDataFormats();
  97. }
  98. IEnumerable<string> IDataObject.GetFileNames()
  99. {
  100. return _wrapped.GetFileNames();
  101. }
  102. string IDataObject.GetText()
  103. {
  104. return _wrapped.GetText();
  105. }
  106. object IDataObject.Get(string dataFormat)
  107. {
  108. return _wrapped.Get(dataFormat);
  109. }
  110. #endregion
  111. #region IOleDataObject
  112. int IOleDataObject.DAdvise(ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection)
  113. {
  114. if (_wrapped is IOleDataObject ole)
  115. return ole.DAdvise(ref pFormatetc, advf, adviseSink, out connection);
  116. connection = 0;
  117. return OLE_E_ADVISENOTSUPPORTED;
  118. }
  119. void IOleDataObject.DUnadvise(int connection)
  120. {
  121. if (_wrapped is IOleDataObject ole)
  122. ole.DUnadvise(connection);
  123. Marshal.ThrowExceptionForHR(OLE_E_ADVISENOTSUPPORTED);
  124. }
  125. int IOleDataObject.EnumDAdvise(out IEnumSTATDATA enumAdvise)
  126. {
  127. if (_wrapped is IOleDataObject ole)
  128. return ole.EnumDAdvise(out enumAdvise);
  129. enumAdvise = null;
  130. return OLE_E_ADVISENOTSUPPORTED;
  131. }
  132. IEnumFORMATETC IOleDataObject.EnumFormatEtc(DATADIR direction)
  133. {
  134. if (_wrapped is IOleDataObject ole)
  135. return ole.EnumFormatEtc(direction);
  136. if (direction == DATADIR.DATADIR_GET)
  137. return new FormatEnumerator(_wrapped);
  138. throw new NotSupportedException();
  139. }
  140. int IOleDataObject.GetCanonicalFormatEtc(ref FORMATETC formatIn, out FORMATETC formatOut)
  141. {
  142. if (_wrapped is IOleDataObject ole)
  143. return ole.GetCanonicalFormatEtc(ref formatIn, out formatOut);
  144. formatOut = new FORMATETC();
  145. formatOut.ptd = IntPtr.Zero;
  146. return unchecked((int)UnmanagedMethods.HRESULT.E_NOTIMPL);
  147. }
  148. void IOleDataObject.GetData(ref FORMATETC format, out STGMEDIUM medium)
  149. {
  150. if (_wrapped is IOleDataObject ole)
  151. {
  152. ole.GetData(ref format, out medium);
  153. return;
  154. }
  155. if(!format.tymed.HasFlag(TYMED.TYMED_HGLOBAL))
  156. Marshal.ThrowExceptionForHR(DV_E_TYMED);
  157. if (format.dwAspect != DVASPECT.DVASPECT_CONTENT)
  158. Marshal.ThrowExceptionForHR(DV_E_DVASPECT);
  159. string fmt = ClipboardFormats.GetFormat(format.cfFormat);
  160. if (string.IsNullOrEmpty(fmt) || !_wrapped.Contains(fmt))
  161. Marshal.ThrowExceptionForHR(DV_E_FORMATETC);
  162. medium = default(STGMEDIUM);
  163. medium.tymed = TYMED.TYMED_HGLOBAL;
  164. int result = WriteDataToHGlobal(fmt, ref medium.unionmember);
  165. Marshal.ThrowExceptionForHR(result);
  166. }
  167. void IOleDataObject.GetDataHere(ref FORMATETC format, ref STGMEDIUM medium)
  168. {
  169. if (_wrapped is IOleDataObject ole)
  170. {
  171. ole.GetDataHere(ref format, ref medium);
  172. return;
  173. }
  174. if (medium.tymed != TYMED.TYMED_HGLOBAL || !format.tymed.HasFlag(TYMED.TYMED_HGLOBAL))
  175. Marshal.ThrowExceptionForHR(DV_E_TYMED);
  176. if (format.dwAspect != DVASPECT.DVASPECT_CONTENT)
  177. Marshal.ThrowExceptionForHR(DV_E_DVASPECT);
  178. string fmt = ClipboardFormats.GetFormat(format.cfFormat);
  179. if (string.IsNullOrEmpty(fmt) || !_wrapped.Contains(fmt))
  180. Marshal.ThrowExceptionForHR(DV_E_FORMATETC);
  181. if (medium.unionmember == IntPtr.Zero)
  182. Marshal.ThrowExceptionForHR(STG_E_MEDIUMFULL);
  183. int result = WriteDataToHGlobal(fmt, ref medium.unionmember);
  184. Marshal.ThrowExceptionForHR(result);
  185. }
  186. int IOleDataObject.QueryGetData(ref FORMATETC format)
  187. {
  188. if (_wrapped is IOleDataObject ole)
  189. return ole.QueryGetData(ref format);
  190. if (format.dwAspect != DVASPECT.DVASPECT_CONTENT)
  191. return DV_E_DVASPECT;
  192. if (!format.tymed.HasFlag(TYMED.TYMED_HGLOBAL))
  193. return DV_E_TYMED;
  194. string dataFormat = ClipboardFormats.GetFormat(format.cfFormat);
  195. if (!string.IsNullOrEmpty(dataFormat) && _wrapped.Contains(dataFormat))
  196. return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
  197. return DV_E_FORMATETC;
  198. }
  199. void IOleDataObject.SetData(ref FORMATETC formatIn, ref STGMEDIUM medium, bool release)
  200. {
  201. if (_wrapped is IOleDataObject ole)
  202. {
  203. ole.SetData(ref formatIn, ref medium, release);
  204. return;
  205. }
  206. Marshal.ThrowExceptionForHR(unchecked((int)UnmanagedMethods.HRESULT.E_NOTIMPL));
  207. }
  208. private int WriteDataToHGlobal(string dataFormat, ref IntPtr hGlobal)
  209. {
  210. object data = _wrapped.Get(dataFormat);
  211. if (dataFormat == DataFormats.Text || data is string)
  212. return WriteStringToHGlobal(ref hGlobal, Convert.ToString(data));
  213. if (dataFormat == DataFormats.FileNames && data is IEnumerable<string> files)
  214. return WriteFileListToHGlobal(ref hGlobal, files);
  215. if (data is Stream stream)
  216. {
  217. byte[] buffer = new byte[stream.Length - stream.Position];
  218. stream.Read(buffer, 0, buffer.Length);
  219. return WriteBytesToHGlobal(ref hGlobal, buffer);
  220. }
  221. if (data is IEnumerable<byte> bytes)
  222. {
  223. var byteArr = bytes is byte[] ? (byte[])bytes : bytes.ToArray();
  224. return WriteBytesToHGlobal(ref hGlobal, byteArr);
  225. }
  226. return WriteBytesToHGlobal(ref hGlobal, SerializeObject(data));
  227. }
  228. private byte[] SerializeObject(object data)
  229. {
  230. using (var ms = new MemoryStream())
  231. {
  232. ms.Write(SerializedObjectGUID, 0, SerializedObjectGUID.Length);
  233. BinaryFormatter binaryFormatter = new BinaryFormatter();
  234. binaryFormatter.Serialize(ms, data);
  235. return ms.ToArray();
  236. }
  237. }
  238. private int WriteBytesToHGlobal(ref IntPtr hGlobal, byte[] data)
  239. {
  240. int required = data.Length;
  241. if (hGlobal == IntPtr.Zero)
  242. hGlobal = UnmanagedMethods.GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, required);
  243. long available = UnmanagedMethods.GlobalSize(hGlobal).ToInt64();
  244. if (required > available)
  245. return STG_E_MEDIUMFULL;
  246. IntPtr ptr = UnmanagedMethods.GlobalLock(hGlobal);
  247. try
  248. {
  249. Marshal.Copy(data, 0, ptr, data.Length);
  250. return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
  251. }
  252. finally
  253. {
  254. UnmanagedMethods.GlobalUnlock(hGlobal);
  255. }
  256. }
  257. private int WriteFileListToHGlobal(ref IntPtr hGlobal, IEnumerable<string> files)
  258. {
  259. if (!files?.Any() ?? false)
  260. return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
  261. char[] filesStr = (string.Join("\0", files) + "\0\0").ToCharArray();
  262. _DROPFILES df = new _DROPFILES();
  263. df.pFiles = Marshal.SizeOf<_DROPFILES>();
  264. df.fWide = true;
  265. int required = (filesStr.Length * sizeof(char)) + Marshal.SizeOf<_DROPFILES>();
  266. if (hGlobal == IntPtr.Zero)
  267. hGlobal = UnmanagedMethods.GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, required);
  268. long available = UnmanagedMethods.GlobalSize(hGlobal).ToInt64();
  269. if (required > available)
  270. return STG_E_MEDIUMFULL;
  271. IntPtr ptr = UnmanagedMethods.GlobalLock(hGlobal);
  272. try
  273. {
  274. Marshal.StructureToPtr(df, ptr, false);
  275. Marshal.Copy(filesStr, 0, ptr + Marshal.SizeOf<_DROPFILES>(), filesStr.Length);
  276. return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
  277. }
  278. finally
  279. {
  280. UnmanagedMethods.GlobalUnlock(hGlobal);
  281. }
  282. }
  283. private int WriteStringToHGlobal(ref IntPtr hGlobal, string data)
  284. {
  285. int required = (data.Length + 1) * sizeof(char);
  286. if (hGlobal == IntPtr.Zero)
  287. hGlobal = UnmanagedMethods.GlobalAlloc(GMEM_MOVEABLE|GMEM_ZEROINIT, required);
  288. long available = UnmanagedMethods.GlobalSize(hGlobal).ToInt64();
  289. if (required > available)
  290. return STG_E_MEDIUMFULL;
  291. IntPtr ptr = UnmanagedMethods.GlobalLock(hGlobal);
  292. try
  293. {
  294. char[] chars = (data + '\0').ToCharArray();
  295. Marshal.Copy(chars, 0, ptr, chars.Length);
  296. return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
  297. }
  298. finally
  299. {
  300. UnmanagedMethods.GlobalUnlock(hGlobal);
  301. }
  302. }
  303. #endregion
  304. }
  305. }