Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clipboard.SetText fails with CLIPBRD_E_CANT_OPEN #9901

Open
josefblaha opened this issue Oct 5, 2024 · 15 comments
Open

Clipboard.SetText fails with CLIPBRD_E_CANT_OPEN #9901

josefblaha opened this issue Oct 5, 2024 · 15 comments
Labels
Investigate Requires further investigation by the WPF team.

Comments

@josefblaha
Copy link

josefblaha commented Oct 5, 2024

Description

Most of the time, the call to Clipboard.SetText takes several hundred milliseconds and then fails with CLIPBRD_E_CANT_OPEN in the flush operation.

The WinForms implementation works fine. Why is that? What is special about the WPF implementation? What is this "flushing" concept anyway? In Win32 API, there is no flushing. You just allocate a buffer and handover its ownership by calling SetClipboardData.

Reproduction Steps

Attempt to copy text to clipboard in a WPF app like this:

Clipboard.SetText("text");

Expected behavior

It should place the text in clipboard immediately.

Actual behavior

Most of the time, it takes several hundred milliseconds and then fails with the following exception:

System.Runtime.InteropServices.COMException (0x800401D0): Module OpenClipboard failed (0x800401D0 (CLIPBRD_E_CANT_OPEN))
   at System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(Int32 errorCode, IntPtr errorInfo)
   at System.Windows.Clipboard.Flush()
   at WpfCopyTest.MainWindow.CopyButton_OnClick(Object sender, RoutedEventArgs e) in D:\Documents\WpfCopyTest\MainWindow.xaml.cs:line 39

Regression?

No response

Known Workarounds

The same code using System.Windows.Forms.Clipboard instead of System.Windows.Clipboard works without a problem.

Impact

No response

Configuration

.NET 8.0.400, Windows 11 23H2, x64

I don't think I have anything unusual installed that would monitor the clipboard and block it.
I tried disabling Windows clipboard history with no effect.

Other information

It doesn't look like a widespread issue, perhaps it's configuration specific.

Possibly related to #9106.

The reason I'm involved is that I'm trying to fix the clipboard issue in PowerToys: microsoft/PowerToys#25437

@rampaa
Copy link

rampaa commented Oct 5, 2024

I get the same exception while using Clipboard.SetText. I'm working around this issue by using Clipboard.SetDataObject(text, false); instead, which obviously is not ideal.

@Symbai
Copy link
Contributor

Symbai commented Oct 5, 2024

The WinForms implementation works fine. Why is that?

Here is possible answer for this question: https://stackoverflow.com/questions/68666/clipbrd-e-cant-open-error-when-setting-the-clipboard-from-net#comment1287439_69081

@miloush
Copy link
Contributor

miloush commented Oct 6, 2024

We retry too, same values as WinForms (10 times 100 ms), both for set data and flush:

// Retry OLE operations several times as mitigation for clipboard locking issues in TS sessions.
int i = OleRetryCount;
while (true)
{
// Clear the system clipboard by calling OleSetClipboard with null parameter.
int hr = OleServicesContext.CurrentOleServicesContext.OleSetClipboard(dataObject);
if (NativeMethods.Succeeded(hr))
{
break;
}
if (--i == 0)
{
Marshal.ThrowExceptionForHR(hr);
}
Thread.Sleep(OleRetryDelay);
}
if (copy)
{
// OleSetClipboard and OleFlushClipboard both modify the clipboard
// and cause notifications to be sent to clipboard listeners. We sleep a bit here to
// mitigate issues with clipboard listeners (like TS) corrupting the clipboard contents
// as a result of these two calls being back to back.
Thread.Sleep(OleFlushDelay);
Flush();
}

public static void Flush()
{
// Retry OLE operations several times as mitigation for clipboard locking issues in TS sessions.
int i = OleRetryCount;
while (true)
{
int hr = OleServicesContext.CurrentOleServicesContext.OleFlushClipboard();
if (NativeMethods.Succeeded(hr))
{
break;
}
if (--i == 0)
{
SecurityHelper.ThrowExceptionForHR(hr);
}
Thread.Sleep(OleRetryDelay);
}
}

