Skip to content
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

NullReferenceException in Marshal.DestroyStructure with embedded structs containing delegates #61839

Closed
f2bo opened this issue Nov 19, 2021 · 1 comment · Fixed by #61985
Closed

Comments

@f2bo
Copy link

f2bo commented Nov 19, 2021

Description

I'm currently building a managed wrapper for a native library. A parameter in one of the API functions includes an embedded structure with callback information. I modeled the corresponding C# type to mirror its native definition using delegates. However, I observe that while the P/Invoke call using this parameter succeeds and that the delegate is marshaled correctly, it crashes upon return.

Reproduction Steps

The issue can be reproduced with the code included below that manually marshals and then destroys the structs.

Struct Foo2 is a simplified definition of the API parameter. It contains a nested CallbackData member that contains the delegate. For comparison, struct Foo1 flattens the same members to remove the embedded structure but that, presumably, has an identical memory layout.

delegate void CallbackDelegate(int value);

struct Foo1
{
    public CallbackDelegate? callback;
    public byte AnotherField;
}

struct Foo2
{
    public struct Data
    {
        public CallbackDelegate? callback;
    }

    public Data CallbackData;
    public byte AnotherField;
}

static unsafe void TestMarshaling<T>(T value)
    where T : struct
{
    void* ptr = stackalloc byte[Marshal.SizeOf<T>()];
    Marshal.StructureToPtr(value, (IntPtr)ptr, false);
    Marshal.DestroyStructure<T>((IntPtr)ptr);
}

private static void Main()
{
    static void Callback(int value) { }

    TestMarshaling(new Foo1
    {
        callback = Callback
    });

    TestMarshaling(new Foo2
    {
        CallbackData = { callback = Callback }
    });
}

Expected behavior

The Foo2 type should be destroyed correctly.

Actual behavior

Struct Foo1 can be marshaled successfully while Foo2 fails to be destroyed and results in a NullReferenceException in Marshal.DestroyStructure.

Object reference not set to an instance of an object.
   at System.Runtime.InteropServices.Marshal.DestroyStructure(IntPtr ptr, Type structuretype)
   at System.Runtime.InteropServices.Marshal.DestroyStructure[T](IntPtr ptr)
   at Benchmark.DestroyStructureBug.TestMarshaling[T](T value) in ...

Regression?

No response

Known Workarounds

Flatten the embedded struct members or use a function pointer instead of the delegate.

Configuration

.NET SDK (reflecting any global.json):
Version: 6.0.100
Commit: 9e8b04bbff

Runtime Environment:
OS Name: Windows
OS Version: 10.0.19043
OS Platform: Windows
RID: win10-x64
Base Path: C:\Program Files\dotnet\sdk\6.0.100\

Host (useful for support):
Version: 6.0.0
Commit: 4822e3c

Other information

No response

@dotnet-issue-labeler dotnet-issue-labeler bot added area-Interop-coreclr untriaged New issue has not been triaged by the area owner labels Nov 19, 2021
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Nov 23, 2021
@AaronRobinsonMSFT AaronRobinsonMSFT removed the untriaged New issue has not been triaged by the area owner label Nov 23, 2021
@AaronRobinsonMSFT AaronRobinsonMSFT added this to the 7.0.0 milestone Nov 23, 2021
@AaronRobinsonMSFT
Copy link
Member

@f2bo Thanks for reporting this. Once we get it into .NET 7, we will see about porting it to .NET 6.

@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Nov 24, 2021
@ghost ghost locked as resolved and limited conversation to collaborators Dec 25, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants