We help IT Professionals succeed at work.

How to pass a dynamic two-dimensional array from c# to c++

anasagasti
anasagasti asked
on
6,542 Views
Last Modified: 2013-11-05
I'm using Visual Studio 2005.
My problem is that I have a dll in C++ in which I have a function with one parameter that is double** which represents a dynamic two-dimensional array. For example:

double SumElementsDin(double** Array, int iRow, int iCol)
{
      double dTemp = 0.0;

      for(int i = 0; i < iRow; ++i)
            for(int j = 0; j < iCol; ++j)
                  dTemp += Array[i][j];

      return dTemp;
}

I have a class in c# and I would like to call that function passing the array.
First of all, I don't know the best way to declare the function:

        [DllImport("mydll.dll")]
        public static extern double SumElementsDin
            (
            // I don't know how to declare Array,
            int iRow,
            int iCol
            );

Then, in c# I don't know how to call the c++ function

public void my_function()
{
// Whatever

double[,] Array = new double[iNumRow, iNumCol];

// Call the c++ function ?
double Result =  SumElementsDin(Array, iNumRow, iNumCol);
}

Thanks.

Santiago.
Comment
Watch Question

You want to look at the System.Runtime.InteropServices Marshall class's Copy method to copy between managed and unmanaged arrays.

Author

Commented:
I've used Marshall class's Copy method for passing a one-dimensional array by ref.
e.g.

In c++

        double PassArrayByRef
            (
            double** Array,  // One dimensional array by ref
            int iDim
            );



Then, in c#
        [DllImport("mydll.dll")]
        public static extern double PassArrayByRef
            (
            ref IntPtr Array,
            int iDim
            );

Then, in c#
{
            double[] pdArray = new double[iSize];

           // Fill array

            IntPtr buffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(pdArray[0])
                     * iSize);
            Marshal.Copy(pdArray, 0, buffer, iSize);

           dResult = PassArrayByRef(ref buffer, iSize);
}

But I don't know how to do something similar with a two-dimensional array. I'm not used with c#.
>>>> But I don't know how to do something similar with a two-dimensional array.
I don't know either but you might pass a one-dimensional array instead:

     double[] pdArray = new double[iRow*iCol];
     ...
     dResult = Pass2DArrayByRef(ref buffer, iRow, iCol);

In c++:

        double Pass2DArrayByRef(double* p, int iRow, int iCol)
            (
            double** Array = &p[0];  // One dimensional array by ref
            for (int i = 0; i < iRow; ++i)
                 Array[i] = &p[i*iCol];
             
            // ....

            );

Regards, Alex

Author

Commented:
Thanks, but I don't have the possibility of changing the parameters of the c++ function, so double** is mandatory.
One solution could be a kind of wrapper in c++ to convert from one-dimensional array into two-dimensional array, but I'd prefer no to do that if there is a direct solution.
>>>> so double** is mandatory.
Note, a double** in C++ actually isn't a 2D array like 'double matrix[MR][MC];'. It is a 1D array of pointers each is pointing to the first item of a 1D array representing the rows of the 2D array. You'll see the issue in the sample code I used to convert from double* to double** (C++).

If you would like to do the same in C# you would need to pass an array of DoublePtr by reference in the same way, but it is surely easier to do that in C++ by using a wrapper:      

           double Pass1DArrayByRef(double* p, int iRow, int iCol)
            (
            double** Array = &p[0];  // One dimensional array by ref
            for (int i = 0; i < iRow; ++i)
                 Array[i] = &p[i*iCol];
             
             return Pass2DArrayByRef(Array, int iRow, int iCol);
            );

Author

Commented:
I know, but the point is I don't have only one c++ function with this problem, I have many, and I'd prefer not to create one wrapper for each function.
It's better for me to find a solution in c#, regardless the complexity, encapsulate it and use it as many times as I need.
This article may be useful to you:

http://msdn2.microsoft.com/en-us/library/hk9wyw21(VS.80).aspx

it shows you how to marshal a 2 dimensional array of integers using P/Invoke - it looks as if the method should work for a double array.

Author

Commented:
I'm afraid not. The example is as following:

In c++:

int TestMatrixOfInts(int pMatrix[][COL_DIM], int row);

In c++, when the array definition is with [][], you have to specify the second dimension. That's not a
dynamic array.
The way of defining a dynamic array is with:

int TestMatrixOfInts(int** pMatrix, int row, int col);

With the first example, the conversion between c# and c++ works. I don't know how to do it with the second example.
So...

      // matrix ByVal
      int[,] matrix = new int[ col, row ];

...isn't it?

Author

Commented:
No. That is not well received in c++ if the array is two-dimensional array. It's ok if the array is a one-dimensional array or a two-dimensional array with the column size predefined.

Commented:
From a memory perspective in C++ there's no difference between a 1 dimensional array and a 2 dimensional.  It's just how the compiler interprets the references to it.  In C# you can convert your 2D array to a 1D array with the same number of elements, varying the second subscript for each of the first subscript.  Then marshall the 1D and C++ wont know the difference.
Maybe a managed C++ wrapper is the best solution then - it would be fairly easy to create a method in managed C++ that does the marshalling manually.  If you use some sort of helper function then it probaly won't be that difficult to wrap up all the uses of the array methods - I know you have many - but each call in the managed C++ need only be a couple of lines of forwarding code.  Something like this (completely unchecked - array indices may be wrong):