Introduced in .NET FW 4.5

@josefblaha
Copy link
Author

I get the same exception while using Clipboard.SetText. I'm working around this issue by using Clipboard.SetDataObject(text, false); instead, which obviously is not ideal.

Yes, that works for me as well. Setting copy: false should not retain the clipboard contents when the app exists. However, for some reason it is retained anyway; at least for short strings.

@rampaa
Copy link

rampaa commented Oct 6, 2024

Setting copy: false should not retain the clipboard contents when the app exists. However, for some reason it is retained anyway; at least for short strings.

For me the text isn't being retained after the app is closed even if the text is quite short.

@miloush
Copy link
Contributor

miloush commented Oct 6, 2024

That sounds likes a noteworthy difference. @josefblaha can you check who is calling you with WM_RENDERFORMAT or WM_RENDERALLFORMATS?

@himgoyalmicro himgoyalmicro added the Investigate Requires further investigation by the WPF team. label Oct 7, 2024
@josefblaha
Copy link
Author

josefblaha commented Oct 8, 2024

I made a small app to test various methods of copying to clipboard: https://github.com/josefblaha/clipboard-playground

@miloush I'm not sure how to check for the caller. I tried handling the WM_RENDERFORMAT message and calling GetClipboardOwner. As per docs, when the message is sent, "the clipboard is currently being held open by the application that requested the format to be rendered." However, in my case, it returns my window handle:
image

Edit: Apparently I mismatched clipboard owner and the app that opened the clipboard to request the format. Owner equal to my window is expected.

@miloush
Copy link
Contributor

miloush commented Oct 8, 2024

GetOpenClipboardWindow. Seems to be TextInputHost for me, which makes sense since it shows the copied text on the touch keyboard. To answer your question, flushing causes the delay-rendered content to be rendered and stay in the clipboard after the app exits.

@miloush
Copy link
Contributor

miloush commented Oct 8, 2024

In fact maybe you could try GetOpenClipboardWindow when you get the CLIPBRD_E_CANT_OPEN error.

See also https://devblogs.microsoft.com/oldnewthing/20240410-00/?p=109632

@rampaa
Copy link

rampaa commented Oct 8, 2024

For the records, I get the CLIPBRD_E_CANT_OPEN exception with Clipboard.GetText() as well (and I don't know any fancy workaround for this problem, so I just retry till I get the text from clipboard).

I tried calling GetOpenClipboardWindow before calling Clipboard.GetText() and after getting the CLIPBRD_E_CANT_OPEN exception and it just returns 0 for both cases. For reference, here's the related code:

    private async Task<bool> CopyFromClipboard()
    {
        while (Clipboard.ContainsText())
        {
            try
            {
                Console.WriteLine(WinApi.GetOpenClipboardWindow()); // Prints 0
                string text = Clipboard.GetText(); // Can throw ExternalException exception, i.e., CLIPBRD_E_CANT_OPEN

                return true;
            }
            catch (ExternalException ex)
            {
                Console.WriteLine(WinApi.GetOpenClipboardWindow()); // Prints 0
                await Task.Delay(5).ConfigureAwait(true);
            }
        }

        return false;
    }

@josefblaha
Copy link
Author

For me, GetOpenClipboardWindow() returns 0 as well. Both after the exception and in WM_RENDERFORMAT handler.

What's interesting: as of today, the WPF SetText operation in fact places the text in the clipboard, even if it fails. I saw other users reported this behavior before and they then ignored the exception as a workaround, but I'm pretty sure the text was not copied on my machine before. I installed Windows updates this morning, so maybe something got fixed.

With that being said, my current theory is that something is reading contents of the clipboard whenever it's set. Sometimes, it manages to render the data and release the clipboard before the flush or after it. It such cases, the operation succeeds. But sometimes it gets there first and locks the clipboard for a second or more. In those cases, the flush fails.

I wonder how the OLE API set data render until the flush is called. Does it go through WM_RENDERFORMAT message? Maybe the flush kind-of locks itself. Suppose the following sequence:

My window Clipboard listener
OleSetClipboard
OpenClipboard
OleFlushClipboard (fails with CLIPBRD_E_CANT_OPEN)
Thread.Sleep
OleFlushClipboard (fails with CLIPBRD_E_CANT_OPEN)
...
COMException
WM_RENDERFORMAT handled
gets its data
CloseClipboard

@miloush
Copy link
Contributor

miloush commented Oct 9, 2024

Then I am afraid this is not going to be easy to figure out without a repro. If you are on Windows 11, I would expect it to behave similar as mine, i.e. the touch keyboard trying to render it, but that does report the handle correctly. Either way it doesn't explain why it would work for WinForms, unless it's just a race condition.

@h3xds1nz
Copy link
Contributor

h3xds1nz commented Oct 9, 2024

This is becoming a fun problem; I just had PresentationCore.Tests fail on WPF build for that exact same reason. I'm on Win10 and last updates I've installed were in April. The only thing that's new is dotnet and Visual Studio on this machine for the past 3 months.

It uses copy=true in the test.

xUnit.net Console Runner v2.9.0+593e57303e (32-bit .NET 10.0.0-alpha.1.24504.10)
  Discovering: PresentationCore.Tests
  Discovered:  PresentationCore.Tests
  Starting:    PresentationCore.Tests
Unhandled exception. System.Runtime.InteropServices.COMException (0x800401D0): OpenClipboard Failed (0x800401D0 (CLIPBRD_E_CANT_OPEN))
   at System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(Int32 errorCode, IntPtr errorInfo)
   at MS.Internal.SecurityHelper.ThrowExceptionForHR(Int32 hr) in C:\Users\somebody\wpf\src\Microsoft.DotNet.Wpf\src\Shared\MS\Internal\SecurityHelper.cs:line 155
   at System.Windows.Clipboard.Flush() in C:\Users\somebody\wpf\src\Microsoft.DotNet.Wpf\src\PresentationCore\System\Windows\clipboard.cs:line 158
   at System.Windows.Clipboard.CriticalSetDataObject(Object data, Boolean copy) in C:\Users\somebody\wpf\src\Microsoft.DotNet.Wpf\src\PresentationCore\System\Windows\clipboard.cs:line 514
   at System.Windows.Clipboard.SetDataObject(Object data, Boolean copy) in C:\Users\somebody\wpf\src\Microsoft.DotNet.Wpf\src\PresentationCore\System\Windows\clipboard.cs:line 442
   at PresentationCore.Tests.BinaryFormat.DataObjectTests.TestLogic(Object value) in C:\Users\somebody\wpf\src\Microsoft.DotNet.Wpf\tests\UnitTests\PresentationCore.Tests\BinaryFormat\DataObjectTests.cs:line 34
   at PresentationCore.Tests.BinaryFormat.DataObjectTests.<>c__DisplayClass0_0.<DataObject_UnSupportedObjects_SetData>b__0() in C:\Users\somebody\wpf\src\Microsoft.DotNet.Wpf\tests\UnitTests\PresentationCore.Tests\BinaryFormat\DataObjectTests.cs:line 21
   at System.Threading.Thread.StartCallback()

@josefblaha
Copy link
Author

josefblaha commented Nov 4, 2024

In the search of the mysterious clipboard listener, I also tried to call GetClipboardViewer, which is the oldest way to monitor clipboard contents in Win32 API. It should return the HWND of the next viewer in a global clipboard viewer chain. Unfortunately, it returned 0.

@lindexi
Copy link
Member

lindexi commented Nov 5, 2024

I find the tool, https://github.com/walterlv/ClipboardViewer , which can view the Clipboard

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Investigate Requires further investigation by the WPF team.
Projects
None yet
Development

No branches or pull requests

7 participants