1

I know this question has already been asked and might be considered duplicate, but after reading other answers I am still quite baffled and do not understand explanation that is provided there.

I would be very grateful if someone could provided a sample code and some explanation.

I am working on following task:
I have a texture/textures (cannot be loaded from drive, generated on fly) in Unity and I convert it into an array. It can be an array of int, byte or float.

Currently, I do my object detection in C#, but I want to make it faster. Therefore, I want to do following.
Access a C++ code (from dll, I have just started my journey with c++) and run a method which would take my input array, do calculations and return a new array (old is not modified) to C# code.

Alternatively, I can input two arrays (preferred) where one of them is modified.
Output array does not have to be created every time, it can be overwritten. I cannot use unsafe code in C# as this is for asset purposes (compatibility issues).

Mock-up code

C#
create arrays aOne,aTwo;
SomeC++Method(aOne,ref aTwo)
or 
aTwo = SomeC++Method(aOne,aTwo)
or 
aTwo = SomeC++Method(aOne) // variant where aTwo is created in C++

SomeC++MethodToDeAllocateMemory() //so there is no memory leak

C++
SomeC++Method(aOne,aTwo)
  for (xyz)
   do someting
  return modified aTwo
SomeC++MethodToDeAllocateMemory()
  release memory

I have found answer to this questions here: Updating a C# array inside C++ without Marshal.Copy or Unsafe

Sample solution:

c++ code:

extern "C" __declspec(dllexport) bool PopulateArray(float* floats, int length)
{
    for (int i = 0; i < length; ++i)
    {
        floats[i] = i;
    }

    return true;
}

C# code

using UnityEngine;
using System.Runtime.InteropServices;

public class Test : MonoBehaviour {

    [DllImport("ArrayFilling", CallingConvention = CallingConvention.Cdecl)]
    public static extern bool PopulateArray([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] float[] floats, int length);

    int length = 4000000; //corresponds to 2000x2000 texture of grayscale values

    void Start()
    {
        //create new array
        float[] floats = new float[length];

        //used for speed test
        System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();

        //check internal method speed
        sw.Start();
        //floats array gets update here
        Populate(ref floats);
        sw.Stop();
        Debug.Log("internal: " + sw.ElapsedMilliseconds);
        sw.Reset();

        //check external method speed
        sw.Start();
        //floats array gets updated here
        PopulateArray(floats, length);
        sw.Stop();
        Debug.Log("external: " +sw.ElapsedMilliseconds);

    }

    void Populate(ref float[] floats)
    {
        for (int i = 0,n = length; i<n;i++)
        {
            floats[i] = i;
        }
    }
}

Regarding speed:
-external method (DLL) - 5 ms
-internal method (void Populate) - 23 ms

M.G.
  • 65
  • 10
  • Is this possible? I guess you can send the data through web services created in both the languages/applications. – Farhan Qasim Jan 26 '18 at 10:38
  • 3
    you could use C++/CLI -> https://stackoverflow.com/questions/5655936/how-to-use-c-cli-within-c-sharp-application – richej Jan 26 '18 at 10:40
  • IntPtr ptrName = Marshal.AllocHGlobal((int)buffSize); and Marshal.FreeHGlobal(ptrName); – jdweng Jan 26 '18 at 10:49
  • 1
    @FarhanQasim this looks like an attempt to improve the performance of number-crunching of in-memory data; going to a web-service would be the worst possible way to implement this, unless the web-service has access to a massively more powerful processing engine (like: multiple GPUs, or with the web-service as a gateway to a cluster of servers that can shard the data) – Marc Gravell Jan 26 '18 at 10:57
  • "Currently, I do my object detection in C#, but I want to make it faster." <== personally, I suspect that you can do exactly that, just using safe C# – Marc Gravell Jan 26 '18 at 11:03
  • 1
    The first link in the duplicate shows how to pass array from C# to C++. The second link shows how to do the opposite. You must free it after receiving the array returned from C++ to C#. You have to pin the array otherwise expect your plugin to crash Unity sometimes and work other times. Also you will be creating and destroying arrays which is slow. That's why people decide to pass array from C# to C++ to be modified. I suggest you read [this](https://codereview.stackexchange.com/questions/162088) post as it describes solution to most of the stuff you mentioned in this question. – Programmer Jan 26 '18 at 13:12
  • 1
    @richej CLI is not compatible with every platform. OP should write the plugin in pure C or C++. – Programmer Jan 26 '18 at 13:12
  • Thanks Programmer, good to know that CLI might not be compatible with some platforms. I have seen other topics but have not seen ones you have provided, thanks for finding these as them seem exactly what I need. Cheers! – M.G. Jan 26 '18 at 15:00
  • Upon closer look I see that they operate one unsafe code in the first topic "public **unsafe** void ProcessData(byte[] data, int size)". I cannot use this method like this:/ – M.G. Jan 26 '18 at 15:04
  • Do you have any hard evidence that a C++ implementation is going to run faster than a C# implementation? – BlueMonkMN Jan 26 '18 at 15:09
  • That is what I have read and that is why I want to investigate. From what I have read, operating on arrays either using c++ or unsafe code is faster than using "safe" c# code. But given that I have to pass data back and forth might provide similar results. Until I test it, I cannot say that for sure it will. – M.G. Jan 26 '18 at 15:25
  • `unsafe` only when the `fixed` keyword is used. The first link in the duplicate mentioned that you can use `GCHandle.Alloc` instead of `fixed` which means you can now remove the `unsafe` keyword if you go this route. I have an example of how to use `GCHandle.Alloc` to pin array on my other answer [here](https://stackoverflow.com/questions/43731305/read-pixel-color-at-screen-point-efficiently/43881621#43881621). Again, there is nothing wrong with using `unsafe`. It is faster than using `GCHandle.Alloc`. Why not use it if this post if about performance? Compatibility is not an issue here at-all. – Programmer Jan 27 '18 at 01:24
  • The algorithm is a part of an asset for Unity users, and in Unity you have to manually unlock unsafe code (method depends on Unity version one is using). I want it to be as simply to use as possible and do not force people to unlock something just for sake of single algorithm. If it was only for my project, I would not bother. Still I think if there is a way to optimize ones code, one should not avoid it just because it is more complex (within reason obviously). – M.G. Jan 27 '18 at 08:21

1 Answers1

4

The P/Invoke support between .NET and C++ is pretty good. Usually it amounts to declaring some extern methods using [DllImport] to tell the runtime where to find the external dll. When passing arrays, the key question is who is allocating them. If the C# code is allocating them, then you can usually just pass them in - for example like here: https://stackoverflow.com/a/5709048/23354. If the C++ code is allocating them, then your C# code will need to talk in pointers, which means unsafe, which you're not allowed to do (from the question). So if possible: have the C# code own the arrays.

If the array doesn't cleanly match any of the available calls - or you need to apply offsets to the array - then you can declare the extern method to take an IntPtr or some int* etc, but you're back in unsafe land here. You would use fixed to turn the reference into a pointer, i.e. fixed(int* ptr = arr) { SomeExternalCall(ptr + offset, arr.Length - offset); }. But if possible, using the simple "pass the array" approach is simpler.

Note that if the methods are not synchronous - i.e. if the C++ code will keep hold of the pointer for some time - then you will need to manually pin the array via a GCHandle. The P/Invoke code knows to pin an array for the duration of a single extern call (just like fixed would do, in the example above), but it doesn't guarantee not to move it after that call has completed - which would make the pointer that the C++ code holds unreliable. A GCHandle solves this, by pinning the object in place for whatever period you need.

Frankly, though; C# isn't bad at processing data - even when using 100% safe code. If your C++ implementation isn't already written, I would suggest the first thing to try would be: to simply improve the C# implementation to be more efficient.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Hi Marc, thank you for the answer. I am reading about marshaling, but it is confusing. I am already scratching bottom with my algorithm in c#. The only way to improve it (as far I can see it) is to use unsafe code (which I cannot). The algorithm is relatively simple (look for points in array, update other array accordingly). The problem is that images are of significant size, so speed at which you can go through array becomes bottleneck. Unsafe would improve it as I could work on bare memory, but cannot use it, so I though about c++. It looks that topics that Programmer found my be the answer. – M.G. Jan 26 '18 at 15:01
  • 1
    Regarding "to simply improve the C# implementation to be more efficient." - I have already spent significant amount of time optimising it and this is next step which might provide better results. I want to explore all possibilities before caging myself with "comfortable" answers. – M.G. Jan 26 '18 at 15:32