Start using CsWin32
Is your feature request related to a problem? Please describe
This issue is to be used to help centralize information around the change to implement CsWin32.
Blockers: ~~https://github.com/microsoft/CsWin32/issues/639~~ ~~https://github.com/microsoft/CsWin32/issues/605~~ https://github.com/microsoft/CsWin32/issues/600 https://github.com/microsoft/CsWin32/issues/644 ~~https://github.com/microsoft/CsWin32/issues/624~~ ~~https://github.com/microsoft/CsWin32/issues/625~~ https://github.com/microsoft/CsWin32/issues/324 https://github.com/dotnet/runtime/issues/59013
Performance: https://github.com/microsoft/CsWin32/issues/595
Useful: https://github.com/microsoft/CsWin32/issues/596 ~~https://github.com/microsoft/CsWin32/issues/606~~ https://github.com/microsoft/CsWin32/issues/602 https://github.com/microsoft/CsWin32/issues/610 https://github.com/microsoft/CsWin32/issues/122 ~~https://github.com/microsoft/CsWin32/issues/626~~
Associated Issues/Discussions: https://github.com/dotnet/winforms/pull/4751 ~~https://github.com/dotnet/winforms/pull/7392~~ ~~https://github.com/dotnet/winforms/pull/7444~~ ~~https://github.com/dotnet/winforms/pull/7446~~ https://github.com/dotnet/winforms/issues/7468
@JeremyKuhne I have created an issue here to help centralize issues/information around CSWin32.
Before anything gets too deep, the Winforms team should decide on how they want to split up the NativeMethods.txt file.
Before anything gets too deep, the Winforms team should decide on how they want to split up the NativeMethods.txt file.
Not sure how we'd want to split this up in ways that make sense given everything going to one class. Any ideas are certainly welcome.
Note that this is currently a prototype effort targeting .NET 8. We have an intern working on this (@fernandanavamoya) with support from myself and our returning @lonitra.
I have asked the CsWin32 team about the NativeMethods.txt in https://github.com/microsoft/CsWin32/issues/606.
AFAIK, each NativeMethods.txt file needs to be in its own folder and added to additionalfiles. So in the context of winforms, we can place a NativeMethods.txt in each library sub folder. That way each library has its own NativeMethods.txt.
Another way is have a single file, but add a comment "header" for each separate library.
I like the idea of separate files as the management is per library.
Its even more simple if winforms is using every method in a library, we can add them via a single wildcard line. Although, I am unsure if Trimming could then remove the used methods afterwards.
@JeremyKuhne do you want all the generated code in one file? If not, you can use the following in NativeMethods.json
{
"$schema": "https://aka.ms/CsWin32.schema.json",
"emitSingleFile": false
}
Personal opinion: I like how we have separation per containing dll (i.e., Interop.User32, Interop.Gdi32, etc). It'd be great to retain the separation if possible, e.g., /User32/NativeMethods.txt, /Gdi32/NativeMethods.txt, /Kernel32/NativeMethods.txt, etc.
AFAIK, each NativeMethods.txt file needs to be in its own folder and added to additionalfiles. So in the context of winforms, we can place a NativeMethods.txt in each library sub folder. That way each library has its own NativeMethods.txt.
👍
Personal opinion: I like how we have separation per containing dll (i.e., Interop.User32, Interop.Gdi32, etc). It'd be great to retain the separation if possible, e.g., /User32/NativeMethods.txt, /Gdi32/NativeMethods.txt, /Kernel32/NativeMethods.txt, etc.
I'll proceed on this basis. It seems easier to deal with.
So @AArnott suggests we keep a centralized NativeMethods.txt and not split it up. The splitting up was designed for shared project scenarios. Which makes sense.
I will adjust my PR to move it back to the main NativeMethods.txt as this will allow better management.
I compiled the project with the whole list of methods I put together, without any code changes. I get 4 of these warnings:
warning PInvoke005: This API is only available when targeting a specific CPU architecture. AnyCPU cannot generate this API.
The Associated APIs:
GetClassLongPtrWGetWindowLongPtrWSetClassLongPtrWSetWindowLongPtrW
I believe in these cases, we have just checked either IntPtr.Size = 4 or !Environment.Is64BitProcess, then call the associated function.
public static IntPtr SetWindowLong(IntPtr hWnd, GWL nIndex, nint dwNewLong)
{
if (!Environment.Is64BitProcess)
{
return SetWindowLongW(hWnd, nIndex, dwNewLong);
}
return SetWindowLongPtrW(hWnd, nIndex, dwNewLong);
}
@AArnott do you have a suggestion here? Is there an escape hatch available?
Edit: I guess we just remove them from generation by CSWin32 and manage our own PInvoke? which isn't ideal.
@elachlan I appreciate that you want to help here and do want your help, but I'd ask that you hold off for a week or so while we get things spun up on our end. I'll ping you when we're ready if that is ok.
Not a problem. I have been mostly probing for issues at this stage. So hopefully that helps in establishing everything. I will hold off on any additional work for now.
Here is a link to a discussion around the the 32bit/64bit based APIs. https://github.com/microsoft/CsWin32/discussions/592 https://github.com/microsoft/CsWin32/issues/528
@AArnott if I read it correctly, the CsWin32 guideline is to commit to a specific bitness, which may work for a end-user app but doesn't work for for an SDK (our case). Windows Forms and WPF binaries are compiled as AnyCPU and flow through to Windows Desktop, where they get cross-compiled for the target bitness.
We also have situations where struct packing is different between bitness, e.g.: https://github.com/dotnet/winforms/blob/07747de084aa403be88bf309baf9b3516e00b76a/src/System.Windows.Forms.Primitives/src/Interop/Comdlg32/Interop.PrintDlgW.cs#L11-L12 https://github.com/dotnet/winforms/blob/07747de084aa403be88bf309baf9b3516e00b76a/src/System.Windows.Forms.Primitives/src/Interop/Comdlg32/Interop.PrintDlgW.cs#L44-L45 https://github.com/dotnet/winforms/blob/07747de084aa403be88bf309baf9b3516e00b76a/src/System.Windows.Forms.Primitives/src/Interop/Comdlg32/Interop.PrintDlgW.cs#L88-L89
do you want all the generated code in one file? If not, you can use the following in NativeMethods.json
@elachlan The default is not one file, so you don't need this setting to turn it off. Only to opt in to single file generation.
warning PInvoke005: This API is only available when targeting a specific CPU architecture. AnyCPU cannot generate this API.
@elachlan This has come up a few times. I would recommend that WinForms actually target specific CPU architectures, at least for its runtime assemblies. That will give it access to the broadest Win32 API surface area. While sometimes you can do quick workarounds with a runtime pointer-size check, that isn't reliable generally, since structs can sometimes be declared very differently across architectures. For ref assemblies, AnyCPU assemblies is really important so customers can continue to build AnyCPU WinForms apps.
doesn't work for for an SDK (our case). Windows Forms and WPF binaries are compiled as AnyCPU and flow through to Windows Desktop, where they get cross-compiled for the target bitness.
@RussKie as you point out, structs themselves vary by bitness. It's not always as simple as a Pack parameter. And while you can sometimes get away with declaring two structs, and doing your own runtime check to know which to use, sometimes the struct in question is deep in a type hierarchy where you'd have to define several more (otherwise AnyCPU) structs twice in order to enable the runtime check. This process is error prone and tedious to code around. It's much easier to just target multiple architectures. As I mention above, you can still compile AnyCPU ref assemblies for your SDK. And as you mention, eventually your runtime crossgen's the runtime assemblies into being arch-specific anyway, so it's not really that you're AnyCPU.
We don't package or release directly, and our binaries follow an established flow through several layers (dotnet/winforms->dotnet/wpf->dotnet/windowsdesktop->...). So we can't target specific bitness, as it would require us to completely reengineer all our build, test, packaging and release pipelines. This is simply unattainable.
Ok. Then you'll have to keep the arch-specific APIs hand-authored.
I'm officially going to tag this for .NET 9, for whatever bits remain after .NET 8. I think we're past where we want to do more in this release, but let's keep going! 😄
Is the plan to convert the remaining Ole32 functions and Shell32 interfaces to CsWin32?
@willibrandon it's been slowly happening. I think it's a lot of com wrapper stuff left.
@elachlan - I'm curious what that will look like, and will keep an eye out, thanks.