dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
7.1k stars 1.17k forks source link

User32.dll RegisterClipboardFormat returns a negative ID #9848

Open vsfeedback opened 1 month ago

vsfeedback commented 1 month ago

This issue has been moved from a ticket on Developer Community.


[severity:It's more difficult to complete my work] I am running into some edge cases in my application where registering a clipboard format returns a negative format ID, so the DataFormat that is created has a negative ID.

When this happens, if I call Clipboard.ContainsData() with my formats, it returns false, even though the formats are in the _formatList and they are on the clipboard.

What causes the format ID to be negative?

image.png


Original Comments

Feedback Bot on 9/19/2024, 06:55 AM:

We have directed your feedback to the appropriate engineering team for further evaluation. The team will review the feedback and notify you about the next steps.

himgoyalmicro commented 1 month ago

Can you please provide a minimal sample repro?

h3xds1nz commented 1 month ago

The ID for the registered format is 0xC079 (RegisterClipboardFormat reserves 0xC000 to 0xFFFF for custom formats).

FORMATETC defines the format IDs as 2 bytes (WORD), RegisterClipboardFormat is using UINT, which well, the typedef used to be 16bit, not 32 as it is now.

Now, since history class is over, WPF defines clipboard format as Int32 because UInt16 wouldn't be CLS compliant is my guess (which wouldn't matter now in case of WPF but earlier they were really scared of unsigned types). InternalGetDataFormat will mask out the top 2 bytes and match only on the two low ones, however the value underlying value is still saved as Int32.

I guess simple solution would be to add a debug display attribute for DataFormat to stop confusing people.

miloush commented 1 month ago

I guess simple solution would be to add a debug display attribute for DataFormat to stop confusing people.

I don't think that's the issue right, the issue is that ContainsData wrongly returns false.

h3xds1nz commented 1 month ago

I might want to read the whole issue instead of checking the image I guess.

Hah, yeah, well, that will happen.

miloush commented 1 month ago

I also believe the CLS compliance is only concerned about publicly exposed types. I don't see why we couldn't change the pinvoke signatures to uint and have only the public DataFormat.ID cast to int.

h3xds1nz commented 1 month ago

Well that's not really the problem, is it. You have 2 high bytes that shouldn't be there.

Formats are just undocumented atoms (ATOM's typedef is WORD - UInt16 mapping), RegisterClipboardFormat is just RegisterWindowMessage, which is just a wrapper for win32u.NtUserRegisterWindowMessage; so it already comes from kernel, because it uses extended (4byte) registers to pass down the value (as you would expect for UInt32).

Now the fun part is that we use RegisterWindowMessage in a lot of places, and never downcasted to UInt16 either, which might create some fun bugs if something like this actually happens. I can't force it.

Eitherway, in my mind, the solution is to just define the PInvokes as UInt16 that deal with atoms, since you cannot get more usable bit width out of them.

Regarding CLS compliance you're correct afaik, I also mentioned that because of DataFormat being a public type hence Id being an int.

miloush commented 1 month ago

I am not very keen on declaring PInvokes using different parameter types than they are documented/declared in native code and relying on undocumented assumptions. We could also just fix the number when calling ContainsData (although since the ctor accepts int and is public, we should probably not do that). Either way, can we have a repro? I cannot reproduce it easily.

h3xds1nz commented 1 month ago

Well, I wouldn't say undocumented. If you go onto Atom Tables overview (https://learn.microsoft.com/en-us/windows/win32/dataxchg/about-atom-tables); you're gonna get a whole portion of how it is an atom (and its true, in kernel they're stored alongside).

Yeah I wouldn't apply the fix onto ContainsData tbh. But the repro is something I wanna see because it doesn't make sense. My guess is there's some detour in the call-chain and that's how those two bytes got returned to us in the first place.