0
votes

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!

1
If you have a string in your struct then your struct isn't blittable... It will slow down everything because the struct will be "translated" on enter and on exit of the C function. See msdn.microsoft.com/en-us/library/ef4c3t39.aspx . If the C struct has a LPSTR or a LPTSTR or a LPWSTR or a char* , put a IntPtr. - xanatos
ah thanks, i missed that! fixed. - Ka-Wiz
Have you looked at why it runs faster in C++ than C#? It may be something that's easy enough to fix, e.g. avoiding bounds checking, better cache locality etc. And you certainly can keep the data on the unmanaged side - the DLL lives in your process space, and it can allocate and free memory at will. Be careful about pinning managed memory - it can prevent garbage collection from working properly (in particular, it's impossible to move pinned memory, for obvious reasons - this can easily cause excessive heap fragmentation if you're not careful). - Luaan
You say that the managed code never references the data in this form. And yet there it is as a C# struct. Something doesn't add up here. Seems to me that you just need to work with the structure entirely on the unmanaged side. If it needs to persist across calls, refer to it through an opaque pointer. - David Heffernan
Luaan: i don't actually have any hard data on it being faster, i just have the algorithm fully implemented in C++ and the knowledge that C++ compilers are much better at low-level optimizing. it seems silly to reimplement in C# with that knowledge, it could only make it slower, no? and do you have a link that explains about keeping memory allocated by a DLL? i thought that any data allocated in unmanaged code and not properly cleaned up upon return to managed basically just became a leak! - Ka-Wiz

1 Answers

3
votes

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