0

I want to pass a two-dimensional array in C# as a parameter to a function from C++ DLL (It has to be C++ as I am using CUDA C++). I tried many things but failed to pass it directly using arrays or vectors. The only thing that I could do is to convert it to a one-dimensional array, passing it to the function along with its dimensions, then convert it back to a two-dimensional vector. Here is the C++ DLL code:

int Add(int* a, int m, int n)
{
    int i, j, ans;
    vector<vector<int>> A(m, vector<int>(n));
    for (i = 0; i < m; i++)
        for (j = 0; j < n; j++)
            A[i][j] = a[i * n + j];
    // Main calculations
    return ans;
}

and this is the C# code that passes the array:

[DllImport("CUDALib.dll")]
static extern int Add(int[] a, int m, int n);
private void PassArray(int[,] A)
{
    int i, j, m, n, ans;
    int[] a;
    m = A.GetLength(0);
    n = A.GetLength(1);
    a = new int[m * n];
    for (i = 0; i < m; i++)
        for (j = 0; j < n; j++)
            a[i * n + j] = A[i, j];
    ans = Add(a, m, n);
}

I there any faster, more efficient and direct way to do this?

AbdelAziz AbdelLatef
  • 3,650
  • 6
  • 24
  • 52
  • 2
    Passing as a 1D array is fine and efficient but turning it into a `vector>` is going to produce a lot of heap allocations and de-allocations. Keep it in 1D form and make a class that you can access with 2 dimensions if that's what you need for clarity. – doug Dec 23 '19 at 22:57
  • What do you mean by "a class that you can access with 2 dimensions"? – AbdelAziz AbdelLatef Dec 23 '19 at 23:34
  • I posted an answer showing one way to do this which is quite efficient and uses standard `[n][m]` method to access array elements. – doug Dec 24 '19 at 18:31

3 Answers3

3

A 2D array in C# is contiguous in memory, so there is no need for all this copying of memory on either side. You should pass the array pointer in C# as-is:

[DllImport("CUDALib.dll", CallingConvention = CallingConvention.Cdecl)]
static extern int Add(int[,] a, int m, int n);
static void PassArray(int[,] A)
{
    int m = A.GetLength(0);
    int n = A.GetLength(1);
    int ans = Add(A, m, n);
}

And then access individual elements in C++ as such:

extern "C" __declspec(dllexport) int Add(int* a, int m, int n)
{
    int i, j;
    for (i = 0; i < m; i++)
        for (j = 0; j < n; j++)
            printf("%d %d: %d\n", i, j, a[i * n + j]);
    // Main calculations
    return 0;
}
mnistic
  • 10,866
  • 2
  • 19
  • 33
  • You solved the first half of my problem - passing - but is there any hope that I can receive the array within C++ as a two-dimensional array as this will be more suitable for my calculations, matrix multiplication, inverse, etc. – AbdelAziz AbdelLatef Dec 24 '19 at 02:21
  • 1
    Well you can use something like this: https://stackoverflow.com/a/22288609/4454124 with virtually no performance penalty, if you really need it to function as a 2D array – mnistic Dec 24 '19 at 02:41
  • YOU ARE THE MAN! – 686InSomNia686 Jul 30 '20 at 08:46
1

You can use a class to encapsulate the pointer, rows and cols and then access the passed 1D array as if it was a standard 2D array. There is no need to make a local copy within the function since p remains valid through the call. @mnistic's answer also notes that there is no need to make a 1D copy in C#.

class Array2D {
public:
    Array2D(int* p, size_t rows, size_t cols) : p(p), cols(cols), rows(rows){};
    int* operator[](int row) { return p + row * cols; }
    const size_t rows;  // not needed but can be used for adding boundary checks
    const size_t cols;
private:
    int *const p;
};

returning the sum of the array elements expressed as a 2D array

extern "C" __declspec(dllexport) int Add(int* p, int rows, int cols)
{
    Array2D a(p, rows, cols);
    int sum{};
    for (int i = 0; i < rows; ++i)
        for (int ii = 0; ii < cols; ++ii)
            sum += a[i][ii];
    return sum;
}

Here's a test that shows how it works

int main()
{
    int* p = new int[6]{ 1, 2, 3, 4, 5, 6 };
    Array2D a2d(p, 3, 2);   // three rows, two cols
    std::cout << a2d[0][0] << '\n';  // prints 1
    std::cout << a2d[1][1] << '\n';  // prints 4
    a2d[1][1] = 10;
    std::cout << a2d[1][1] << '\n';  // now prints 10
    std::cout << a2d[2][0] << '\n';  // prints 5
    std::cout << a2d[2][1] << '\n';  // prints 6
}
AbdelAziz AbdelLatef
  • 3,650
  • 6
  • 24
  • 52
doug
  • 3,840
  • 1
  • 14
  • 18
0

Here is template class of AbdelAziz's answer:

    template <class TdataType> 
class Array2d {
public:
    Array2d<TdataType>(TdataType* arr, size_t rows, size_t cols) : p(arr), cols(cols), rows(rows) {};
    TdataType* operator[](int row) { return p + row * cols; }
    const size_t rows;  // not needed but can be used for adding boundary checks
    const size_t cols;
private:
    TdataType* const p;
};

Here is an example of using it:

auto my2dArray = Array2d<uint8_t>(inputArrayFromCsharp, numRows, numCols);
mohghaderi
  • 2,520
  • 1
  • 19
  • 12