Passing `null` reference for a `ref struct` parameter in interop method
Asked Answered
L

4

8

I am using C# to call a DLL function.

[DllImport("MyDLL.dll", SetLastError = true)]
public static extern uint GetValue(
        pHandle handle,
        ref somestruct a,
        ref somestruct b);

How can I pass a null reference for argument 3?

When I try, I am getting a compile-time error:

Cannot convert from <null> to ref somestruct.

I also tried IntPtr.Zero.

Leasehold answered 5/3, 2013 at 6:41 Comment(3)
It is a ref parameter? So why don't you show us the exact method signature? Is somestruct really a struct or a class?Slim
fixed it.It's a structLeasehold
Can this article msdn.microsoft.com/en-us/library/1t3y8s4s(v=vs.80).aspx help you?Worcester
S
7

You have two options:

  1. Make somestruct a class, and change the function signature to:

    [DllImport("MyDLL.dll", SetLastError = true)]
    public static extern uint GetValue(
        pHandle handle, somestruct a, somestruct b);
    

    Usually this must not change anything else, except that you can pass a null as the value of a and b.

  2. Add another overload for the function, like this:

    [DllImport("MyDLL.dll", SetLastError = true)]
    public static extern uint GetValue(
        pHandle handle, IntPtr a, IntPtr b);
    

    Now you can call the function with IntPtr.Zero, in addition to a ref to an object of type somestruct:

    GetValue(myHandle, ref myStruct1, ref myStruct2);
    GetValue(myHandle, IntPtr.Zero, IntPtr.Zero);
    
Slim answered 5/3, 2013 at 6:58 Comment(1)
This is not very commonly known technique, very useful!Wellheeled
A
3

Since .NET 5.0 there is System.Runtime.CompilerServices.Unsafe.NullRef<T>()

GetValue(myHandle, ref myStruct1, ref myStruct2);
GetValue(myHandle, ref Unsafe.NullRef<somestruct>(), ref Unsafe.NullRef<somestruct>());
Ascription answered 16/4, 2021 at 18:12 Comment(0)
S
0

This answer suggests to make SomeStruct a class. I would like to show an implementation of that idea which appears to work nicely… even when you cannot change the definition of SomeStruct (such as when it is a predefined type like System.Guid; see also this answer).

  1. Define a generic wrapper class:

    [StructLayout(LayoutKind.Explicit)]
    public sealed class SomeStructRef
    {
        [FieldOffset(0)]
        private SomeStruct value;
    
        public static implicit operator SomeStructRef(SomeStruct value)
        {
            return new SomeStructRef { value = value };
        }
    }
    

    The basic idea here is identical to boxing.

  2. Change your interop method definition to the following:

    [DllImport("MyDLL.dll", SetLastError = true)]
    public static extern uint GetValue(
        pHandle handle,
        ref SomeStruct a,
        [MarshalAs(UnmanagedType.LPStruct)] SomeStructRef b);
    

The third parameter b will then be "nullable". Since SomeStructRef is a reference type, you can pass a null reference. You can also pass a SomeStruct value because an implicit conversion operator from SomeStruct to SomeStructRef exists. And (at least in theory), due to the [StructLayout]/[FieldOffset] marshalling instructions, any instance of SomeStructRef should get marshalled just like an actual instance of SomeStruct.

I'd be happy if someone who is an interop expert could validate the soundness of this techinque.

Scarabaeid answered 17/5, 2015 at 21:36 Comment(0)
S
0

Another obvious solution is to resort to unsafe code and change the interop method declaration to this:

[DllImport("MyDLL.dll", SetLastError = true)]
unsafe public static extern uint GetValue(
        pHandle handle,
        ref somestruct a,
        somestruct* b);

Notice that the method is now marked unsafe, and that the parameter has changed from ref somestruct to somestruct*.

This has the following implications:

  • The method can only be called from inside an unsafe context. For example:

    somestruct s;
    unsafe { GetValue(…, …, &s);   }  // pass a struct `s`
    unsafe { GetValue(…, …, null); }  // pass null reference
    
  • In order for the above to work, unsafe code must be allowed for the project (either in the project settings, or via the /unsafe command-line compiler switch).

  • Using unsafe leads to unverifiable IL code. IIRC, this means that loading this assembly will require full trust (which can be problematic in some situations).

Scarabaeid answered 21/6, 2016 at 22:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.