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

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

Open
mfeemster opened this issue Sep 19, 2024 · 5 comments
Open

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

mfeemster opened this issue Sep 19, 2024 · 5 comments
Assignees
Labels
area-Clipboard Issues related to Clipboard 💥 regression-preview Regression from a preview release
Milestone

Comments

@mfeemster
Copy link

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

@dotnet-policy-service dotnet-policy-service bot added the untriaged The team needs to look at this issue in the next triage label Sep 19, 2024
Copy link
Contributor

Tagging subscribers to this area: @dotnet/interop-contrib
See info in area-owners.md if you want to be subscribed.

@jkoritzinsky
Copy link
Member

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

@JeremyKuhne
Copy link
Member

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

@JeremyKuhne JeremyKuhne transferred this issue from dotnet/runtime Sep 19, 2024
@JeremyKuhne JeremyKuhne added the 💥 regression-preview Regression from a preview release label Sep 19, 2024
@JeremyKuhne JeremyKuhne added this to the 9.0 RTM milestone Sep 19, 2024
@JeremyKuhne JeremyKuhne added area-Clipboard Issues related to Clipboard and removed untriaged The team needs to look at this issue in the next triage labels Sep 19, 2024
@lonitra
Copy link
Member

lonitra commented Sep 20, 2024

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
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-Clipboard Issues related to Clipboard 💥 regression-preview Regression from a preview release
Projects
Status: No status
Development

No branches or pull requests

4 participants