File: System\Private\Windows\Ole\ClipboardCore.cs
Web Access
Project: src\src\System.Private.Windows.Core\src\System.Private.Windows.Core.csproj (System.Private.Windows.Core)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Windows.Win32.System.Com;
 
namespace System.Private.Windows.Ole;
 
/// <summary>
///  Contains platform-agnostic clipboard operations.
/// </summary>
internal static unsafe class ClipboardCore<TOleServices>
    where TOleServices : IOleServices
{
    /// <summary>
    ///  The number of times to retry OLE clipboard operations.
    /// </summary>
    private const int OleRetryCount = 10;
 
    /// <summary>
    ///  The amount of time in milliseconds to sleep between retrying OLE clipboard operations.
    /// </summary>
    private const int OleRetryDelay = 100;
 
    /// <summary>
    ///  Removes all data from the Clipboard.
    /// </summary>
    /// <returns>An <see cref="HRESULT"/> indicating the success or failure of the operation.</returns>
    internal static HRESULT Clear()
    {
        TOleServices.EnsureThreadState();
 
        HRESULT result;
        int retryCount = OleRetryCount;
 
        while ((result = PInvokeCore.OleSetClipboard(null)).Failed)
        {
            if (--retryCount < 0)
            {
                break;
            }
 
            Thread.Sleep(millisecondsTimeout: OleRetryDelay);
        }
 
        return result;
    }
 
    /// <summary>
    ///  Attempts to set the specified data on the Clipboard.
    /// </summary>
    /// <param name="dataObject">The data object to set on the Clipboard.</param>
    /// <param name="copy">Indicates whether to copy the data to the Clipboard.</param>
    /// <param name="retryTimes">The number of times to retry the operation if it fails.</param>
    /// <param name="retryDelay">The amount of time in milliseconds to wait between retries.</param>
    /// <returns>An <see cref="HRESULT"/> indicating the success or failure of the operation.</returns>
    internal static HRESULT SetData(
        IComVisibleDataObject dataObject,
        bool copy,
        int retryTimes = OleRetryCount,
        int retryDelay = OleRetryDelay)
    {
        TOleServices.EnsureThreadState();
 
        ArgumentOutOfRangeException.ThrowIfNegative(retryTimes);
        ArgumentOutOfRangeException.ThrowIfNegative(retryDelay);
 
        using var iDataObject = ComHelpers.GetComScope<IDataObject>(dataObject);
 
        HRESULT result;
        int retry = OleRetryCount;
        while ((result = PInvokeCore.OleSetClipboard(iDataObject)).Failed)
        {
            if (--retry < 0)
            {
                return result;
            }
 
            Thread.Sleep(millisecondsTimeout: OleRetryDelay);
        }
 
        if (copy)
        {
            retry = retryTimes;
            while ((result = PInvokeCore.OleFlushClipboard()).Failed)
            {
                if (--retry < 0)
                {
                    return result;
                }
 
                Thread.Sleep(millisecondsTimeout: retryDelay);
            }
        }
 
        return result;
    }
 
    /// <summary>
    ///  Attempts to retrieve data from the Clipboard.
    /// </summary>
    /// <param name="proxyDataObject">The proxy data object retrieved from the Clipboard.</param>
    /// <param name="originalObject">The original object retrieved from the Clipboard, if available.</param>
    /// <returns>An <see cref="HRESULT"/> indicating the success or failure of the operation.</returns>
    public static HRESULT TryGetData(out ComScope<IDataObject> proxyDataObject, out object? originalObject)
    {
        TOleServices.EnsureThreadState();
 
        proxyDataObject = new(null);
        originalObject = null;
 
        int retryTimes = OleRetryCount;
        HRESULT result;
 
        while ((result = PInvokeCore.OleGetClipboard(proxyDataObject)).Failed)
        {
            if (--retryTimes < 0)
            {
                return result;
            }
 
            Thread.Sleep(millisecondsTimeout: OleRetryDelay);
        }
 
        // OleGetClipboard always returns a proxy. The proxy forwards all IDataObject method calls to the real data object,
        // without giving out the real data object. If the data placed on the clipboard is not one of our CCWs or the clipboard
        // has been flushed, a wrapper around the proxy for us to use will be given. However, if the data placed on
        // the clipboard is one of our own and the clipboard has not been flushed, we need to retrieve the real data object
        // pointer in order to retrieve the original managed object via ComWrappers if an IDataObject was set on the clipboard.
        // To do this, we must query for an interface that is not known to the proxy e.g. IComCallableWrapper.
        // If we are able to query for IComCallableWrapper it means that the real data object is one of our CCWs and we've retrieved it successfully,
        // otherwise it is not ours and we will use the wrapped proxy.
        using ComScope<IComCallableWrapper> realDataObject = proxyDataObject.TryQuery<IComCallableWrapper>(out HRESULT wrapperResult);
 
        if (wrapperResult.Succeeded)
        {
            ComHelpers.TryUnwrapComWrapperCCW(realDataObject.AsUnknown, out originalObject);
        }
 
        return result;
    }
 
    /// <summary>
    ///  Checks if the specified <paramref name="format"/> is valid and compatible with the specified <paramref name="type"/>.
    /// </summary>
    /// <remarks>
    ///  <para>
    ///   This is intended to be used as a pre-validation step to give a more useful error to callers.
    ///  </para>
    /// </remarks>
    internal static bool IsValidTypeForFormat(Type type, string format)
    {
        if (string.IsNullOrWhiteSpace(format))
        {
            return false;
        }
 
        if (IsValidPredefinedFormatTypeCombination(format, type))
        {
            return true;
        }
 
        throw new NotSupportedException(string.Format(
            SR.ClipboardOrDragDrop_InvalidFormatTypeCombination,
            type.FullName,
            format));
 
        static bool IsValidPredefinedFormatTypeCombination(string format, Type type) => format switch
        {
            DataFormatNames.Text
                or DataFormatNames.UnicodeText
                or DataFormatNames.String
                or DataFormatNames.Rtf
                or DataFormatNames.Html
                or DataFormatNames.OemText => typeof(string) == type,
 
            DataFormatNames.FileDrop
                or DataFormatNames.FileNameAnsi
                or DataFormatNames.FileNameUnicode => typeof(string[]) == type,
 
            _ => TOleServices.IsValidTypeForFormat(type, format)
        };
    }
}