Version: Unity 6.5 (6000.5)
Language : English
Primitive types
NativeArrays

Structures, classes, and unions

To exchange data in a structure, class, or union, both the unmanaged and the managed code must declare compatible data structures. The .NET Interop Services API provides attributes that you can use to specify how the fields of a C# type are arranged in memory and how each field in the type should be marshalled when transferred between managed and unmanaged code.

The StructLayoutAttribute lets you match the layout of a managed type to that of the corresponding struct defined in unmanaged code. By default, C# lays out managed structs sequentially based on the size of each field’s data type (sometimes with extra padding for alignment). You can use other values of StructLayoutAttribute to precisely control the offset and effective size of a struct’s fields. You can also specify how to marshal strings in the struct.

Use MarshalAsAttribute on individual fields to control how that field is marshalled.

Use LayoutKind.Explicit along with the FieldOffsetAttribute to define a struct that corresponds to a C/C++ union.

Note: When an unmanaged function returns a structure or class by value to managed code, that type must be blittable. (Non-blittable scalar values such as bool can still be returned when you specify how to marshal them, as the following example does.)

For example, the following C-language code defines a struct representing an axis-aligned box and an unmanaged function that takes the struct and an array pointer as parameters:

#include <math.h> // for fabsf

typedef struct _Box {
    float Position[3];
    float Scale;
    float Size[3];
} CustomBox;

extern "C" {
    bool CheckInside(CustomBox box, float* point) {
        // Use fabsf so the float differences aren't truncated to integers.
        // (The integer abs() function would round each difference toward zero.)
        if( fabsf( box.Position[0] - point[0] ) < box.Size[0] * box.Scale &&
            fabsf( box.Position[1] - point[1] ) < box.Size[1] * box.Scale &&
            fabsf( box.Position[2] - point[2] ) < box.Size[2] * box.Scale
          )
                return true;

        return false;
    }
}

Note: Refer to Call unmanaged functions from managed code for information about the annotations needed to compile and call functions such as this as part of a dynamically loaded library.

The CheckInside function calculates whether the point is inside the box and returns a C bool type. To call this function from managed code, you must define a compatible C# struct and tell the scripting runtime how to treat the parameters and return type:

using UnityEngine;
using System.Runtime.InteropServices;

// Sequential layout is the default and should be used in most cases
[StructLayout(LayoutKind.Sequential)]
public struct CustomBox
{
    // Marshall the array in place, with 3 elements
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    float[] Position;
    float Scale;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
    float[] Size;

    // Constructor (defining a constructor is not mandatory)
    public CustomBox (Vector3 center, float scale, Vector3 size)
    {
        Position = new float[3];
        Size = new float[3];
        for (var i = 0; i < 3; i++)
        {
            Position[i] = center[i];
            Size[i] = size[i];
        }
        Scale = scale;
    }
}

public class StructMarshalExamples
{
    // Import the native function
    [DllImport("__Internal")]
    [return: MarshalAs(UnmanagedType.I1)] // Marshal bool as a 1 byte signed integer
    static extern bool CheckInside(CustomBox box, [In] float[] point);

    // Run the example code
    public static void RunExamples()
    {
        // box1's half-extent in each axis is Size * Scale = 1 * 2.5 = 2.5
        var box1 = new CustomBox(Vector3.zero, 2.5f, Vector3.one);
        // box2's half-extent in each axis is Size * Scale = 1 * 4 = 4
        var box2 = new CustomBox(Vector3.zero, 4, Vector3.one);
        // The x distance (2.7) is just outside box1 (2.5) but inside box2 (4).
        // If the native code truncated 2.7 to 2, box1 would wrongly report "inside".
        float[] point = { 2.7f, 1.5f, -1.5f };

        var test1 = CheckInside(box1, point);
        var test2 = CheckInside(box2, point);

        Debug.Log($"Test point is {(test1 ? "inside" : "outside")} " +
                  $"the first box and {(test2 ? "inside" : "outside")} " +
                  $"the second");
    }
}

Note: Change DllImport to use the library name if you use precompiled, dynamically linked libraries. Use the special string, __Internal for source code plug-ins and statically linked libraries. Refer to DllImport attribute for more information.

Primitive types
NativeArrays