Search code examples
c#c++unity-game-enginepinvokemarshalling

Storing data for unmanaged code when using P/Invoke


I have an array of arrays of this struct (shown here in C#, but existing in C++ as well):

[StructLayout(LayoutKind.Sequential)]
public struct MyStruct
{
    IntPtr name;             //pointer to string, char* on C++ side

    long pValues;
    long jValues;
    long eValues;
    long kValues;

    int cost;
};

and an algorithm in a C++ DLL that does work on it, being called from managed C# code. It's CPU-heavy, which is what necessitates this as it runs much faster in C++ than C#. The managed (C#) side never has to know the contents of the struct data, as the algorithm only returns a single array of ints.

So, how would I go about storing this data in the most efficient way (ie with the least overhead), for the lifetime of the application? I think I have it narrowed down to two options:

  1. Initialize structs and set values in C#, pin memory with GCHandle and pass reference to C++ whenever I want to do work (see this post on Unity forums)

  2. Initialize structs and set values in C++, have structs persist in memory on unmanaged side

So my questions are very specific:

With 1, I'm confused as to how marshalling works. It looks like in MSDN: Copying and Pinning that you are able to pass arrays of structures by pinning and passing a reference to the pinned data, without having to copy or convert any of it (and as long as the struct looks the same on both sides). Am I reading that correctly, is that how it actually works? Referring to the Unity3d forum post, I see Marshal.PtrToStructure being called; I thought that performs copying operations? As the data would be stored on the managed side in this instance, having to copy and/or convert the data every time the C++ function is called would cause a lot of overhead, unless I'm thinking that those type of operations are a lot more expensive than they actually are.

With 2, I'm wondering if it's possible to have persistence between C++ calls. To the best of my knowledge, if you're P/Invoking from a DLL, you can't have persistent data on the unmanaged side, so I can't just define and store my struct arrays there, making the only data transferred between managed and unmanaged the int array resulting from the unmanaged algorithm. Is this correct?

Thank you very much for taking the time to read and help!


Solution

  • If the C# code does not need to know the internals of the array and the structure, don't expose it to the C# code. Do all the work on this type in the unmanaged code and avoid marshalling overhead.

    Essentially, you want to follow this basic pattern. I'm sure the details will differ, but this should give you the basic concept.

    C++

    MyStruct* newArray(const int len)
    {
        return new MyStruct[len];
    }
    
    void workOnArray(MyStruct* array, const int len)
    {
        // do stuff with the array
    }
    
    void deleteArray(const MyStruct* array)
    {
        delete[] array;
    }
    

    C#

    [DllImport(dllname)]
    static extern IntPtr newArray(int len);
    
    [DllImport(dllname)]
    static extern void workOnArray(IntPtr array int len);
    
    [DllImport(dllname)]
    static extern void deleteArray(IntPtr array);