-
Notifications
You must be signed in to change notification settings - Fork 976
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
Removed CA1838 warning suppressions on p/invokes for StringBuilders. #4751
Conversation
src/System.Windows.Forms.Primitives/src/Interop/Shlwapi/Interop.SHLoadIndirectString.cs
Outdated
Show resolved
Hide resolved
src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Internals/UnsafeNativeMethods.cs
Outdated
Show resolved
Hide resolved
src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Internals/UnsafeNativeMethods.cs
Outdated
Show resolved
Hide resolved
src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Internals/UnsafeNativeMethods.cs
Outdated
Show resolved
Hide resolved
src/System.Windows.Forms.Primitives/src/Interop/Shell32/Interop.DragQueryFileW.cs
Outdated
Show resolved
Hide resolved
src/System.Windows.Forms.Primitives/src/Interop/Shell32/Interop.DragQueryFileW.cs
Outdated
Show resolved
Hide resolved
src/System.Windows.Forms.Primitives/src/Interop/Kernel32/Interop.FormatMessageW.cs
Outdated
Show resolved
Hide resolved
You cannot change |
src/System.Windows.Forms.Primitives/src/Interop/Shell32/Interop.DragQueryFileW.cs
Outdated
Show resolved
Hide resolved
src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Internals/UnsafeNativeMethods.cs
Outdated
Show resolved
Hide resolved
src/System.Windows.Forms.Primitives/src/Interop/Kernel32/Interop.FormatMessageW.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
None of these API must have a StringBuilder
as an input parameter, instead it's likely be a char*
.
Each callsite must be reviewed individually, and updated to correctly allocate buffers either by stackalloc char[...]
or by ArrayPool<char>.Shared.Rent(...)
.
Please consider how each of the changes will be tested. |
Alright |
f0bac04
to
f5fe213
Compare
e60c786
to
a49928b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might or might not be related to test failures, but I don't like the usage of Trim
here
- its used in cases where the Windows API should be returning the exact size, why is it trimming in those cases?
Trim
also removes leading zero bytes which makes no sense at all, maybe useTrimEnd
instead?- In practice this probably only leads to errors when there is garbage behind an "empty" string, you'd trim the leading zero byte indicating the empty string and return the garbage behind it instead. Which leads to the next point:
- it has massively different semantics from C, which looks for the first zero byte. Removing trailing (not leading) zero bytes often is equivalent if you can guarantee that there is no garbage beyond the first zero byte. I'm not sure if all Windows APIs do guarantee that (its not in the contract and if Windows write a longer string into the buffer, then shortens it, there may be garbage beyond the first zero byte, which you would expose because you only trim trailing zero bytes)
- the way you're using it allocates a string and then allocates another string if trimming is necessary - this could be optimized by trimming the span before converting it into a string
I know I'm being nitpicky here, since in practice it probably always works, but why take risks on edge case failures by performing an operation which is more convenient but having the wrong semantics? If the cost of doing it "correctly" is too high I'd suggest doing a TrimEnd('\0')
on the span (not the string) as compromise.
That said, fixing any bugs triggering test failures is probably more important than going for style issues about Trim
So all of these including (well except for |
yes:
|
src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Internals/UnsafeNativeMethods.cs
Outdated
Show resolved
Hide resolved
src/System.Windows.Forms/src/System/Windows/Forms/ComboBox.AutoCompleteDropDownFinder.cs
Outdated
Show resolved
Hide resolved
....Windows.Forms/src/System/Windows/Forms/ComponentModel/COM2Interop/COM2PropertyDescriptor.cs
Outdated
Show resolved
Hide resolved
src/System.Windows.Forms/src/System/Windows/Forms/InputLanguage.cs
Outdated
Show resolved
Hide resolved
Yeah, I think I should migrate this to use the DllImport generator added in .NET 7 so It can help me with passing spans into the signatures of the p/invokes. |
@RussKie would the team be okay using: https://github.com/microsoft/CsWin32 ? It reduces the amount of code considerably. Related: #4136 |
IIRC, we'd discussed https://github.com/microsoft/CsWin32 and https://github.com/terrafx/terrafx.interop.windows (and maybe some other option) with @JeremyKuhne, and arrived to a conclusion that it was undesirable for few reasons. One is that it'd introduce external dependencies, which we'd have to ship (with all related issues such as payload size, packaing, signing, etc). Also, we strive to import only those definitions that we use (i.e., less code - less to compile - less to jit, etc.). And lastly, some of our imports are optimised (e.g., we use As per #4136 we do plan to use COM source generators when those are ready (.NET 8?), and replace the manual COM wrappers that @kant2002 has kindly built for us in .NET 7. |
Technically what is unused in TerraFX.Interop.Windows can be trimmed, however how it is currently the WindowsDesktop profiles both do not support trimming at all yet to provide such a space savings. Also as for external dependencies, wasn't TerraFX made a member of the DNF despite it staying in it's own github organization (it might be me misremembering and it being ClangSharp instead)? If all else fails, winforms and wpf could manually use clangsharp instead (for the COM if that is supported in it) if that route is desired as well. |
@AArnott - does using cswin32 result in a shippable dependency or is it just design/build time? Also would adding optimisations such as IHandle be possible? @RussKie - as far as I can tell cswin32 only generates the definitions you use. which would keep the size down. Only problem I see would be relying on the CSWin32 team to update any bad definitions. But I think that they are keen to keep it updated. |
@elachlan CsWin32 generates code directly into your compilation. The only (recommended) runtime dependency it adds is to System.Memory, which most compilations already have anyway. CsWin32 is opt-in per-API. If you ask for I'd love to see WinForms replace their interop code with CsWin32-generated code and would be happy to support you in doing so. There are bugs in some of the generated code, but we're working them down in a priority order and I think there are only a few left that don't block adoption except for those APIs that are impacted. I recommend folks start migration by adding a CsWin32 package reference and replace just a few APIs with generated ones to get a feel for it, then migrate gradually over time, so if you do need any of the APIs that may still be generated incorrectly, it only blocks adoption for those particular APIs, and we'd be happy to hear feedback on anything blocking. CC: @RussKie |
I'm personally for using CsWin32 instead of manually looking at the docs and writing PInvokes. I think that work may not start earlier then .NET 8 unless somebody really motivated can reduce risks by explaining how that would not be an issue. Practical matters:
|
Tagging @tannergooding as we're talking about TerraFX here as well. We have a few design principles we're trying to achieve with our interop code:
We're trying to find the right balance between making things fast and pushing contributors to the pit of success. If we could generate nearly the same interop surface area with a library that falls under Microsoft's ownership or the .NET Foundation I'd be very keen on making the jump for .NET 8. For CsWin32 I think that would mean:
@AArnott I'm happy to talk more about it with you whenever you want. cc: @merriemcgaw |
No. That is how the dotnet/pinvoke project divided it too, but CsWin32 uses namespaces from win32metadata that the SDK PM (@mikebattista) worked on for quite a while. All the pinvoke extern methods go into a single PInvoke class, and the types that support them are divided across the namespaces based on use case. This made the functions much easier to find for many customers that couldn't otherwise figure out that, for example, when they asked for CreateFile that would go off to some namespace and class while another method would go elsewhere. Folks in C++ don't tend to have to think about what module a function is exported from, so in C# we didn't require them to think about that either.
It's there for you to try out. Or we could set up a meeting to review it together. As I mentioned earlier, we already have fairly low overhead, and I suspect the perf is comparable with the source generator you mentioned except perhaps when using AOT, and I don't know whether that is applicable to WinForms.
I'd have to see
CsWin32 can do that with a switch. In which case absolutely no marshaling occurs at all, and there are no SafeHandles. I would expect it to have better performance than LibraryImportGenerator which as I understand it still does marshaling... just in your own library code instead of leaving it to the runtime.
That's an interesting idea that we've only spent a few minutes talking about. I'd like to learn more about where/when/how you would use this. Maybe CsWin32 can emit types to help you with this.
We made a design decision to emit APIs that exactly match those found in the docs and header files. This is optimized to make it easy to translate C++ code to C# and so that win32 API docs match your code, allowing you to translate and jump either direction. Trimming prefixes from enum values for example would make it much more difficult to jump around since symbols would have different names.
I'm not sure what you mean by this. When you turn marshaling off in CsWin32, any COM interfaces that CsWin32 generates are struct pointers instead of interfaces, which of course is much less convenient but completely avoids RCWs and the GC/finalization pressure that comes from that. Is that what you're looking for?
You mean you want to suppress the friendly overloads? I could certainly see us adding that as an option if that's important to you.
We offer |
I'm playing around with your repo and seeing how |
Here's an idea of what CsWin32 use in WinForms would look like.
Not bad. Just a few APIs moving over drops 194 lines of code from your repo, and deletes several files. |
That looks great, I wonder what all the other apis used would look like (except the ones that are currently bugged). |
That is an impressive reduction. I think the positive here is that Winforms offloads a lot of the pinvoke interop effort to a project which is being used by many other projects. This would hopefully help improve the interop code more than doing it ourselves. |
@JeremyKuhne CsWin32 can help with #4135 as well. |
@AArnott, @kant2002 is very interested in NativeAOT for performance and all their work is centred around enabling that. Would you be able to expand on the performance implications you were eluding to here? |
Well, in the default CsWin32 mode where marshaling is allowed, CsWin32 can generate functions that take structs as parameter types, which .NET will then potentially need to marshal, and do so within its own interop layer that I guess doesn't work in all runtime modes (e.g. AOT + trimming). It may be slower too, I don't know. |
@AArnott form what I read bringing CsWin32 will negate small perf gains here and there across codebase. Each bullet points of @JeremyKuhne response is small decision which maintained across codebase for perf reasons, so that's unlikely they can be reversed.
AOT works just fine with most marshaling except marshaling COM interfaces and marshaling COM objects inside VARIANT and SAFEARRAY. Would be curious to know does some primitives for VARIANT and SAFEARRAY provided so. Also while we talk about AOT, reflection in AOT supported, that's common misconception to not talk about it.
For this repo, that's explicit decision which maintained by the team and contributors as well. That allow WinForms easier jump on board with runtime innovations/refactoring like https://docs.microsoft.com/en-us/dotnet/standard/native-interop/disabled-marshalling and being close to supporting And obviously perf reasons plays big role here. so I would not dismiss these concerns.
I think item with "some work" is what I can help you with, at least I would like evaluate options, and take easiest task which will help this project. Also if you have some simple, or not so simple showcase project which I can git clone and play with it, that would help me to better understand on what I sighing up for. 😄
At least for me, having all native dependencies listed in single folder help with my ComWrappers work massively, since I can learn about dependencies easier. C# for me is all about easier learning of code structure, and having one big class looks like not very appealing. I do agree that knowing that CreateFile will go to some magic namespace is probably unintuitive, but what is namespace in which generate code can be derived from folder where TXT file store, or via additional Item metadata. That's just throwing ideas without really knowing how CsWin32 work. |
@kant2002: thanks for your offer to help and your thoughts on AOT, COM, etc. I'm learning here and your experience is helping.
Fortunately the only thing that "one big class" is relatively simple (extern methods, friendly overloads, and constants). That tends to me a minority of the generated code, since structs, enums and interfaces get generated into separate types and files. Also, I'm pretty sure we at least generate each module's methods into different files via partial classes, so that may help with your review of the code as well. We tried supporting splitting this out on a class-per-module basis initially, but that design conflicted with some other very real requirements so I don't think we'll backtrack on that decision unless there's a firm requirement that isn't being met with the current design.
Are you asking to play with a project that is already using CsWin32 so you can get a feel for what it's like? If so, here's a PR I found of another repo adopting CsWin32: microsoft/vs-extension-testing#92. Does that help? Or if you wanted to try actually adopting CsWin32 in a small repo, I might be able to find a good candidate for you. |
@AraHaan did you want to reroll this against the current branch? |
I could yeah. However I will need to reclone as the original clone on my pc got deleted automatically. |
Since this was implemented, I have closed this. |
Fixes #3983
Proposed changes
Customer Impact
Regression?
No
Risk
Test methodology
TODO: Test.
Test environment(s)
(used my browser on my mac this time to make changes)
Note: the code to the StringBuilders was attempted to try to be a minimal change, however it may require changing all the callsites too and simply not having StringBuilder parameters for the methods that wrap the actual p/invokes, this is due to the fact that StringBuilder was used for the buffers instead of string.
Also VB still needs done on it however.
This is opened as a draft because I think it may require a bit more work and testing on a windows system.
Microsoft Reviewers: Open in CodeFlow