Skip to content
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

Enable friendly overload generation for optional ref, out and in parameters #137

Open
AArnott opened this issue Feb 22, 2021 · 10 comments
Open
Labels
enhancement New feature or request

Comments

@AArnott
Copy link
Member

AArnott commented Feb 22, 2021

We should use ref or out modifiers to avoid pointers in more places. We don't do it today where the argument is optional, because C# disallows null for ref or out parameters.

However, callers can use Unsafe.NullRef<T>. We could even add this tip to the API docs so folks can discover it.

And the helper function can use Unsafe's SkipInit and IsNullRef methods to detect when this has occurred, if this were ever necessary.

@AArnott AArnott added the enhancement New feature or request label Feb 22, 2021
@AArnott AArnott changed the title Enable friendly overload generation for optional ref or out parameters Enable friendly overload generation for optional ref, out and in parameters Mar 18, 2021
@julianxhokaxhiu
Copy link

I am trying to use this package and I ended up exactly in this situation: either I mark most of my code unsafe or I need to use any way to pass pointers without marking the code unsafe. Any idea how to do so? What is the progress around this?

@AArnott
Copy link
Member Author

AArnott commented Sep 20, 2023

We should keep #1040 in mind. As an implementation for such a method, getting a ref or out parameter that might be given a null reference will throw NRE when initialized by the method. Detecting and avoiding this is tricky and unintuitive. So maybe we should stick with pointers for optional parameters, at least for COM interfaces since they are more likely to be implemented in C#.

@netcorefan1
Copy link

Sorry guys for the stupid question, but I have not yet understood what is the recommended way to handle such situations. As soon as I'm getting into CsWin32 I had to face the same issues stated from #137 (comment), even for simple functions that just return uint values.
For example, as of now, to get the process id from GetWindowThreadProcessId (#107), I am forced to go unsafe (PInvoke.GetWindowThreadProcessId(win, &prid);) and I'm unable to access prid inside a lambda expression unless I further expand unsafe code and this is going to create many other issues.

What would be the recommended workaround for this and all the other similar signatures?
Thanks

@AArnott
Copy link
Member Author

AArnott commented Jan 6, 2024

@netcorefan1 You can isolate the unsafe code by taking one of the approaches demonstrated in the following code:

using System.Runtime.CompilerServices;

using Windows.Win32;
using Windows.Win32.Foundation;

HWND hwnd = default; // set to value out of scope for sample

PInvoke.GetWindowThreadProcessId(hwnd, out uint processId);

// Ways of providing null in the optional out parameter.
PInvoke.GetWindowThreadProcessId(hwnd, out Unsafe.NullRef<uint>());
PInvoke.GetWindowThreadProcessId(hwnd);

namespace Windows.Win32
{
    partial class PInvoke
    {
        internal unsafe static uint GetWindowThreadProcessId(HWND hwnd)
        {
            return GetWindowThreadProcessId(hwnd, null);
        }

        internal unsafe static uint GetWindowThreadProcessId(HWND hwnd, out uint lpdwProcessId)
        {
            fixed (uint* _lpdwProcessId = &lpdwProcessId)
                return GetWindowThreadProcessId(hwnd, _lpdwProcessId);
        }
    }
}

@weltkante
Copy link

weltkante commented Jan 6, 2024

In my own experiments I started to generate the base functions with in/ref/out (not pointers) and manually write overloads passing Unsafe.NullRef - thats a little less manual work than having to write all the overloads.

I reserve generating pointer arguments only for cases where multiple items are being passed (and then write overloads using spans and leave out length parameters).

Just reporting from what worked for my own experiments, not necessarily the best for this project, but figured I'd mention it.

@AArnott
Copy link
Member Author

AArnott commented Jan 9, 2024

Note to self: #1081 reveals that while this may work for extern methods, COM interface methods throw NRE from the interop layer when an in parameter is passed an Unsafe.NullRef<T> reference.

@weltkante
Copy link

That makes sense, I've stopped using classic COM interop on .NET Core, since I'm already generating custom code anyways I can go the full way and use the new interop features of .NET 7+

Though if this effort is out of scope for this project this might mean you'll have to wait for the COM source generators to become more mature and feature complete compared to classic COM interop, right now its a bit rough to use the builtin generators (e.g. property syntax isn't supported, Variant marshalling is missing, etc.)

I assume API compatibility with Desktop Framework is no concern because Unsafe.NullRef doesn't work there anyways. Any project where I need Desktop Framework compatibility I try to generate things in a way that are source-compatible via extension methods or implicit conversions, but not binary compatible. Allows me to write code usable for both frameworks, but not having to make compromises or miss any of the new features when writing new code that doesn't need to run on Desktop Framework.

@AArnott
Copy link
Member Author

AArnott commented Jan 9, 2024

I assume API compatibility with Desktop Framework is no concern because Unsafe.NullRef doesn't work there anyways.

Sure it does.
In general, CsWin32 supports both runtimes, and it generates better code for each successive version of .NET to match its new capabilities. For example, more Span-related APIs are generated on later .NET versions where .NET Framework doesn't support some of the more advanced stuff.

@weltkante
Copy link

uh, what? the doc page says its available in .NET 5-8, and it certainly isn't available in a fresh project multitargeting net472/net8. If I try to import the System.Runtime nuget package the newest version compatible with both is 4.3.1; is there a compatibility nuget package I'm missing?

also I was under the impression that ref-returns needed runtime changes when they were introduce so they don't work in Desktop Framework (but I just heared that and haven't tried to verify it myself)

@AArnott
Copy link
Member Author

AArnott commented Jan 9, 2024

apisof.net is more reliable in my experience than docs.microsoft.com. apisof.net tells you that Unsafe.NullRef is available on net472 via the System.Runtime.CompilerServices.Unsafe package, which is available as low as net461.

As for ref-returns, that's off topic for this thread. But as that's a language feature, I expect the C# compiler will only permit its use when targeting the runtimes that support it. It's pretty good at that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants