Skip to content

Add GetLParam<T> and GetLParamRef<T>#835

Closed
sharwell wants to merge 12 commits intodotnet:masterfrom
sharwell:getlparam
Closed

Add GetLParam<T> and GetLParamRef<T>#835
sharwell wants to merge 12 commits intodotnet:masterfrom
sharwell:getlparam

Conversation

@sharwell
Copy link
Contributor

@sharwell sharwell commented Apr 19, 2019

This pull request implements the new GetLParam<T> and GetLParamRef<T> (#792) as internal methods, and uses them to avoid marshaling overhead for existing value types.

The first commit is shared with #818.

@sharwell sharwell requested a review from a team as a code owner April 19, 2019 21:13
@danmoseley
Copy link
Member

cc @JeremyKuhne

@zsd4yr
Copy link
Contributor

zsd4yr commented Apr 22, 2019

honestly LGTM; I don't really understand the implications however...

@JeremyKuhne
Copy link
Member

@jkotas, @stephentoub, @jkoritzinsky Any feedback on using this ref T approach versus unsafe and T* (i.e. ->) to access struct members in native data? Are there any unobvious pro/cons? Thanks!

@jkotas
Copy link
Member

jkotas commented Apr 23, 2019

I think it is better to stick to regular unmanaged pointers for interop. It makes the intent much more obvious. For example, I do think that the following change is an improvement?

NativeMethods.WINDOWPOS* wp = (NativeMethods.WINDOWPOS *)m.LParam;

to

ref NativeMethods.WINDOWPOS wp = ref m.GetLParamRef<NativeMethods.WINDOWPOS>();

@danmoseley
Copy link
Member

@sharwell do you plan to recast this in terms of pointers, as requested by @jkotas above?

@sharwell
Copy link
Contributor Author

@danmosemsft I'll take a look Monday

@sharwell
Copy link
Contributor Author

sharwell commented Apr 28, 2019

@sharwell do you plan to recast this in terms of pointers, as requested by @jkotas above?

I investigated this change this morning, and my conclusion is I do not believe the change leads to an improved outcome. There is no pointer representation equivalent to ref readonly, and I view the readonly constraint as a valuable restriction on usage.

Managed references also does a better job of restricting the locations where unsafe operations are used.

@codecov
Copy link

codecov bot commented Apr 28, 2019

Codecov Report

Merging #835 into master will increase coverage by 0.00387%.
The diff coverage is 16.58537%.

@@                 Coverage Diff                 @@
##              master        #835         +/-   ##
===================================================
+ Coverage   28.83584%   28.83972%   +0.00388%     
===================================================
  Files           1078        1078                 
  Lines         293919      293921          +2     
  Branches       38408       38408                 
===================================================
+ Hits           84754       84766         +12     
+ Misses        205131      205118         -13     
- Partials        4034        4037          +3
Flag Coverage Δ
#Debug 28.83972% <16.58537%> (+0.00387%) ⬆️
#production 18.75421% <16.58537%> (+0.00452%) ⬆️
#test 98.68151% <ø> (ø) ⬆️

@jkotas
Copy link
Member

jkotas commented Apr 29, 2019

Conclusion is I do not believe the change leads to an improved outcome.

It is matter of personal preference. Here is a quick summary of pros and cons:

Managed references:

  • More overhead (e.g. each instantiation of GetLParamRef is several hundred bytes of memory, conversions from pointers to byrefs turn the GC tracking on that increases size of GCInfo and generates worse code here and there because of that)
  • Unsafe is hidden.
  • Able to use readonly.

Unmanaged pointers:

  • Leaner and faster
  • Unsafe is explicit. The unsafe code looks more like C/C++, less like C#.
  • Unable to use readonly.

I prefer leaner and faster code with explicit unsafe because of it is much easier to see that is going on. I believe it is also where the CoreCLR/CoreFX repos are leaning towards.

If other folks prefer the opposite and understand consequences of such preference, I do not have a problem with it. Ultimately, it is up to the WinForms maintainers to make the choice.

@sharwell
Copy link
Contributor Author

sharwell commented Apr 29, 2019

I believe the perceived magnitude of this downside differs between us:

... The unsafe code looks more like C/C++, less like C#.

The pointers don't represent the way I would write (or suggest others write) C#. It doesn't mean it can't be done that way (it's a matter of style, not correctness), but if managed references aren't a possibility here it would likely save a bunch of time to go ahead and close this.