void ManagedArrayFunc(array<double,2>^ arr)
{
      int x = arr->GetLength(0);
      int y = arr->GetLength(1);

      double** umarr = new double*[x];
      for(size_t i=0; i<static_cast<size_t>(x); i++)
      {
            umarr[i] = new double[y];
            for(size_t j=0; j<static_cast<size_t>(y); j++)
            {
                  umarr[i][j] = arr[i,j];
            }
      }

      UnmanagedArrayFunc(umarr,y,x);
}

Author

Commented:
mastoo, can you give me an example on what you are saying?

Commented:
Which part?  C# like below to convert 2D to 1D, then pass the result as one-dimensional array by ref. like you mentioned.

    static private int[] MyTwoDee( int[,] i_iTwoDee )
    {
      int iRowMax = i_iTwoDee.GetUpperBound(0);
      int iColMax = i_iTwoDee.GetUpperBound(1);
      int[] iReturn = new int[i_iTwoDee.Length];
      int iIndex = 0;
      for( int iRow = 0; iRow <= iRowMax; ++iRow )
      {
        for( int iCol = 0; iCol <= iColMax; ++iCol )
        {
          iReturn[ iIndex++ ] = i_iTwoDee[iRow, iCol];
        }
      }
      return iReturn;
    }
> From a memory perspective in C++ there's no difference between a 1 dimensional array and a 2 dimensional.

Beware that in unmanaged C++, a 2-D array which is passed as an array of pointers to 1-D arrays is different from an array with the first dimension fixed.

A double** is a pointer to a bunch of pointers, which typically point to individually allocated arrays of doubles. A double[][100] is a pointer (which a C programmer would have you call a "reference") to a chunk of contiguous memory which is a multiple of sizeof(double)*100 bytes in size.

See http://www.parashift.com/c++-faq-lite/freestore-mgmt.html#faq-16.16

However this shouldn't impact on what anasagasti is doing if his simply works through the two dimensions, indexing each element.

>>>> But I don't know how to do something similar with a two-dimensional array.
Maybe like that:

   IntPtr[] p2dArray = new IntPtr[iRow];
   for (long i = 0; i < iRow; i++)
   {
            double[] pdArray = new double[iCol];

           // Fill row (array)

            p2dArray[i] = Marshal.AllocCoTaskMem(Marshal.SizeOf(pdArray[0])
                     * iCol);
            Marshal.Copy(pdArray, 0, p2dArray[i], iCol);

    }
    dResult = Pass2DArrayByRef(ref p2dArray, iRow, iCol);

Regards, Alex

Author

Commented:
mastoo, I think your idea doesn't work because of what rstaveley says.
>>>>> A double** is a pointer to a bunch of pointers
So you have to send the correct array of pointers.
Even when in c++ you say
double[][100]
I suppose the compiler needs the "100" in order to do a correct dereferencing pointer arithmetic.

I'm going to try what Alex(itsmeandnobodyelse)  says. It has sense after what we have discussed.
This one is on us!
(Get your first solution completely free - no credit card required)
UNLOCK SOLUTION
> Even when in c++ you say double[][100]...

If you are willing to do the pointer arithmetic yourself you can pass a pointer to raw memory with two variables (columns and rows) and treat that memory as if it is contiguously laid out as double[][rows]. Axter has a class which he regularly posted to 2D array questions here on EE (e.g. http:/Q_20946955.html#10779117), which elegantly did exactly that.

[The Marshall Cline excellent mantra is that "Pointers and arrays are evil".]

Author

Commented:
Alex, after some modifications, your first idea works. Apparently, it's not necessary to add the two lines. Anyway, it doesn't compile. I think is necessary to do the following with SizeOf

ntPtr buffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(p2dArray[0])
                              * iRow);
Marshal.Copy(p2dArray, 0, buffer, iRow);

I haven't tried this, because the first syntax works.
>>>> Marshal.SizeOf(p2dArray[0])
Yes, C# Marshal.SizeOf doesn't seem to accept types rather than variables (as C/C++ sizeof does).

>>>> I haven't tried this, because the first syntax works
Seems as the C# array object has the same reference address as its (internal) member array (same as in C++). That's why passing a reference to the p2darray  is equivalent to passing a reference to the temporary 'buffer' object. However, you may consider that C# objects are a subject of garbage collection (and defragmentation). Hence the address passed to C++ may not be safe as it would be a IntPtr returned by Marshal.AllocCoTaskMem (as the first array element is a IntPtr as well, all addresses may be equal and safe at least for the duration of the call).

Regards, Alex

Author

Commented:
Understood. Thanks for your comment. Best regards.
Unlock the solution to this question.
Join our community and discover your potential

Experts Exchange is the only place where you can interact directly with leading experts in the technology field. Become a member today and access the collective knowledge of thousands of technology experts.

*This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

OR

Please enter a first name

Please enter a last name

8+ characters (letters, numbers, and a symbol)

By clicking, you agree to the Terms of Use and Privacy Policy.