Proposal: For static extern methods, C# should support passing null
to any out
or ref
parameters marked with [Optional]
.
#79
Replies: 10 comments 3 replies
-
This would allow me to reduce the number of pinvoke overloads I maintain. (@VSadov nullable ref parameters?) |
Beta Was this translation helpful? Give feedback.
-
[Out, Optional] IntPtr* ppDevice seems the very right way to handle this situation. The callee expects a pointer and has a special handling of Mapping such parameter to an The proposal tries to special case certain methods and special case certain arguments. Basically - I understand the issue, but do not see it as deserving this kind of solution. |
Beta Was this translation helpful? Give feedback.
-
Unless it fits into a wider picture. Suppose nullable refs become an ordinary managed thing as well, as has been requested? |
Beta Was this translation helpful? Give feedback.
-
I think this now has more precedence with the existence of Having this support allows the user to not allocate when it is unnecessary (even if said allocation is only on the stack). It also reduces the API surface area for interop code (no longer have to have a 'safe' version for normal use and an 'unsafe' version just for when doing validation of input parameters). |
Beta Was this translation helpful? Give feedback.
-
@jnm2, we can workaround this right now using |
Beta Was this translation helpful? Give feedback.
-
@tannergooding When's that coming to .NET Framework 😄 |
Beta Was this translation helpful? Give feedback.
-
@jnm2, it is available already. |
Beta Was this translation helpful? Give feedback.
-
I've stumbled upon this problem too when I was writing code for the Vulkan C++ API. In it there is the function VkResult vkEnumerateInstanceLayerProperties(
uint32_t* pPropertyCount,
VkLayerProperties* pProperties
); which returns either the number of layers in the first parameter or the layers themselves (by writing in an array) in the second parameter depending on whether the second parameter is null or not. I think fundamentally the problem here is that one function is being used for two different behaviours. This works for native code because there is a lot of freedom in what value can be shoved into what variable or argument, but in C# there is a lot less freedom - that's kind of the whole point of C#, it tries to be safer. Therefore I think this can be solved in one of two ways:
[DllImport(vulkanLibrary, CallingConvention = CallingConvention.Winapi, EntryPoint = "vkEnumerateInstanceLayerProperties", ExactSpelling = true)]
private static extern unsafe Result EnumerateInstanceLayerProperties(
ref UInt32 propertyCount,
LayerProperties* properties
); which enables both behaviours but requires the use of unsafe code. Of course there's also the option of using Marshalling, but that's not really viable for performance critical code. (For example the Vulkan API contains many other functions that behave like this one.)
[DllImport(vulkanLibrary, CallingConvention = CallingConvention.Winapi, EntryPoint = "vkEnumerateInstanceLayerProperties", ExactSpelling = true)]
private static extern Result EnumerateInstanceLayerProperties(
out UInt32 propertyCount,
UIntPtr properties /* = UIntPtr.Zero */
);
[DllImport(vulkanLibrary, CallingConvention = CallingConvention.Winapi, EntryPoint = "vkEnumerateInstanceLayerProperties", ExactSpelling = true)]
private static extern Result EnumerateInstanceLayerProperties(
in UInt32 propertyCount,
[Out] LayerProperties[] properties
); and then I can do public static Result EnumerateInstanceLayerProperties(
out UInt32 propertyCount
) => EnumerateInstanceLayerProperties(
out propertyCount,
UIntPtr.Zero
);
public static Result EnumerateInstanceLayerProperties(
LayerProperties[] properties
) => EnumerateInstanceLayerProperties(
(uint)properties.Length,
properties
); which I think would be the preferred way of doing these kinds of things. Also there's the option of using nullable types - using classes instead of structures, which solves some of the simpler types of these kinds of problems. (Especially if used together with in/out/ref to create double pointers as explained in https://docs.microsoft.com/en-us/dotnet/framework/interop/passing-structures) In conclusion I think that the interoperability capabilities of C# are quite powerful already and I don't think it would be possible to (even theoretically) seamlessly interoperate between native and managed code without either breaking the safety benefits of using managed code or introducing (possibly large) overhead. |
Beta Was this translation helpful? Give feedback.
-
I often find myself working with the Win32 API and I come across functions with |
Beta Was this translation helpful? Give feedback.
-
I recently faced the same problem. I don't know how many people are looking at this thread right now, but I'd like to share my perspective. There are many functions in the Win32 API that have an optional out parameter. Another solution is to have an overload (or a function with a different name) that omits optional out parameter. However, there is a disadvantage: if there is more than one optional out parameter, then 2^n overloads are needed. This is not reasonable. I think we need a new attribute like the one below. D3D12CreateDevice(
IUnknown? pAdapter,
D3D_FEATURE_LEVEL MinimumFeatureLevel,
Guid* riid,
[OptionalOut] out IntPtr ppDevice); The D3D12CreateDevice(adapter, level, riid, out _); is evaluated as D3D12CreateDevice(adapter, level, riid, out Unsafe.NullRef<IntPtr>()); In this way, a caller can use P.S. |
Beta Was this translation helpful? Give feedback.
-
Ported from dotnet/roslyn#13119
Issue
There are a number of interop scenarios where a static extern method takes an out parameter and allows it to be
null
.For Example
might translate to
However, C# does not currently support passing
null
to the optional fourth parameter.Workaround
Use unsafe code and remove the
out
keyword so that the user can actually passnull
.For example, the above becomes:
Proposal
For static extern methods, C# should support passing
null
to anyout
orref
parameter marked withOptional
.This would allow you to write code such as:
Other Thoughts
It would be immensely useful if this was supported in other scenarios as well, such as for COM Interop.
For example, one might modify the above function to take a strongly type interface:
The proposal would work for the static extern method, but it would not work for the
ID3D12Device.CreateHeap
method.Support for the static extern method is much simpler, because the code is guaranteed to not exist in managed code.
However, support for the latter would require some extension to the language. Such an extension would either indicate the interface/delegate/method exist in native code (and can therefore handle a
null
out parameter) or it would allow the language itself to work withnull
out parameters (currently there is no way to determine that theout
is null, only that the objectout
points to isnull
).It should be noted that
[ComImport]
does provide some of the functionality desired for optional out parameters on interfaces. However, applying theComImport
attribute may provide functionality that is undesirable in some COM interop scenarios.Beta Was this translation helpful? Give feedback.
All reactions