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.

有帮助吗?

解决方案

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);
    

其他提示

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.

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).

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

GetValue(myHandle, ref myStruct1, ref myStruct2);
GetValue(myHandle, ref Unsafe.NullRef<somestruct>(), ref Unsafe.NullRef<somestruct>());
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top