-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Proposal: Unsafe APIs to support ref void* and ref T* #44968
Comments
Aside, this code does build public ID3D12ResourceMap(ID3D12Resource* d3d12resource)
{
this.d3D12Resource = d3d12resource;
// ID3D12Resource::Map takes a void**
fixed (void** p = &Pointer)
{
d3d12resource->Map(0, null, p);
}
} Though I assume you'd want to do this; which does not build public ID3D12ResourceMap(ID3D12Resource* d3d12resource)
{
this.d3D12Resource = d3d12resource;
// ID3D12Resource::Map takes a void**, this code does not build
d3d12resource->Map(0, null, &Pointer);
} |
@benaadams Yup, mentioned that as a workaround. Either that, or just do: void* pointer;
d3d12resource->Map(0, null, &pointer);
Pointer = pointer; But both are very verbose for what they're doing, and Of course, for how much working with this stuff can be considered "natural and easy to use" 😄 EDIT: yeah in this specific scenario, I wish C# just allowed getting the address of a field in a |
IMO this is going to lead to overload explosion. The cleanest solution would be for the C# language to support ref arithmetic natively and to relax some restrictions on taking pointer. Consider your earlier example: public ID3D12ResourceMap(ID3D12Resource* d3d12resource)
{
this.d3D12Resource = d3d12resource;
// ID3D12Resource::Map takes a void**, but this code will not build.
- d3d12resource->Map(0, null, Unsafe.AsPointer(ref Pointer));
+ d3d12resource->Map(0, null, &this.Pointer);
} Right now that doesn't compile, but since 'this' is a ref struct it should be perfectly legal to get the address of it or any of its member fields without pinning and without interference from the GC. That C# doesn't allow this is best filed as an issue in the csharplang repo. As a temporary workaround, I believe all APIs proposed here can be provided on a custom |
Unfortunately, they said they won't be changing this soon . > As a temporary workaround, I believe all APIs proposed here can be provided on a custom UnsafeEx class written solely in terms of existing Unsafe APIs. Because you can't use pointers as generic type params, there is no way to do |
There is an existing issue and it is currently on the long term backlog due to complications in changing how
This is blocked by |
There is also #13627, which proposes allowing pointers as generic type arguments and which would unblock most of these scenarios (minus ref structs). |
@GrabYourPitchforks I agree with the high number of overloads - in fact I initially included the full list of possible APIs as I figured the conversation here would've trimmed them down to an actual subset to propose for review. Personally I'd be fine with a smaller subset of APIs, which currently can't be expressed otherwise as @tannergooding. Also there's the issue with C# not supporting this feature any time soon, so it'd be nice if these APIs could act as a stopgap for the time being. For instance: namespace System.Runtime.CompilerServices
{
public static class Unsafe
{
public static ref T* As<T>(ref void* source) where T : unmanaged;
public static ref TTo* As<TFrom, TTo>(ref TFrom* source) where TFrom : unmanaged where TTo : unmanaged;
public static void** AsPointer(ref void* value);
public static T** AsPointer<T>(ref T* value) where T : unmanaged;
public static void SkipInit(out void* value);
public static void SkipInit<T>(out T* value) where T : unmanaged;
// Optional, nice to have
public static bool IsNullRef(ref void* source);
public static bool IsNullRef<T>(ref T* source) where T : unmanaged;
public static ref void* PointerNullRef();
public static ref T* PointerNullRef<T>() where T : unmanaged;
}
} Seems like a more reasonable API surface for a proposal? 🙂 |
Why do you need to use ref structs to write unsafe code like this? |
If you need to support |
I don't think ref structs tend to be the problem and I merely called them out as an example of what generics don't support today. However, I do frequently hit the case of Some example cases are dealing with Having |
Quick & dirty hack to get namespace System.Runtime.CompilerServices
{
public unsafe static class UnsafeEx
{
public static T** AsPointer<T>(ref T* value) where T : unmanaged
{
delegate*<ref byte, void*> d = &Unsafe.AsPointer;
return ((delegate*<ref T*, T**>)d)(ref value);
}
}
} Creating a standalone DLL where this is written in pure IL would be slightly more efficient (since no calli opcode), but if you need to stick with C# and need an immediate workaround this should get the job done. Ninja edit, since I saw some questions on this. This sample should be fully legal per ECMA-335. It's not relying on internal / undocumented implementation details of the runtime. However, I did see some samples saying "oh, I can use a normal delegate and use |
Right, it needs #44610 to inline that AsPointer 🙂 |
This is definitely a Thanks I Love/Hate It scenario. But I'd still much rather see us expose the two |
So uhm... I actually found a solution that has no pinning, and no extra internal readonly unsafe ref struct ID3D12ResourceMap
{
public readonly void* Pointer;
public ID3D12ResourceMap(ID3D12Resource* d3d12resource)
{
var pMap = (delegate* unmanaged<ID3D12Resource*, uint, D3D12_RANGE*, void**, int>)(*(void***)d3d12resource)[8];
((delegate* unmanaged<ID3D12Resource*, uint, D3D12_RANGE*, out void*, int>)pMap)(d3d12resource, 0, null, out Pointer);
}
} Not sure how I feel about this to be honest 😄 |
Because your |
Oooh, so that's how that works! I was just about wondering why But jokes aside (would never actually use code like this in production anyway, for obvious reasons), other than this specific example which I already refactored differently anyway, I agree with @tannergooding that I think there would be value in offering at least a small subset of APIs from this proposal. Like, some |
Just figured I'd touch base again about this to figure out what the next steps would be, and also to avoid just letting this issue stuck in limbo if we agree it's not the best way forwards. From re-reading the previous conversation, it seems to me that:
Should I just close this and we can eventually pick it up from #13627 once things settle for .NET 6? 🙂 |
@Sergio0694 this sounds reasonable for me, I'd rather push for #13627 to be fixed |
Alright, closing this then, and hopefully we'll get #13627 in for .NET 7 or soon enough anyway, a dev can dream 😄 |
This would be really useful in my upcoming interop library conversion from C++/CLI to C# :) This will be several months down the road though We were talking about it on Discord and I was able to come up with this, which doesn't require the delegate pointer trick and may produce better codegen (unverified either way):
... although this requires changing from using e.g. (Posting this in case others wander in here while searching for a solution...) |
Background and Motivation
The
System.Runtime.CompilerServices.Unsafe
class exposes an extremely useful set of APIs that allow developers working with low-level APIs to do things that are currently not expressible in C#, but due to how the APIs are currently designed, there are a number of scenarios that are still not properly supported, specifically around the usage of pointer types. This is mostly due to the fact that C# doesn't allow usingvoid
as a generic type parameter, nor any pointer type. So if you're doing any kind of work with aref void*
orref T*
, you're mostly out of luck today, and need to resort to either rewriting your code rifferently, or using slower workarounds like pinning theref
and then using a double pointer to do work. But even then it's not really ideal.Proposed API
Usage Examples
For instance, C# today doesn't allow getting the address of a field in a
ref struct
without pinning, even ifthis
is already pinned by definition (see dotnet/csharplang#1792, such a feature is not being added any time soon unfortunately). To work around this, you can just doUnsafe.AsPointer(ref myField)
, but if the field is of a pointer type, none of the current APIs will work.Consider this example:
Risks
None that I can see. The class is already named
Unsafe
and it's in theS.R.CS
namespace, so only developers looking for exactly these APIs will ever use them. And they can already shoot themselves in the foot with the currently existing APIs 😄The text was updated successfully, but these errors were encountered: