-
Notifications
You must be signed in to change notification settings - Fork 992
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
Consider using structs to wrap simple native typedefs #2069
Comments
First off, I'm in favor of using wrapper structs if using an enum is no longer good enough for the needs of the WinForms project. WPF already uses them so there is precedence in the framework. However WinForms has a much larger interop surface than WPF and I'd like to ensure that they actually will work in all relevant cases.
Technically this isn't true, which was discovered after a bugfix was made, which started crashing WPF usage of struct wrappers. This has lead to a technical discussion which highlighted that the native calling conventions for struct returns differ depending on whether the method has an implicit this parameter. Primitive(__stdcall SomeInterface::*SomeMethod)(); // returns value in register
Primitive(__stdcall *SomeMethod)(SomeInterface*); // returns value in register
SomeStruct(__stdcall SomeInterface::*SomeMethod)(); // returns struct by writing to caller provided reference
SomeStruct(__stdcall *SomeMethod)(SomeInterface*); // returns struct in register This means if CoreCLR were implemented correctly then struct wrappers don't work in COM interfaces, only in P/Invokes. It was not investigated in depth if struct wrappers in other positions, other calling conventions, or other platforms than Windows x86/x64 would cause any incompatibilities, since all those cases were out of scope as far as the WPF usage was concerned. So while the particular scenario of struct wrappers as return values in Windows x86/x64 has ensured compatibility the assumption that struct wrappers are equivalent is based on a bug, it is unclear if it holds in other scenarios. I'd like to point out two particular risks going forward without checking this:
|
It is true from the .NET Framework perspective. As you pointed out, even though it is technically incorrect, the runtime could never change their treatment of these as it would essentially break the world. What would likely happen is that they would add additional modifiers that allow you to get structs to pass per spec. For the record I was the source of the break you mentioned as I stumbled across the fact that I couldn't wrap some COM interfaces correctly and requested the original "fix". :) (COM interfaces that returned a struct rather than an HRESULT).
They work fine as parameters (by ref or otherwise). I use structs in my WInterop library, which has several hundred P/Invokes.
It would break a ton of apps (as you've pointed out with WPF). The runtime will keep compatibility- there really is no choice. |
I'm going to assert that using structs would be the best choice as it would improve encapsulation and "type safety". This came up again with #2218, where we really should have a |
Generally I'm ok with this, but I'd like to point out that its not been clearly answered on whether this will work for ARM64. Would be nice if this could be verified. I only could check the native side, the C/C++ ABIs seem the have the same difference of how structs are passed, I can't test if CoreCLR will generate the right assembly code for everything to work out. The workaround in CoreCLR doesn't look like it would be x86/x64 specific but it would be nice to make sure. That said its not something to block on, if it can't be tested now I'm fine with moving it either into #2053 or a separate issue. |
We will be getting access to ARM64 hardware in near to medium term future, so we should be able to test the hypothesis. |
Another valuable thing we could get out of "structifying" type definitions is that we could guard against sign extension problems with handles. See #2004 (comment) and the bug fix with #2736. An |
Should we close this since winforms is now moving to CsWin32? |
Yes, thank you. |
We've been using enums to wrap
typedef
s from Windows. For example:We're wrapping as:
While enums are more directly aligned with
typedef
, they don't provide other advantages that C# can provide, such as implicit operators. This can be useful in the case ofBOOL
, where checkingif (b == BOOL.TRUE)
is not correct as anything other thanBOOL.FALSE
is true.We mitigate this somewhat with extension methods and copious comments, but it still makes for less than ideal consumption.
if (SomeMethod().IsTrue())
isn't natural.Using a struct with a single internal field is functionally equivalent from an interop perspective. It allows you to use operators with can make code much easier to write:
The downsides are:
enum
, but the difference is trivialUpsides are:
Another thing that is possible with structs that isn't with enums is wrapping pointers. Notably things like
HWND
,HBITMAP
, etc. Using structs can allow us to strongly type our APIs:With pointer size objects added to the mix I'd argue that using structs for
typedef
is the best pattern to be using in WinForms.Discussed here.
cc: @hughbe, @zsd4yr, @RussKie, @weltkante
The text was updated successfully, but these errors were encountered: