0

I have a C++ function that accepts a pointer to a native object. I can call the function from my C# code in two ways:

  1. Pass an IntPtr to it, like this,
  2. Pass the subclass of the SafeHandle to it, like this.

While the first case is quite straightforward, I'm struggling to understand what is actually happening under the hood in the second case.

Does the runtime simply call DangerousGetHandle() on the SafeHandle? Does it also update the reference counter on the SafeHandle instance using DangerousAddRef() and DangerousRelease(), by analogy with SafeBuffer.AcquirePointer()?

The reason I'm trying to understand this is that I have an old native API that can only accept IntPtr, there is no way to use the implicit SafeHandle to IntPtr conversion, so I'm wondering weather it would be acceptable to manually "convert" SafeHandle to IntPtr using DangerousGetHandle().

I tried to decompile the DLL with my C# code hoping to see what is happening during the conversion, however, it seems like the conversion is not a part of the DLL, the code seems to be a part of the runtime.

yaskovdev
  • 1,213
  • 1
  • 12
  • 22
  • 2
    Yes, lots of runtime support involved with a safehandle, ultimately producing a call to DangerousGetHandle(). With the runtime guarantee that this call is made with AddRef() having been called first and Release() to be called afterwards, ensuring it is not dangerous. If you do it yourself then there is of course no such guarantee and no real point in using SafeHandle at all. Which tends to be the case for handles that are not kernel OS handles, no real risk for handle recycle attacks. – Hans Passant Sep 10 '22 at 14:52
  • @HansPassant, thank you for the clarification! Are you sure about "no real point in using `SafeHandle` at all"? If I understand correctly, one more advantage of the `SafeHandle` is that it gives a very strong guarantee that [the resource it manages is eventually released](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.safehandle?view=net-6.0#why-safehandle), even if an asynchronous exception happens. Even `using` and `try catch` cannot give such a strong guarantee. – yaskovdev Sep 10 '22 at 15:04
  • The nice thing about kernel OS handles is that you can trust the kernel to still operate correctly when the process state gets corrupted. The kind of trust you definitely can't assign to a piece of native code you didn't write and can't debug. Especially so when it is the cause of the runtime failure. Now releasing the handle gets very risky, likely to cause an additional SEH exception and obfuscating the original mishap. – Hans Passant Sep 10 '22 at 15:27
  • @HansPassant, do you mean that if it is a handle to an OS resource, then I don't have to worry about releasing it in a situation when even `using` and `try finally` are not guaranteed; if it is a handle to a native object created by some piece of C++ code, then releasing it in case of such a serious failure that even `using` and `try finally` stop working is even worse idea? What is the use case for a `SafeHandle` then? – yaskovdev Sep 10 '22 at 15:38
  • 1
    You seem to have a misunderstanding about what is going on: `SafeHandle` is understood by the marshaller as `IntPtr`, but it will manage the `AddRef` etc automatically. What exactly that handle represents is up to the native code and your code, all the marshaller guarantees is that reference-counting is done correctly, and it holds a reference so that GC doesn't destroy it. You *must* use a `using` on a `SafeHandle` or it will not be released correctly. This is all ony useful if you can actually control the lifetime of the handle. If it's not your handle don't use `SafeHandle` – Charlieface Sep 11 '22 at 02:59
  • I've got your point, @Charlieface. Still, the reference counting sounds a bit vague. What I'm trying to understand is what _exactly_ the marshaller is doing during the `SafeHandle` to `IntPtr` conversion (and backwards). Could you suggest some official documentation or, better, the source code of the conversion (if it is public)? – yaskovdev Sep 11 '22 at 12:01
  • 1
    It's all on Github. You probably want to be looking here https://github.com/dotnet/runtime/blob/59b5585bd29333a8f00f3819f908930705c484f4/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/SafeHandleMarshaller.cs and here https://github.com/dotnet/runtime/blob/59b5585bd29333a8f00f3819f908930705c484f4/src/libraries/System.Runtime.InteropServices/gen/Microsoft.Interop.SourceGeneration/Marshalling/SafeHandleMarshaller.cs and here https://github.com/dotnet/runtime/blob/57bfe474518ab5b7cfe6bf7424a79ce3af9d6657/src/coreclr/vm/safehandle.cpp – Charlieface Sep 11 '22 at 12:42

0 Answers0