dotnet / winforms

Windows Forms is a .NET UI framework for building Windows desktop applications.
MIT License
4.41k stars 982 forks source link

.NET 9 appears to break Windows API clipboard functionality #12184

Closed mfeemster closed 1 month ago

mfeemster commented 1 month ago

Description

Using the Windows API call SetClipboardData() to send data to the clipboard works fine with a project target framework of net8.0-windows. However, when I change the project to 'net9.0-windows', the same code fails to properly set the clipboard data.

I've read that Windows is very picky under the hood about locking memory when using the clipboard, but it seems to work fine on .NET 8 regardless. So I am wondering if .NET 9 changed how things are done with system memory or perhaps made the rules more strict? If so and this was intentional, this should be included in the release notes/what's new documents.

Reproduction Steps

Project file:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net9.0-windows</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <LangVersion>12</LangVersion>
    <RunAnalyzersDuringBuild>False</RunAnalyzersDuringBuild>
    <EnableNETAnalyzers>False</EnableNETAnalyzers>
    <UseWindowsForms>true</UseWindowsForms>
  </PropertyGroup>

</Project>

Code for Program.cs:

using System.Runtime.InteropServices;

namespace CsConTest
{
    internal class Program
    {
        [DllImport("user32.dll")]
        internal static extern bool CloseClipboard();

        internal static object GetClipboardData()
        {
            if (OpenClipboard(IntPtr.Zero))
            {
                _ = CloseClipboard();

                if (Clipboard.GetData(DataFormats.Text) is string text)
                    return text;

                if (Clipboard.GetData(DataFormats.Html) is string html)
                    return html;

                if (Clipboard.GetData(DataFormats.Rtf) is string rtf)
                    return rtf;

                if (Clipboard.GetData(DataFormats.SymbolicLink) is string sym)
                    return sym;

                if (Clipboard.GetData(DataFormats.UnicodeText) is string uni)
                    return uni;

                if (Clipboard.GetData(DataFormats.OemText) is string oem)
                    return oem;

                if (Clipboard.GetData(DataFormats.CommaSeparatedValue) is string csv)
                    return csv;

                if (Clipboard.GetData(DataFormats.FileDrop) is string[] files)
                    return string.Join(Environment.NewLine, files);
            }

            return "";
        }

        [DllImport("user32.dll")]
        internal static extern bool OpenClipboard(IntPtr hWndNewOwner);

        [DllImport("user32.dll")]
        internal static extern bool SetClipboardData(uint uFormat, IntPtr data);

        [System.STAThreadAttribute()]
        private static void Main(string[] args)
        {
            Clipboard.Clear();
            var isOpen = OpenClipboard(IntPtr.Zero);
            var ptr = Marshal.StringToHGlobalUni("Hello World!");
            var isSet = SetClipboardData(13, ptr);
            var isClosed = CloseClipboard();
            Marshal.FreeHGlobal(ptr);
            var val = GetClipboardData();//Returns "Hello World!" on .net 8 and "" on .net 9

            if (val.ToString() == "Hello World!")
                Console.WriteLine("pass");
            else
                Console.WriteLine("fail");
        }
    }
}

Expected behavior

The value copied to the clipboard and read back from it is "Hello World!" on both .NET 8 and 9.

Actual behavior

The value copied to the clipboard and read back from it is "Hello World!" for .NET 8 and the empty string "" for .NET 9.

Regression?

It worked in .NET 8.

Known Workarounds

None.

Configuration

.NET 8 and 9 Windows 10 x64 Visual Studio 2022 Community edition 17.11.4

Other information

No response

jkoritzinsky commented 1 month ago

cc: @JeremyKuhne can you think of any changes in WinForms that may have broken this?

JeremyKuhne commented 1 month ago

@lonitra can you look into this and see if this is on our end?

lonitra commented 1 month ago

It seems as though we are incorrectly clearing the clipboard. Documentation mentions to clear clipboard we should be calling OleSetClipboard(null), but this is not what is happening in our Clipboard.Clear(). Currently it is unclear why this had worked in .NET 8, but correcting this behavior looks to produce expected results.

@mfeemster Could you try replacing calls to Clipboard.Clear() with the following code and see if there is any other issues?

        if (Application.OleRequired() != ApartmentState.STA)
        {
            throw new ThreadStateException();
        }

        int hresult;
        int retry = 10;
        while ((hresult = OleSetClipboard(null)) != 0)
        {
            if (--retry < 0)
            {
                // clipboard is being used by something else
                throw new InvalidOperationException();
            }

            Thread.Sleep(millisecondsTimeout: 100);
        }

where OleSetClipboard is:

    [DllImport("ole32.dll", ExactSpelling = true)]
    public static extern int OleSetClipboard(System.Runtime.InteropServices.ComTypes.IDataObject? pDataObj);

We plan to change our Clipboard.Clear() to similar code above after some more investigation as to why it had worked in the past.

mfeemster commented 1 month ago

Thanks for the code sample. Yes, if I either do not call Clipboard.Clear() or if I use the code you provided in my own clearing function, then everything works fine.

So there is something within Clipboard.Clear() that is either affecting, or being affected by a change that was made in .NET 9.

Zheng-Li01 commented 1 month ago

Verified the issue with .NET SDK 9.0.100-rc.2.24475.2 build version, the issue has been fixed that the value copied to the clipboard and read back from it is "Hello World!" on .NET 9 as below screenshot. image

Liv-Goh commented 1 month ago

Verified this issue in the latest .NET 9.0 SDK build: 9.0.100-rc.2.24475.2 + dlls built from Winforms repo main branch, the issue has been fixed that the value copied to the clipboard and read back from it is "Hello World!" on .NET 9 as below screenshot.

image
mfeemster commented 1 month ago

@lonitra This remains broken in .NET 9 RC 2. Was it included as part of that release?

lonitra commented 1 month ago

The fix is included in 9.0 GA. This did not make it in time for RC2.

Philip-Wang01 commented 2 weeks ago

Verified this issue with .NET 9.0 GA test pass .NET SDK 9.0.100 build, the issue has been fixed that the value copied to the clipboard and read back from it is "Hello World!" on .NET 9 as below screenshot. Image