diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/ClipboardConstants.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/ClipboardConstants.cs
new file mode 100644
index 00000000000..81c5c4aea44
--- /dev/null
+++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/ClipboardConstants.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Private.Windows.Ole;
+
+internal static class ClipboardConstants
+{
+ ///
+ /// The number of times to retry OLE clipboard operations.
+ ///
+ internal const int OleRetryCount = 10;
+
+ ///
+ /// The amount of time in milliseconds to sleep between retrying OLE clipboard operations.
+ ///
+ internal const int OleRetryDelay = 100;
+}
diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/ClipboardCore.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/ClipboardCore.cs
index 994bbc07597..96018bcb342 100644
--- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/ClipboardCore.cs
+++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/ClipboardCore.cs
@@ -12,23 +12,13 @@ namespace System.Private.Windows.Ole;
internal static unsafe class ClipboardCore
where TOleServices : IOleServices
{
- ///
- /// The number of times to retry OLE clipboard operations.
- ///
- private const int OleRetryCount = 10;
-
- ///
- /// The amount of time in milliseconds to sleep between retrying OLE clipboard operations.
- ///
- private const int OleRetryDelay = 100;
-
///
/// Removes all data from the Clipboard.
///
/// An indicating the success or failure of the operation.
internal static HRESULT Clear(
- int retryTimes = OleRetryCount,
- int retryDelay = OleRetryDelay)
+ int retryTimes = ClipboardConstants.OleRetryCount,
+ int retryDelay = ClipboardConstants.OleRetryDelay)
{
TOleServices.EnsureThreadState();
@@ -53,8 +43,8 @@ internal static HRESULT Clear(
///
/// An indicating the success or failure of the operation.
internal static HRESULT Flush(
- int retryTimes = OleRetryCount,
- int retryDelay = OleRetryDelay)
+ int retryTimes = ClipboardConstants.OleRetryCount,
+ int retryDelay = ClipboardConstants.OleRetryDelay)
{
TOleServices.EnsureThreadState();
@@ -85,8 +75,8 @@ internal static HRESULT Flush(
internal static HRESULT SetData(
IComVisibleDataObject dataObject,
bool copy,
- int retryTimes = OleRetryCount,
- int retryDelay = OleRetryDelay)
+ int retryTimes = ClipboardConstants.OleRetryCount,
+ int retryDelay = ClipboardConstants.OleRetryDelay)
{
TOleServices.EnsureThreadState();
@@ -134,8 +124,8 @@ internal static HRESULT SetData(
internal static HRESULT TryGetData(
out ComScope proxyDataObject,
out object? originalObject,
- int retryTimes = OleRetryCount,
- int retryDelay = OleRetryDelay)
+ int retryTimes = ClipboardConstants.OleRetryCount,
+ int retryDelay = ClipboardConstants.OleRetryDelay)
{
TOleServices.EnsureThreadState();
@@ -184,8 +174,8 @@ internal static HRESULT TryGetData(
///
internal static bool IsObjectOnClipboard(
object @object,
- int retryTimes = OleRetryCount,
- int retryDelay = OleRetryDelay)
+ int retryTimes = ClipboardConstants.OleRetryCount,
+ int retryDelay = ClipboardConstants.OleRetryDelay)
{
if (@object is null)
{
@@ -210,8 +200,8 @@ internal static bool IsObjectOnClipboard(
///
internal static HRESULT GetDataObject(
out TIDataObject? dataObject,
- int retryTimes = OleRetryCount,
- int retryDelay = OleRetryDelay)
+ int retryTimes = ClipboardConstants.OleRetryCount,
+ int retryDelay = ClipboardConstants.OleRetryDelay)
where TDataObject : class, IDataObjectInternal, TIDataObject
where TIDataObject : class
{
diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.NativeToManagedAdapter.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.NativeToManagedAdapter.cs
index 115809900a6..4b1e6d3baba 100644
--- a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.NativeToManagedAdapter.cs
+++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Composition.NativeToManagedAdapter.cs
@@ -359,12 +359,27 @@ private static bool TryGetHGLOBALData(
tymed = (uint)Com.TYMED.TYMED_HGLOBAL
};
- if (dataObject->QueryGetData(formatetc).Failed)
+ HRESULT hr = HRESULT.S_OK;
+
+ Utilities.ExecuteWithRetry(() =>
+ {
+ hr = dataObject->QueryGetData(formatetc);
+ return hr == HRESULT.CLIPBRD_E_CANT_OPEN;
+ });
+
+ if (hr.Failed)
{
return false;
}
- HRESULT hr = dataObject->GetData(formatetc, out Com.STGMEDIUM medium);
+ Com.STGMEDIUM medium = default;
+ hr = HRESULT.S_OK;
+
+ Utilities.ExecuteWithRetry(() =>
+ {
+ hr = dataObject->GetData(formatetc, out medium);
+ return hr == HRESULT.CLIPBRD_E_CANT_OPEN;
+ });
// One of the ways this can happen is when we attempt to put binary formatted data onto the
// clipboard, which will succeed as Windows ignores all errors when putting data on the clipboard.
@@ -376,6 +391,7 @@ private static bool TryGetHGLOBALData(
Debug.WriteLineIf(hr == HRESULT.E_UNEXPECTED, "E_UNEXPECTED returned when trying to get clipboard data.");
Debug.WriteLineIf(hr == HRESULT.COR_E_SERIALIZATION,
"COR_E_SERIALIZATION returned when trying to get clipboard data, for example, BinaryFormatter threw SerializationException.");
+ Debug.WriteLineIf(hr == HRESULT.CLIPBRD_E_CANT_OPEN, "CLIPBRD_E_CANT_OPEN returned when clipboard was in locked state.");
// If GetData failed, don't try to read from the medium - it may contain uninitialized data.
// (This can easily happen when the clipboard content changes between QueryGetData and GetData calls.)
@@ -424,9 +440,30 @@ private static bool TryGetIStreamData(
tymed = (uint)Com.TYMED.TYMED_ISTREAM
};
+ HRESULT result = HRESULT.S_OK;
+
+ Utilities.ExecuteWithRetry(() =>
+ {
+ result = dataObject->QueryGetData(formatEtc);
+ return result == HRESULT.CLIPBRD_E_CANT_OPEN;
+ });
+
+ if (result.Failed)
+ {
+ return false;
+ }
+
+ Com.STGMEDIUM medium = default;
+ result = HRESULT.S_OK;
+
+ Utilities.ExecuteWithRetry(() =>
+ {
+ result = dataObject->GetData(formatEtc, out medium);
+ return result == HRESULT.CLIPBRD_E_CANT_OPEN;
+ });
+
// Limit the # of exceptions we may throw below.
- if (dataObject->QueryGetData(formatEtc).Failed
- || dataObject->GetData(formatEtc, out Com.STGMEDIUM medium).Failed)
+ if (result.Failed)
{
return false;
}
diff --git a/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Utilities.cs b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Utilities.cs
new file mode 100644
index 00000000000..b115e0df8df
--- /dev/null
+++ b/src/System.Private.Windows.Core/src/System/Private/Windows/Ole/Utilities.cs
@@ -0,0 +1,34 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Private.Windows.Ole;
+
+internal static class Utilities
+{
+ ///
+ /// Executes the given action with retry logic for OLE operations.
+ ///
+ /// Execute the action which returns bool value indicating whether to continue retries or stop.
+ /// Number of retry attempts.
+ /// Delay in milliseconds between retries.
+ internal static void ExecuteWithRetry(
+ Func action,
+ int retryCount = ClipboardConstants.OleRetryCount,
+ int retryDelay = ClipboardConstants.OleRetryDelay)
+ {
+ int attempts = 0;
+
+ while (attempts < retryCount)
+ {
+ if (action())
+ {
+ attempts++;
+ Thread.Sleep(retryDelay);
+
+ continue;
+ }
+
+ break;
+ }
+ }
+}
diff --git a/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/UtilitiesTests.cs b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/UtilitiesTests.cs
new file mode 100644
index 00000000000..9fc225c3847
--- /dev/null
+++ b/src/System.Private.Windows.Core/tests/System.Private.Windows.Core.Tests/System/Private/Windows/Ole/UtilitiesTests.cs
@@ -0,0 +1,58 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Private.Windows.Ole;
+
+public class UtilitiesTests
+{
+ [Fact]
+ public void ExecuteWithRetry_ActionReturnsFalse_CallsOnce()
+ {
+ int calls = 0;
+
+ Utilities.ExecuteWithRetry(() =>
+ {
+ calls++;
+
+ return false;
+ },
+ retryCount: 3,
+ retryDelay: 0);
+
+ Assert.Equal(1, calls);
+ }
+
+ [Fact]
+ public void ExecuteWithRetry_ActionReturnsTrueThenFalse_RetriesUntilFalse()
+ {
+ int calls = 0;
+
+ Utilities.ExecuteWithRetry(() =>
+ {
+ calls++;
+
+ return calls < 3;
+ },
+ retryCount: 5,
+ retryDelay: 0);
+
+ Assert.Equal(3, calls);
+ }
+
+ [Fact]
+ public void ExecuteWithRetry_ActionAlwaysReturnsTrue_StopsAtRetryCount()
+ {
+ int calls = 0;
+
+ Utilities.ExecuteWithRetry(() =>
+ {
+ calls++;
+
+ return true;
+ },
+ retryCount: 3,
+ retryDelay: 0);
+
+ Assert.Equal(3, calls);
+ }
+}
diff --git a/src/System.Windows.Forms/System/Windows/Forms/OLE/WinFormsOleServices.cs b/src/System.Windows.Forms/System/Windows/Forms/OLE/WinFormsOleServices.cs
index 7bf810195bd..3e091d9558a 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/OLE/WinFormsOleServices.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/OLE/WinFormsOleServices.cs
@@ -85,19 +85,34 @@ static unsafe bool TryGetBitmapData(Com.IDataObject* dataObject, [NotNullWhen(tr
tymed = (uint)TYMED.TYMED_GDI
};
- HRESULT result = dataObject->QueryGetData(formatEtc);
+ HRESULT result = HRESULT.S_OK;
+
+ Utilities.ExecuteWithRetry(() =>
+ {
+ result = dataObject->QueryGetData(formatEtc);
+ return result == HRESULT.CLIPBRD_E_CANT_OPEN;
+ });
+
if (result.Failed)
{
return false;
}
- result = dataObject->GetData(formatEtc, out STGMEDIUM medium);
+ result = HRESULT.S_OK;
+ STGMEDIUM medium = default;
+
+ Utilities.ExecuteWithRetry(() =>
+ {
+ result = dataObject->GetData(formatEtc, out medium);
+ return result == HRESULT.CLIPBRD_E_CANT_OPEN;
+ });
// One of the ways this can happen is when we attempt to put binary formatted data onto the
// clipboard, which will succeed as Windows ignores all errors when putting data on the clipboard.
// The data state, however, is not good, and this error will be returned by Windows when asking to
// get the data out.
Debug.WriteLineIf(result == HRESULT.CLIPBRD_E_BAD_DATA, "CLIPBRD_E_BAD_DATA returned when trying to get clipboard data.");
+ Debug.WriteLineIf(result == HRESULT.CLIPBRD_E_CANT_OPEN, "CLIPBRD_E_CANT_OPEN returned when clipboard was in locked state.");
try
{