diff --git a/UnitTests/Windows.Shell/ClipboardTests.cs b/UnitTests/Windows.Shell/ClipboardTests.cs
new file mode 100644
index 00000000..d5fde5e5
--- /dev/null
+++ b/UnitTests/Windows.Shell/ClipboardTests.cs
@@ -0,0 +1,81 @@
+using NUnit.Framework;
+using System;
+using System.Linq;
+using System.Windows.Forms;
+using Vanara.PInvoke;
+using static Vanara.PInvoke.Shell32;
+using Clipboard = Vanara.Windows.Shell.NativeClipboard;
+using WFClipboard = System.Windows.Forms.Clipboard;
+
+namespace Vanara.Windows.Shell.Tests
+{
+ [TestFixture, SingleThreaded]
+ public class ClipboardTests
+ {
+ private const string html = "
“We’ve been here”
";
+
+ [Test]
+ public void DumpWFClipboardTest()
+ {
+ TestContext.WriteLine($"ContainsAudio: {WFClipboard.ContainsAudio()}");
+ TestContext.WriteLine($"ContainsData: {WFClipboard.ContainsData(DataFormats.StringFormat)}");
+ TestContext.WriteLine($"ContainsFileDropList: {WFClipboard.ContainsFileDropList()}");
+ TestContext.WriteLine($"ContainsImage: {WFClipboard.ContainsImage()}");
+ TestContext.WriteLine($"ContainsText: {WFClipboard.ContainsText()}");
+ TestContext.WriteLine($"GetAudioStream: {WFClipboard.GetAudioStream()}");
+ TestContext.WriteLine($"GetData: {WFClipboard.GetData(DataFormats.StringFormat)}");
+ TestContext.WriteLine($"GetDataObject: {WFClipboard.GetDataObject()}");
+ TestContext.WriteLine($"GetFileDropList: {string.Join("\n", WFClipboard.GetFileDropList().Cast())}");
+ TestContext.WriteLine($"GetImage: {WFClipboard.GetImage()}");
+ TestContext.WriteLine($"GetText: {WFClipboard.GetText()}");
+ }
+
+ [Test]
+ public void EnumFormatsTest()
+ {
+ using var cb = new Clipboard();
+ var fmts = cb.EnumAvailableFormats();
+ Assert.That(fmts, Is.Not.Empty);
+ TestContext.Write(string.Join(", ", fmts.Select(f => f.Name)));
+
+ var fmt = fmts.First();
+ Assert.IsTrue(Clipboard.IsFormatAvailable(fmt.Id));
+ Assert.IsTrue(Clipboard.IsFormatAvailable(fmt.Name));
+ }
+
+ [Test]
+ public void GetNativeTextTest()
+ {
+ using var cb = new Clipboard();
+ foreach (TextDataFormat e in Enum.GetValues(typeof(TextDataFormat)))
+ TestContext.WriteLine($"{e}: {cb.GetText(e)}");
+ }
+
+ [Test]
+ public void GetPriorityFormatTest()
+ {
+ var fmts = Clipboard.CurrentlySupportedFormats.Select(f => f.Id).ToArray();
+ Assert.That(Clipboard.GetFirstFormatAvailable(fmts), Is.GreaterThan(0));
+ }
+
+ [Test]
+ public void SetNativeTextHtmlTest()
+ {
+ using (var cb = new Clipboard())
+ cb.SetText(html, TextDataFormat.Html);
+ using (var cb = new Clipboard())
+ {
+ var outVal = cb.GetText(TextDataFormat.Html);
+ Assert.That(outVal, Is.EqualTo(html));
+ }
+ }
+
+ [Test]
+ public void SetNativeTextMultTest()
+ {
+ const string txt = @"“We’ve been here”";
+ using var cb = new Clipboard();
+ cb.SetText(txt, html);
+ }
+ }
+}
\ No newline at end of file
diff --git a/UnitTests/Windows.Shell/Windows.Shell.csproj b/UnitTests/Windows.Shell/Windows.Shell.csproj
index c57d9504..b46c7dd0 100644
--- a/UnitTests/Windows.Shell/Windows.Shell.csproj
+++ b/UnitTests/Windows.Shell/Windows.Shell.csproj
@@ -20,7 +20,7 @@
DEBUG;TRACE
prompt
4
- AnyCPU
+ x86
pdbonly
@@ -49,6 +49,7 @@
+
@@ -88,6 +89,10 @@
{30fd6779-6549-449e-880a-695815eb89b0}
Vanara.PInvoke.ShlwApi
+
+ {a6771907-addc-49fc-8444-a97aa65e77e2}
+ Vanara.PInvoke.User32
+
{43685be2-a65e-4b01-be16-479526940f23}
Vanara.Windows.Shell
diff --git a/Windows.Shell/NativeClipboard.cs b/Windows.Shell/NativeClipboard.cs
index 0be6b660..c90890b0 100644
--- a/Windows.Shell/NativeClipboard.cs
+++ b/Windows.Shell/NativeClipboard.cs
@@ -1,121 +1,597 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
-using System.Drawing;
-using System.IO;
-using System.Reflection;
-using System.Security.Permissions;
+using System.Globalization;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.ComTypes;
using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading;
using System.Windows.Forms;
using Vanara.Extensions;
+using Vanara.InteropServices;
+using Vanara.PInvoke;
using static Vanara.PInvoke.Ole32;
-using static Vanara.PInvoke.User32;
using static Vanara.PInvoke.Shell32;
-using System.Linq;
+using static Vanara.PInvoke.User32;
using IComDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
-using System.Runtime.InteropServices.ComTypes;
namespace Vanara.Windows.Shell
{
- internal static class ClipboardEx
+ ///
+ /// Initializes and closes a session using the Clipboard calling and then on
+ /// disposal. This can be called multiple times in nested calls and will ensure the Clipboard is only opened and closed at the highest scope.
+ ///
+ ///
+ public class NativeClipboard : IDisposable
{
- public static void Clear() => Clipboard.Clear();
- public static bool ContainsAudio() => Clipboard.ContainsAudio();
- public static bool ContainsData(string format) => Clipboard.ContainsData(format);
- public static bool ContainsFileDropList() => Clipboard.ContainsFileDropList();
- public static bool ContainsImage() => Clipboard.ContainsImage();
- public static bool ContainsText() => Clipboard.ContainsText();
- public static bool ContainsText(TextDataFormat format) => Clipboard.ContainsText(format);
- public static Stream GetAudioStream() => Clipboard.GetAudioStream();
- public static object GetData(string format) => Clipboard.GetData(format);
- public static System.Windows.Forms.IDataObject GetDataObject() => Clipboard.GetDataObject();
- public static IList GetFileDropList() => new List(Clipboard.GetFileDropList().Cast());
- public static Image GetImage() => Clipboard.GetImage();
- public static string GetText() => Clipboard.GetText();
- public static string GetText(TextDataFormat format) => Clipboard.GetText(format);
- public static void SetAudio(byte[] audioBytes) => Clipboard.SetAudio(audioBytes);
- public static void SetAudio(Stream audioStream) => Clipboard.SetAudio(audioStream);
- public static void SetData(string format, object data) => Clipboard.SetData(format, data);
- public static void SetDataObject(object data) => Clipboard.SetDataObject(data);
- public static void SetDataObject(object data, bool copy) => Clipboard.SetDataObject(data, copy);
- [UIPermission(SecurityAction.Demand, Clipboard = UIPermissionClipboard.OwnClipboard)]
- public static void SetDataObject(object data, bool copy, int retryTimes, int retryDelay) => Clipboard.SetDataObject(data, copy, retryTimes, retryDelay);
- public static void SetFileDropList(IEnumerable filePaths) => Clipboard.SetFileDropList(ToSC(filePaths));
- public static void SetImage(Image image) => Clipboard.SetImage(image);
- public static void SetText(string text) => Clipboard.SetText(text);
- public static void SetText(string text, TextDataFormat format) => Clipboard.SetText(text, format);
+ private static readonly ListenerWindow listener = new ListenerWindow();
+ private static int HdrLen = 0;
- internal static StringCollection ToSC(IEnumerable e) { var sc = new StringCollection(); if (e != null) sc.AddRange(e.ToArray()); return sc; }
+ [ThreadStatic]
+ private static bool open = false;
- internal static string Id(this ShellDataFormat fmt)
+ private bool dontClose = false;
+
+ /// Initializes a new instance of the class.
+ /// If set to , is called to clear the Clipboard.
+ ///
+ /// A handle to the window to be associated with the open clipboard. If this parameter is HWND.NULL, the open clipboard is
+ /// associated with the current task.
+ ///
+ public NativeClipboard(bool empty = false, HWND hWndNewOwner = default)
{
- var ansi = System.Text.Encoding.Default.IsSingleByte;
- string cfval = null;
- switch (fmt)
+ if (open)
{
- case ShellDataFormat.FileDescriptor:
- cfval = ansi ? ShellClipboardFormat.CFSTR_FILEDESCRIPTORA : ShellClipboardFormat.CFSTR_FILEDESCRIPTORW;
- break;
- case ShellDataFormat.FileName:
- cfval = ansi ? ShellClipboardFormat.CFSTR_FILENAMEA : ShellClipboardFormat.CFSTR_FILENAMEW;
- break;
- case ShellDataFormat.FileNameMap:
- cfval = ansi ? ShellClipboardFormat.CFSTR_FILENAMEMAPA : ShellClipboardFormat.CFSTR_FILENAMEMAPW;
- break;
- case ShellDataFormat.InetUrl:
- cfval = ansi ? ShellClipboardFormat.CFSTR_INETURLA: ShellClipboardFormat.CFSTR_INETURLW;
- break;
- case ShellDataFormat.AutoPlayLists:
- cfval = ShellClipboardFormat.CFSTR_AUTOPLAY_SHELLIDLISTS;
- break;
- case ShellDataFormat.FileAttributes:
- cfval = ShellClipboardFormat.CFSTR_FILE_ATTRIBUTES_ARRAY;
- break;
- case ShellDataFormat.InvokeCommandDropParam:
- cfval = ShellClipboardFormat.CFSTR_INVOKECOMMAND_DROPPARAM;
- break;
- default:
- cfval = GetSCFField("CFSTR_" + fmt.ToString().ToUpper());
- break;
+ dontClose = true;
+ return;
}
- return cfval ?? throw new ArgumentOutOfRangeException(nameof(fmt));
+ if (!OpenClipboard(hWndNewOwner))
+ Win32Error.ThrowLastError();
+ open = true;
+ if (empty)
+ Empty();
+ }
- string GetSCFField(string fName)
+ /// Occurs when whenever the contents of the Clipboard have changed.
+ public static event EventHandler ClipboardUpdate;
+
+ /// Retrieves the currently supported clipboard formats.
+ /// A sequence of the currently supported formats.
+ public static IEnumerable CurrentlySupportedFormats
+ {
+ get
{
- var fi = typeof(ShellClipboardFormat).GetField(fName, BindingFlags.Public | BindingFlags.Static);
- if (fi != null && fi.IsLiteral && !fi.IsInitOnly && fi.FieldType == typeof(string))
- return (string)fi.GetRawConstantValue();
- return null;
+ GetUpdatedClipboardFormats(null, 0, out var cnt);
+ var fmts = new uint[cnt];
+ Win32Error.ThrowLastErrorIfFalse(GetUpdatedClipboardFormats(fmts, (uint)fmts.Length, out cnt));
+ return fmts.Take((int)cnt).Select(u => DataFormats.GetFormat(unchecked((int)u))).ToArray();
+ }
+ }
+
+ /// Retrieves the window handle of the current owner of the clipboard.
+ ///
+ /// If the function succeeds, the return value is the handle to the window that owns the clipboard.
+ /// If the clipboard is not owned, the return value is IntPtr.Zero.
+ ///
+ ///
+ /// The clipboard can still contain data even if the clipboard is not currently owned.
+ /// In general, the clipboard owner is the window that last placed data in clipboard.
+ ///
+ public static IntPtr GetClipboardOwner() => (IntPtr)User32.GetClipboardOwner();
+
+ /// Retrieves the first available clipboard format in the specified list.
+ /// The clipboard formats, in priority order.
+ ///
+ /// If the function succeeds, the return value is the first clipboard format in the list for which data is available. If the
+ /// clipboard is empty, the return value is 0. If the clipboard contains data, but not in any of the specified formats, the return
+ /// value is –1.
+ ///
+ public static int GetFirstFormatAvailable(params int[] idList) => GetPriorityClipboardFormat(Array.ConvertAll(idList, i => (uint)i), idList.Length);
+
+ /// Retrieves the handle to the window that currently has the clipboard open.
+ ///
+ /// If the function succeeds, the return value is the handle to the window that has the clipboard open. If no window has the
+ /// clipboard open, the return value is IntPtr.Zero.
+ ///
+ ///
+ /// If an application or DLL specifies a NULL window handle when calling the OpenClipboard function, the clipboard is opened
+ /// but is not associated with a window. In such a case, GetOpenClipboardWindow returns IntPtr.Zero.
+ ///
+ public static IntPtr GetOpenClipboardWindow() => (IntPtr)User32.GetOpenClipboardWindow();
+
+ /// Determines whether the clipboard contains data in the specified format.
+ /// The name of a standard or registered clipboard format.
+ /// If the clipboard format is available, the return value is ; otherwise .
+ public static bool IsFormatAvailable(string format) => IsClipboardFormatAvailable((uint)DataFormats.GetFormat(format).Id);
+
+ /// Determines whether the clipboard contains data in the specified format.
+ /// A standard or registered clipboard format.
+ /// If the clipboard format is available, the return value is ; otherwise .
+ public static bool IsFormatAvailable(int id) => IsClipboardFormatAvailable((uint)id);
+
+ ///
+ /// Retrieves data from the clipboard in a specified format. The clipboard must have been opened previously and this pointer cannot
+ /// be used once goes out of scope.
+ ///
+ /// A clipboard format. For a description of the standard clipboard formats, see Standard Clipboard Formats.
+ ///
+ /// If the function succeeds, the return value is the handle to a clipboard object in the specified format.
+ /// If the function fails, the return value is IntPtr.Zero. To get extended error information, call GetLastError.
+ ///
+ ///
+ /// Caution Clipboard data is not trusted. Parse the data carefully before using it in your application.
+ /// An application can enumerate the available formats in advance by using the EnumClipboardFormats function.
+ ///
+ /// The clipboard controls the handle that the GetClipboardData function returns, not the application. The application should
+ /// copy the data immediately. The application must not free the handle nor leave it locked. The application must not use the handle
+ /// after the method is called, after is disposed, or after any of the
+ /// Set... methods are called with the same clipboard format.
+ ///
+ ///
+ /// The system performs implicit data format conversions between certain clipboard formats when an application calls the
+ /// GetClipboardData function. For example, if the CF_OEMTEXT format is on the clipboard, a window can retrieve data in the
+ /// CF_TEXT format. The format on the clipboard is converted to the requested format on demand. For more information, see
+ /// Synthesized Clipboard Formats.
+ ///
+ ///
+ public IntPtr DanagerousGetData(int formatId) => GetClipboardData((uint)formatId);
+
+ ///
+ /// Retrieves data from the clipboard in a specified format. The clipboard must have been opened previously and this pointer cannot
+ /// be used once goes out of scope.
+ ///
+ /// A clipboard format. For a description of the standard clipboard formats, see Standard Clipboard Formats.
+ ///
+ /// If the function succeeds, the return value is the handle to a clipboard object in the specified format.
+ /// If the function fails, the return value is IntPtr.Zero. To get extended error information, call GetLastError.
+ ///
+ ///
+ /// Caution Clipboard data is not trusted. Parse the data carefully before using it in your application.
+ /// An application can enumerate the available formats in advance by using the EnumClipboardFormats function.
+ ///
+ /// The clipboard controls the handle that the GetClipboardData function returns, not the application. The application should
+ /// copy the data immediately. The application must not free the handle nor leave it locked. The application must not use the handle
+ /// after the method is called, after is disposed, or after any of the
+ /// Set... methods are called with the same clipboard format.
+ ///
+ ///
+ /// The system performs implicit data format conversions between certain clipboard formats when an application calls the
+ /// GetClipboardData function. For example, if the CF_OEMTEXT format is on the clipboard, a window can retrieve data in the
+ /// CF_TEXT format. The format on the clipboard is converted to the requested format on demand. For more information, see
+ /// Synthesized Clipboard Formats.
+ ///
+ ///
+ public IntPtr DanagerousGetData(string format) => GetClipboardData((uint)DataFormats.GetFormat(format).Id);
+
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ public void Dispose()
+ {
+ if (dontClose) return;
+ CloseClipboard();
+ open = false;
+ }
+
+ ///
+ /// Empties the clipboard and frees handles to data in the clipboard. The function then assigns ownership of the clipboard to the
+ /// window that currently has the clipboard open.
+ ///
+ public void Empty() => Win32Error.ThrowLastErrorIfFalse(EmptyClipboard());
+
+ /// Enumerates the data formats currently available on the clipboard.
+ /// An enumeration of the data formats currently available on the clipboard.
+ ///
+ ///
+ /// The EnumFormats function enumerates formats in the order that they were placed on the clipboard. If you are copying
+ /// information to the clipboard, add clipboard objects in order from the most descriptive clipboard format to the least descriptive
+ /// clipboard format. If you are pasting information from the clipboard, retrieve the first clipboard format that you can handle.
+ /// That will be the most descriptive clipboard format that you can handle.
+ ///
+ ///
+ /// The system provides automatic type conversions for certain clipboard formats. In the case of such a format, this function
+ /// enumerates the specified format, then enumerates the formats to which it can be converted.
+ ///
+ ///
+ public IEnumerable EnumAvailableFormats() => EnumClipboardFormats().Select(i => DataFormats.GetFormat((int)i));
+
+ /// Gets the text from the native Clipboard in the specified format.
+ /// The format.
+ /// The string value or if the format is not available.
+ public string GetText(TextDataFormat format)
+ {
+ return format switch
+ {
+ TextDataFormat.Text => StringHelper.GetString(GetClipboardData(CLIPFORMAT.CF_TEXT), CharSet.Ansi),
+ TextDataFormat.UnicodeText => StringHelper.GetString(GetClipboardData(CLIPFORMAT.CF_UNICODETEXT), CharSet.Unicode),
+ TextDataFormat.Rtf => StringHelper.GetString(DanagerousGetData(DataFormats.Rtf), CharSet.Ansi),
+ TextDataFormat.Html => GetHtml(DanagerousGetData(DataFormats.Html)),
+ TextDataFormat.CommaSeparatedValue => StringHelper.GetString(DanagerousGetData(DataFormats.CommaSeparatedValue), CharSet.Ansi),
+ _ => null,
+ };
+ }
+
+ /// Places data on the clipboard in a specified clipboard format.
+ /// The clipboard format. This parameter can be a registered format or any of the standard clipboard formats.
+ /// The binary data in the specified format.
+ /// data
+ public void SetBinaryData(string format, byte[] data) => SetBinaryData(DataFormats.GetFormat(format).Id, data);
+
+ /// Places data on the clipboard in a specified clipboard format.
+ /// The clipboard format. This parameter can be a registered format or any of the standard clipboard formats.
+ /// The binary data in the specified format.
+ /// data
+ public void SetBinaryData(int formatId, byte[] data)
+ {
+ using var pMem = new SafeMoveableHGlobalHandle(data);
+ Win32Error.ThrowLastErrorIfInvalid(pMem);
+ Win32Error.ThrowLastErrorIfNull(SetClipboardData((uint)formatId, pMem.DangerousGetHandle()));
+ }
+
+ /// Places data on the clipboard in a specified clipboard format.
+ /// The clipboard format. This parameter can be a registered format or any of the standard clipboard formats.
+ /// The data in the format dictated by .
+ public void SetData(int formatId, T data)
+ {
+ using var pMem = SafeMoveableHGlobalHandle.CreateFromStructure(data);
+ Win32Error.ThrowLastErrorIfInvalid(pMem);
+ Win32Error.ThrowLastErrorIfNull(SetClipboardData((uint)formatId, pMem.DangerousGetHandle()));
+ }
+
+ /// Places data on the clipboard in a specified clipboard format.
+ /// The clipboard format. This parameter can be a registered format or any of the standard clipboard formats.
+ /// The data in the format dictated by .
+ public void SetData(int formatId, IEnumerable values) where T : struct
+ {
+ using var pMem = SafeMoveableHGlobalHandle.CreateFromList(values);
+ Win32Error.ThrowLastErrorIfInvalid(pMem);
+ Win32Error.ThrowLastErrorIfNull(SetClipboardData((uint)formatId, pMem.DangerousGetHandle()));
+ }
+
+ /// Places data on the clipboard in a specified clipboard format.
+ /// The clipboard format. This parameter can be a registered format or any of the standard clipboard formats.
+ /// The list of strings.
+ /// The packing type for the strings.
+ /// The character set to use for the strings.
+ public void SetData(int formatId, IEnumerable values, StringListPackMethod packing = StringListPackMethod.Concatenated, CharSet charSet = CharSet.Auto)
+ {
+ using var pMem = SafeMoveableHGlobalHandle.CreateFromStringList(values, packing, charSet);
+ Win32Error.ThrowLastErrorIfInvalid(pMem);
+ Win32Error.ThrowLastErrorIfNull(SetClipboardData((uint)formatId, pMem.DangerousGetHandle()));
+ }
+
+ /// Sets multiple text types to the Clipboard.
+ /// The Unicode Text value.
+ /// The HTML text value. If , this format will not be set.
+ /// The Rich Text Format value. If , this format will not be set.
+ public void SetText(string text, string htmlText = null, string rtfText = null)
+ {
+ if (text is null && htmlText is null && rtfText is null) return;
+ SetText(text, TextDataFormat.UnicodeText);
+ SetBinaryData(DataFormats.Locale, BitConverter.GetBytes(CultureInfo.CurrentCulture.LCID));
+ if (htmlText != null) SetText(htmlText, TextDataFormat.Html);
+ if (rtfText != null) SetText(rtfText, TextDataFormat.Rtf);
+ }
+
+ /// Sets a specific text type to the Clipboard.
+ /// The text value.
+ /// The clipboard text format to set.
+ public void SetText(string value, TextDataFormat format)
+ {
+ (byte[] bytes, int fmt) = format switch
+ {
+ TextDataFormat.Text => (Encoding.ASCII.GetBytes(value + '\0'), (int)CLIPFORMAT.CF_TEXT),
+ TextDataFormat.UnicodeText => (Encoding.Unicode.GetBytes(value + '\0'), (int)CLIPFORMAT.CF_UNICODETEXT),
+ TextDataFormat.Rtf => (Encoding.ASCII.GetBytes(value + '\0'), DataFormats.GetFormat(DataFormats.Rtf).Id),
+ TextDataFormat.Html => (MakeClipHtml(value), DataFormats.GetFormat(DataFormats.Html).Id),
+ TextDataFormat.CommaSeparatedValue => (Encoding.ASCII.GetBytes(value + '\0'), DataFormats.GetFormat(DataFormats.CommaSeparatedValue).Id),
+ _ => default,
+ };
+ SetBinaryData(fmt, bytes);
+ }
+
+ /// Sets a URL with optional title to the clipboard.
+ /// The URL.
+ /// The title. This value can be .
+ /// url
+ public void SetUrl(string url, string title = null)
+ {
+ if (url is null) throw new ArgumentNullException(nameof(url));
+ SetText(url, $"{title ?? url}", null);
+ var textUrl = (title is null ? "" : (title + '\n')) + url + '\0';
+ SetBinaryData(ShellClipboardFormat.CFSTR_INETURLA, Encoding.ASCII.GetBytes(textUrl));
+ SetBinaryData(ShellClipboardFormat.CFSTR_INETURLW, Encoding.Unicode.GetBytes(textUrl));
+ }
+
+ internal static StringCollection ToSC(IEnumerable e)
+ {
+ var sc = new StringCollection();
+ if (e != null)
+ sc.AddRange(e.ToArray());
+ return sc;
+ }
+
+ private static T GetComData(DataObject dobj, string fmt, Func convert, T defValue = default)
+ {
+ T ret = defValue;
+ if (dobj is IComDataObject cdo)
+ {
+ var fc = new FORMATETC { cfFormat = (short)DataFormats.GetFormat(fmt).Id, dwAspect = DVASPECT.DVASPECT_CONTENT, lindex = -1, tymed = TYMED.TYMED_HGLOBAL };
+ try
+ {
+ cdo.GetData(ref fc, out var medium);
+ if (medium.unionmember != default)
+ ret = convert(medium.unionmember);
+ ReleaseStgMedium(medium);
+ }
+ catch { }
+ }
+ return ret;
+ }
+
+ internal static string GetHtml(IntPtr ptr)
+ {
+ const string HdrRegEx = @"Version:\d\.\d\s+StartHTML:(\d+)\s+EndHTML:(\d+)\s+StartFragment:(\d+)\s+EndFragment:(\d+)\s+(?:StartSelection:(\d+)\s+EndSelection:(\d+)\s+)?";
+
+ if (ptr == IntPtr.Zero) return null;
+
+ // Find length of data by looking for a '\0' byte.
+ var byteCount = 0;
+ unsafe
+ {
+ for (byte* bp = (byte*)ptr.ToPointer(); byteCount < 4 * 1024 * 1024 && *bp != 0; byteCount++, bp++) ;
+ }
+ var bytes = ptr.ToArray(byteCount);
+ // Get UTF8 encoded string
+ var utf8String = Encoding.UTF8.GetString(bytes);
+ // Find markers
+ var match = Regex.Match(utf8String, HdrRegEx);
+ if (!match.Success) throw new InvalidOperationException("HTML format header cannot be processed.");
+ var startHtml = int.Parse(match.Groups[1].Value.TrimStart('0'));
+ var endHtml = int.Parse(match.Groups[2].Value.TrimStart('0'));
+ var startFrag = int.Parse(match.Groups[3].Value.TrimStart('0'));
+ var endFrag = int.Parse(match.Groups[4].Value.TrimStart('0'));
+ var startSel = int.Parse(match.Groups[5].Value.TrimStart('0'));
+ var endSel = int.Parse(match.Groups[6].Value.TrimStart('0'));
+
+ return Encoding.UTF8.GetString(bytes, startFrag, endFrag - startFrag);
+ }
+
+ private static byte[] MakeClipHtml(string value)
+ {
+ const string Header = "Version:0.9\r\nStartHTML:{0:0000000000}\r\nEndHTML:{1:0000000000}\r\nStartFragment:{2:0000000000}\r\nEndFragment:{3:0000000000}\r\nStartSelection:{4:0000000000}\r\nEndSelection:{5:0000000000}\r\n";
+ const string htmlDocType = "";
+ const string htmlBodyStart = "Snippet";
+ const string htmlBodyEnd = "";
+ const string fragmentStart = "";
+ const string fragmentEnd = "";
+
+ var sb = new StringBuilder();
+ if (value.IndexOf("", StringComparison.OrdinalIgnoreCase) < 0)
+ sb.Append(htmlBodyStart);
+
+ var fragStartIdx = value.IndexOf(fragmentStart, StringComparison.OrdinalIgnoreCase);
+ if (fragStartIdx < 0)
+ sb.Append(fragmentStart);
+ else
+ {
+ sb.Append(value.Substring(0, fragStartIdx + fragmentStart.Length));
+ value = value.Remove(0, fragStartIdx + fragmentStart.Length);
+ }
+ fragStartIdx = Encoding.UTF8.GetByteCount(sb.ToString());
+
+ var fragEndIdx = value.IndexOf(fragmentEnd, StringComparison.OrdinalIgnoreCase);
+ if (fragEndIdx < 0)
+ {
+ sb.Append(value);
+ fragEndIdx = Encoding.UTF8.GetByteCount(sb.ToString());
+ sb.Append(fragmentEnd);
+ }
+ else
+ {
+ var preFrag = value.Substring(0, fragEndIdx);
+ value = value.Remove(0, fragEndIdx);
+ sb.Append(preFrag);
+ fragEndIdx = Encoding.UTF8.GetByteCount(sb.ToString());
+ sb.Append(value);
+ }
+ if (value.IndexOf("