More overhead (e.g. each instantiation of GetLParamRef is several hundred bytes of memory, conversions from pointers to byrefs turn the GC tracking on that increases size of GCInfo and generates worse code here and there because of that)

I would expect this to improve over time as managed references become the preferred style for users.

@JeremyKuhne
Copy link
Member

I prefer leaner and faster code with explicit unsafe because of it is much easier to see that is going on.

I agree with this when it comes to direct interop with native code, particularly at these low levels of the stack. I believe there is value in giving uplevel code the best performance possible. On top of performance, the obfuscated "unsafeness" concerns me when it comes to long term maintenance. You can mitigate that somewhat by liberally putting Unsafe in methods that do unsafe things. If WinForms is ok with using ref instead I would highly recommend using System.Runtime.CompilerServices.Unsafe.AsRef directly (or at worst, remove the indirection of GetLParamRef<T> or get "Unsafe" somewhere in the name).

I would expect this to improve over time as managed references become the preferred style for users.

Quite possibly, but we should be very cautious about taking hits based on potential future improvements.

Ultimately, it is up to the WinForms maintainers to make the choice.

For the record, that isn't me. I'm on the CoreFx team, and I'm here because I've been spending quite a bit of time on interop and I also am very interested in seeing interop improved in WinForms and WPF.

@sharwell
Copy link
Contributor Author

You can mitigate that somewhat by liberally putting Unsafe in methods that do unsafe things.

I wouldn't be opposed to this. We can't use Unsafe.AsRef directly because it doesn't have an overload that takes an IntPtr.

@JeremyKuhne
Copy link
Member

We can't use Unsafe.AsRef directly because it doesn't have an overload that takes an IntPtr.

Well, not without adding unsafe and either casting to (void*) or calling .ToPointer(). ;P

@tannergooding
Copy link
Member

I would expect this to improve over time as managed references become the preferred style for users.

I don't think this will ever get as slim as raw pointers. You also have other issues, such as not being able to accurately handle/represent things like null.

if(nmhdr->code == NativeMethods.TTN_SHOW) {
m.Result = UnsafeNativeMethods.SendMessage(new HandleRef(null, nmhdr->hwndFrom), Interop.WindowMessages.WM_REFLECT + m.Msg, m.WParam, m.LParam);
private void WmNotify(ref Message m) {
ref readonly NativeMethods.NMHDR nmhdr = ref m.GetLParamRef<NativeMethods.NMHDR>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there actually any guarantee that the underlying nmhdr is readonly or are you just using this to say the local WmNotify shouldn't modify the state?

{
IntPtr srcPtr, destPtr;
if (IntPtr.Size == 4) {
srcPtr = new IntPtr(sourceData.Scan0.ToInt32() + offsetSrc);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IntPtr has a operator +(IntPtr pointer, int offset) method.

@zsd4yr
Copy link
Contributor

zsd4yr commented May 6, 2019

hey @sharwell , i kind of fell out of touch here while i was OOF; how is this going?

@zsd4yr
Copy link
Contributor

zsd4yr commented May 10, 2019

@jkotas , @tannergooding , and @JeremyKuhne -- could one of you please approve when your perspective is satisfied?

@sharwell pinging again 😄

@tannergooding
Copy link
Member

@zsd4yr, I think the perspective given so far was that this PR is largely unnecessary and sticking with raw pointers/unsafe code is probably better (e.g: #835 (comment)).

@zsd4yr zsd4yr closed this May 10, 2019
@ghost ghost locked as resolved and limited conversation to collaborators Feb 6, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants