//
you're reading...
Interop Marshaling

Passing Multi-Dimensional Managed Array To C++ Part 2

1. Introduction.

1.1 This blog forms part 2 of a series of blogs which explores techniques for passing multi-dimensional managed arrays to C++ applications.

1.2 We saw in part 1 how the distinctive feature of the layout of a C/C++ array enabled the seamless access of the data of a multi-dimensional managed array from a C++ program. Cool.

1.3 In this part 2, we shall continue our exploration into the passing of a multi-dimensional managed array to C++. This time, the focus is on the use of the SafeArray.

2. A Primer on SafeArrays.

2.1 I normally strongly advocate the use of SafeArrays for transfer of arrays between managed and unmanaged code. This is the most natural unmanaged type to use because a SafeArray, like a managed array, is self-contained. It intrinsically embeds information on the type of the contained data, the number of dimensions in the array as well as the number of elements per dimension.

2.2 In this section, I shall provide a primer on how SafeArrays are used in general in unmanaged code.

2.3 Listed below is a summary of the features of a SafeArray :

  • A SafeArray intrinsically contains type information, dimensional and bounds information.
  • A SafeArray’s data is laid out in memory C-style.
  • A SafeArray’s indexing procedures can be unintuitive and require special attention.

These features are discussed in greater length in the sections that follow.

3. Type, Dimensional and Bounds Information.

3.1 When a SafeArray is created, its type, dimensional and bounds information must be specified. An example code is given below :

  SAFEARRAY*		pSafeArray = NULL;
  // Array of 3 SAFEARRAYBOUND structs. One for each dimension.
  SAFEARRAYBOUND	rgsabound[3];
  // Set the bounds information.
  // The following sets an array with the following dimensions :
  // [2][3][4].
  //
  // 24 elemenst in total. All elements laid out in
  // memory sequentially and contiguously.
  rgsabound[0].lLbound = 0;
  rgsabound[0].cElements = 4;
  rgsabound[1].lLbound = 0;
  rgsabound[1].cElements = 3;
  rgsabound[2].lLbound = 0;
  rgsabound[2].cElements = 2;
  // Create the SAFEARRAY.
  pSafeArray = SafeArrayCreate
  (
    VT_I4, // This signifies the integer type.
    3,  // This signifies 3 dimensions.
    rgsabound  // Bounds information.
  );

A SafeArray is created via the SafeArrayCreate() API. The first parameter indicates the type of the data that will be stored in the array. The second parameter indicates the number of dimensions. The last parameter “rgsabound” contains the bounds information of each dimension

3.2 The “rgsabound” bounds information parameter is an array of SAFEARRAYBOUND structures. As can be seen in the above code, this structure contains only 2 fields : “lLbound” and “cElements”. These indicate the lower bound value (e.g. 0) and the count of elements a particular dimension contains.

3.3 These are fine and intrinsic. What is not intrinsic is the how each element of the “rgsabound” array maps to the dimensions of the array. Observe the following code (taken from the the listing in point 3.1) :

  rgsabound[0].lLbound = 0;
  rgsabound[0].cElements = 4;
  rgsabound[1].lLbound = 0;
  rgsabound[1].cElements = 3;
  rgsabound[2].lLbound = 0;
  rgsabound[2].cElements = 2;

In simple terms, this indicates the following type of C-style array :

int array[2][3][4];

The right-most index is specified by rgsabound[0]. The middle index by rgsabound[1] and the left most by rgsabound[2]. This is not very intuitive but it is the way dimensional and bounds information is specified for a SafeArray.

4. A SafeArray’s data is laid out in memory C-style.

4.1 This is by far the most useful feature of a SafeArray. Its data is laid out in memory C-style. Hence no matter how many dimensions there are in a SafeArray, we can always treat the raw data as a C-style one-dimensional array.

5. A SafeArray’s Indexing Is Unintuitive.

5.1 This unfortunate fact of life is best demonstarted by code. Listed below is a fragment of a C++ function which assigns sequential values (0 through 14) to a 2x3x4 3-dimensional SafeArray :

// We use an array of indices to access
// elements of the array.
long	rgIndices[3];
int	value = 0;
// Assign values to the 3-D array of integers.
for (int i = 0; i < 2; i++)
{
	for (int j = 0; j < 3; j++)
	{
		for (int k = 0; k < 4; k++)
		{
		  // The indices of the array are specified
		  // using "rgIndices".
		  // Here the specification indicates that
		  // we want to access the item at array
		  // location : [i][j][k].
		  rgIndices[0] = k;
		  rgIndices[1] = j;
		  rgIndices[2] = i;
		  // Assign "value" to the array according
		  // to element location [i][j][k].
		  SafeArrayPutElement
		  (
			pSafeArray,
			rgIndices,
			(void FAR*)&value
		  );
		  // Increment value by 1 after every assignment.
		  value++;
		}
	}
}

5.2  The indices of a SafeArray is specified by an array of long values, e.g. :

long	rgIndices[3];

Just like the SAFEARRAYBOUND array that was used to specify the dimensional and bounds information that we saw in section 3, the “rgIndices” array specifies the indices in “reverse” order. rgIndices[0] indicates the right-most index. rgIndices[1] indicates the middle index. rgIndices[2] indicates the left-most index :

rgIndices[0] : array[ ][ ][x]
rgIndices[1] : array[ ][x][ ]
rgIndices[2] : array[x][ ][ ]

where 'x' marks the index of the relevant dimension.

This certainly will take some time for a newbie to get used to.

5.3 The SafeArrayPutElement() API is used to insert values into the array. Fortunately, the memory layout of the array data is as expected from a C-style array :

0x0000 0x0001 0x0002 0x0003 ... 0x0010 0x0011 ... 0x0014 0x0015 0x0016 0x0017

This will be so as long as the indexing procedures have been followed through correctly when SafeArrayPutElement() is called.

5.4 You can see the memory layout of a SafeArray by accessing a pointer to the internal buffer of the SafeArray using the SafeArrayAccessData() and SafeArrayUnaccessData() APIs :

	int* pInteger = NULL;
    	SafeArrayAccessData(pSafeArray, (void**)&pInteger);
                // pInteger now points to the internal array buffer
                // of "pSafeArray".
    	SafeArrayUnaccessData(pSafeArray);

5.5 After a SafeArray has been properly created and initialized with values, it can be queried for various information using the various SafeArray APIs. These include SafeArrayGetDim() which returns the number of dimensions of a SafeArray, and SafeArrayGetElement() which retrieves the values of a SafeArray based on the same indexing principles expounded in point 5.2 above.

5.6 Another significant set of APIs which echoes the confusing indexing principles is the SafeArrayGetLBound() and SafeArrayGetUBound() API pair. This is illustrated below using the same 2x3x4 3-dimensional SafeArray that we have been discussing so far :

// Now obtain bounds information.
long lLbound = 0;
long lUbound = 0;
// Get the bounds information of the 3rd dimension.
// That is, [][][x].
SafeArrayGetLBound(pSafeArray, 1, &lLbound);
SafeArrayGetUBound(pSafeArray, 1, &lUbound);
long lDim3Size = lUbound - lLbound + 1;
// Get the bounds information of the 2nd dimension.
// That is, [][x][].
SafeArrayGetLBound(pSafeArray, 2, &lLbound);
SafeArrayGetUBound(pSafeArray, 2, &lUbound);
long lDim2Size = lUbound - lLbound + 1;
// Get the bounds information of the 1st dimension.
// That is, [x][][].
SafeArrayGetLBound(pSafeArray, 3, &lLbound);
SafeArrayGetUBound(pSafeArray, 3, &lUbound);
long lDim1Size = lUbound - lLbound + 1;

5.7 The second parameter to SafeArrayGetLBound() and SafeArrayGetUBound() indicate the dimension the lowerbound or upperbound (respectively) of which is to be obtained. This is, again, in “reverse” order : the lowest value (1) obtains the bound value of the right-most dimension. The highest value (e.g. 3) obtains the bound value of the left-most dimension.

5.8 These lowerbound and upperbound values can be used to calculate the size of specific dimensions as shown in the code above. Note that I have deliberately used intuitive conventions in the naming of the variables that hold the dimension sizes. By “lDim1Size” I mean the left-most dimension and “lDim3Size” I mean the right-most dimension.

5.9 When we loop through the dimensions of the array, we need to remember the “reverse” indexing convention used by the SafeArrayGetElement() API :

// Get elements of the array and display the value.
for (int i = 0; i < lDim1Size; i++)
{
	for (int j = 0; j < lDim2Size; j++)
	{
		for (int k = 0; k < lDim3Size; k++)
		{
			long	rgIndices[3];
			int		value;
			// The indices of the array are specified
			// using "rgIndices".
			// Here the specificatio indicates that
			// we want to access the item at array
			// location : [i][j][k].
			rgIndices[0] = k;
			rgIndices[1] = j;
			rgIndices[2] = i;
			SafeArrayGetElement
			(
				pSafeArray,
				rgIndices,
				(void FAR*)&value
			);
			printf ("%d\r\n", value);
		}
	}
}

5.10 I have provided below a full listing of the function code fragments of which has been used in this section :

void __stdcall Demonstrate3DArrayOfInt()
{
  SAFEARRAY*		pSafeArray = NULL;
  // Array of 3 SAFEARRAYBOUND structs. One for each dimension.
  SAFEARRAYBOUND	rgsabound[3];
  // Set the bounds information.
  // The following sets an array with the following dimensions :
  // [2][3][4].
  //
  // 24 elemenst in total. All elements laid out in
  // memory sequentially and contiguously.
  rgsabound[0].lLbound = 0;
  rgsabound[0].cElements = 4;
  rgsabound[1].lLbound = 0;
  rgsabound[1].cElements = 3;
  rgsabound[2].lLbound = 0;
  rgsabound[2].cElements = 2;
  // Create the SAFEARRAY.
  pSafeArray = SafeArrayCreate
  (
    VT_I4, // This signifies the integer type.
    3,  // This signifies 3 dimensions.
    rgsabound  // Bounds information.
  );

  if (pSafeArray != NULL)
  {
    // We use an array of indices to access
    // elements of the array.
    long	rgIndices[3];
    int		value = 0;
	// Assign values to the 3-D array of integers.
	for (int i = 0; i < 2; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			for (int k = 0; k < 4; k++)
			{
			  // The indices of the array are specified
			  // using "rgIndices".
			  // Here the specification indicates that
			  // we want to access the item at array
			  // location : [i][j][k].
			  rgIndices[0] = k;
			  rgIndices[1] = j;
			  rgIndices[2] = i;
			  // Assign "value" to the array according
			  // to element location [i][j][k].
			  SafeArrayPutElement
			  (
				pSafeArray,
				rgIndices,
				(void FAR*)&value
			  );
			  // Increment value by 1 after every assignment.
			  value++;
			}
		}
	}
	// After assigning values to the array, examine the values
	// of the array data.
	// In this example, the values of the array are laid out
	// as follows :
	// 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
	// 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
	// 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17
	int* pInteger = NULL;

    	SafeArrayAccessData(pSafeArray, (void**)&pInteger);
    	SafeArrayUnaccessData(pSafeArray);
  }

  Examine3DArrayOfInt(pSafeArray);

  ::SafeArrayDestroy(pSafeArray);
  pSafeArray = NULL;
}

void __stdcall Examine3DArrayOfInt(SAFEARRAY* pSafeArray)
{
	// Test to see the total number of dimensions in the SafeArray.
	// This should be 3.
	UINT lDim = SafeArrayGetDim(pSafeArray);
	// Now obtain bounds information.
	long lLbound = 0;
	long lUbound = 0;
    	// Get the bounds information of the 3rd dimension.
    	// That is, [][][x].
	SafeArrayGetLBound(pSafeArray, 1, &lLbound);
	SafeArrayGetUBound(pSafeArray, 1, &lUbound);
	long lDim3Size = lUbound - lLbound + 1;
    	// Get the bounds information of the 2nd dimension.
    	// That is, [][x][].
	SafeArrayGetLBound(pSafeArray, 2, &lLbound);
	SafeArrayGetUBound(pSafeArray, 2, &lUbound);
	long lDim2Size = lUbound - lLbound + 1;
    	// Get the bounds information of the 1st dimension.
    	// That is, [x][][].
	SafeArrayGetLBound(pSafeArray, 3, &lLbound);
	SafeArrayGetUBound(pSafeArray, 3, &lUbound);
	long lDim1Size = lUbound - lLbound + 1;	

	// Get elements of the array and display the value.
	for (int i = 0; i < lDim1Size; i++)
	{
		for (int j = 0; j < lDim2Size; j++)
		{
			for (int k = 0; k < lDim3Size; k++)
			{
				long	rgIndices[3];
				int		value;
				// The indices of the array are specified
				// using "rgIndices".
				// Here the specificatio indicates that
				// we want to access the item at array
				// location : [i][j][k].
				rgIndices[0] = k;
				rgIndices[1] = j;
				rgIndices[2] = i;
				SafeArrayGetElement
				(
					pSafeArray,
					rgIndices,
					(void FAR*)&value
				);
				printf ("%d\r\n", value);
			}
		}
	}
}

6. Specifying SafeArray Marshaling in Managed Code.

6.1 To specify that a multi-dimensional array is to be marshaled into unmanged code as a SafeArray, the MarshalAsAttribute is used as usual. This time, the UnmanagedType enumeration to use is “SafeArray”. An additional argument SafeArraySubType is also required to specify the exact type of the SafeArray. The value for this would be one of the VarEnum enumerations. For the integer type this would be “VarEnum.VT_I4”.

6.2 Let’s say we have an unmanaged C++ API declared as follows :

void __stdcall Set2DArrayOfInt02(/*[in]*/ SAFEARRAY* p2DIntArray);

This API takes a pointer to a SAFEARRAY. The C# declaration for such an API would be :

[DllImport("TestDLL.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void Set2DArrayOfInt02
  (
    [In][MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_I4)] int[,] pArrayOfInt
  );

6.3 Listed below is a sample C# code that calls this API :

static void Call_Set2DArrayOfInt02()
{
  // Define a 3x5 2D array of ints.
  int[,] TwoDArrayOfInt = new int[3, 5];
  int k = 0;
  // Fill up values. For ease of checking
  // we shall fill the 2D array with sequential
  // values from 0 through 14.
  for (int i = 0; i < 3; i++)
  {
    for (int j = 0; j < 5; j++)
    {
      TwoDArrayOfInt[i, j] = k++;
    }
  }
  // Call the unmanaged C++ function.
  // Note that the way a managed array
  // arranges its data is very similar
  // to the way a C/C++ program arranges
  // array data : sequentially.
  //
  // The MarshalAsAttribute set for the
  // first parameter of Set2DArrayOfInt02()
  // indicates that the 2D array is to be
  // marshaled as a SafeArray.
  // Hence the Interop Marshaler will internally
  // create a temporary SafeArray, fill it
  // with data from the managed 2D array and
  // then pass a pointer to this SafeArray to
  // the unmanaged API.
  Set2DArrayOfInt02(TwoDArrayOfInt);
}

The above C# function will create a 3×5 2D array of integers, fill it with sequential values (0 through 14) and then call the unmanaged API Set2DArrayOfInt02().

6.4 Internally, what will happen is that the Interop Marshaler will internally create a temporary SafeArray. This SafeArray will then be initialized with dimensional and bounds information. Thereafter, the data from the 2D array will be copied to the SafeArray. Finally a pointer to this SafeArray is passed as parameter to the unmanaged API.

7. A Problem with SafeArrays Generated by the Interop Marshaler.

7.1 Now, when the Interop Marshaler transforms a muti-dimensional managed array into a SafeArray, things turn from strange to bizarre.

7.2 This has to do with the way the Interop Marshaler sets the dimensional information of the SafeArray. It is completely opposite of that expected by COM conventions.

7.3 The dimensional indexing arranged by the Interop Marshaler is also completely opposite to that expected by COM standards.

7.4 The internal array data of the safeArray is arranged in such a way that reflects the above-mentioned “opposite” conventions. Therefore there is consistency in data access but consistency is coding is impossible to achieve. One has to anticipate a SafeArray created by the Interop Marshaler and use a different approach in accessing data.

7.5 Because of the above-mentioned problems, code like the following would produce unexpected data :

long lLbound = 0;
long lUbound = 0;

// Obtain bounds information of the right-most
// dimension.
SafeArrayGetLBound(p2DIntArray, 1, &lLbound);
SafeArrayGetUBound(p2DIntArray, 1, &lUbound);
// The left-most dimensional size is obtained // instead.
long lDim2Size = lUbound - lLbound + 1;

// Obtain bounds information of the left-most
// dimension.
SafeArrayGetLBound(p2DIntArray, 2, &lLbound);
SafeArrayGetUBound(p2DIntArray, 2, &lUbound);
// The right-most dimensional size is obtained // instead.
long lDim1Size = lUbound - lLbound + 1;

The above code is based on the 2D array created by the Call_Set2DArrayOfInt02() C# method as listed in point 6.3 above.

SafeArrayGetLBound(…, 1, …) and SafeArrayGetUBound(…, 1, …) returns bounds information for the left-most dimension instead of the right-most.

SafeArrayGetLBound(…, 2, …) and SafeArrayGetUBound(…, 2, …) returns bounds information for the right-most dimension instead of the left-most.

7.6 Code like the following would also produce completely unexpected data :

// Obtain values from SafeArray and display.
for (int i = 0; i < lDim1Size; i++)
{
  for (int j = 0; j < lDim2Size; j++)
  {
    long	rgIndices[2];
    int		value;

    rgIndices[0] = j;
    rgIndices[1] = i;
    SafeArrayGetElement
    (
      p2DIntArray,
      rgIndices,
      (void FAR*)&value
    );
    printf ("%d\r\n", value);
  }
}

The above code is consistent with COM SafeArray conventions but the following completely unexpected output will be produced :

0
5
10
1
6
11
2
7
12
3
8
13
4
9
14

However, if we have a look at the internal array data of the SafeArray, we will see that the above data is stored contiguously in memory. The Interop Marshaler has created such a buffer.

7.7 The next two sections will provide 2 solutions to this problem. The first one is not recommendable. It provides short term benefits at the expense of long term COM development standards. The second solution requires understanding of .NET Custom Marshaling techniques. It is by far a more superior solution.

8. Solution 1 Quick and Short-Termed.

8.1 One solution is to react to the situation created by the Interop Marshaler and re-arrange bounds and looping limits as in the following code :

void __stdcall Set2DArrayOfInt02(SAFEARRAY* p2DIntArray)
{
    long lLbound = 0;
    long lUbound = 0;

    // Obtain bounds information of the right-most
    // dimension.
    SafeArrayGetLBound(p2DIntArray, 1, &lLbound);
    SafeArrayGetUBound(p2DIntArray, 1, &lUbound);
    long lDim2Size = lUbound - lLbound + 1;

    // Obtain bounds information of the left-most
    // dimension.
    SafeArrayGetLBound(p2DIntArray, 2, &lLbound);
    SafeArrayGetUBound(p2DIntArray, 2, &lUbound);
    long lDim1Size = lUbound - lLbound + 1;

    // Obtain values from SafeArray and display.
    // This should normally be lDim1Size.
    for (int i = 0; i < lDim2Size; i++)
    {
      // This should normally be lDim2Size.
      for (int j = 0; j < lDim1Size; j++)
      {
        long	rgIndices[2];
	int	value;

	rgIndices[0] = i; // This should normally be j.
	rgIndices[1] = j; // This should normally be i.
	SafeArrayGetElement
	(
	  p2DIntArray,
	  rgIndices,
	  (void FAR*)&value
	);
	printf ("%d\r\n", value);
      }
    }
}

The above code will work. But this is at the expense of standard COM development practices.

9. Solution 2 – Superior and Long-Termed.

9.1 This section provides a much better long term solution to the problem. It involves the use of the .NET Custom Marshaling.

9.2 Note that I shall not be expounding on the subject of .NET Custom Marshaling. For this, please refer to MSDN.

9.3 I shall, however, touch on the pertinent parts of the custom marshaler class that will be presented in this section. The code for the custom marshaler is listed below :

public class ManagedArrayToTwoDSafeArray : ICustomMarshaler
{
  [StructLayout(LayoutKind.Sequential)]
  struct SAFEARRAYBOUND
  {
    public UInt32 cElements;
    public Int32 lLbound;
  };

  [DllImport("Oleaut32.dll", CallingConvention = CallingConvention.StdCall)]
  private static extern IntPtr SafeArrayCreate(VarEnum vt, uint cDims, SAFEARRAYBOUND [] rgsabound);

  [DllImport("Oleaut32.dll", CallingConvention = CallingConvention.StdCall)]
  private static extern uint SafeArrayPutElement(IntPtr pSafeArray, UInt32[] rgIndices, IntPtr pv);

  [DllImport("Oleaut32.dll", CallingConvention = CallingConvention.StdCall)]
  private static extern uint SafeArrayDestroy(IntPtr pSafeArray);

  static ManagedArrayToTwoDSafeArray marshaler;

  public object MarshalNativeToManaged(IntPtr pNativeData)
  {
    // Nothing to do.
    return null;
  }

  public IntPtr MarshalManagedToNative(object ManagedObj)
  {
    // Managed Object must be an array.
    if (!(ManagedObj is Array))
    {
      return IntPtr.Zero;
    }

    Array array = (Array)ManagedObj;
    // The number of dimensions must be 2.
    if (array.Rank != 2)
    {
      return IntPtr.Zero;
    }

    int[,] TwoDArray = (int[,])ManagedObj;

    // Array of 2 SAFEARRAYBOUND structs. One for each dimension.
    SAFEARRAYBOUND[] rgsabound = new SAFEARRAYBOUND[2];
    // Arrange the bounds and dimensional information
    // which conforms to COM standards.
    rgsabound[0].lLbound = TwoDArray.GetLowerBound(1);
    rgsabound[0].cElements = (uint)TwoDArray.GetLength(1);
    rgsabound[1].lLbound = TwoDArray.GetLowerBound(0);
    rgsabound[1].cElements = (uint)TwoDArray.GetLength(0);
    // Create the SafeArray.
    IntPtr pSafeArrayOfInt = SafeArrayCreate
    (
      VarEnum.VT_I4, // This signifies the integer type.
      2,  // This signifies 2 dimensions.
      rgsabound  // Bounds information.
    );

    // Assign values to the SafeArray.
    for (int i = TwoDArray.GetLowerBound(0); i < TwoDArray.GetLength(0); i++)
    {
      for (int j = TwoDArray.GetLowerBound(1); j < TwoDArray.GetLength(1); j++)
      {
                UInt32[] rgIndices = new UInt32[2];
                int value = TwoDArray[i, j];
                GCHandle gch = GCHandle.Alloc(value, GCHandleType.Pinned);
                // Indexing should be conformant to COM standards.
	rgIndices[0] = (UInt32)j;
	rgIndices[1] = (UInt32)i;
	SafeArrayPutElement
	(
	  pSafeArrayOfInt,
	  rgIndices,
	  gch.AddrOfPinnedObject()
	);

        gch.Free();
      }
    }    

    return pSafeArrayOfInt;
  }

  public void CleanUpNativeData(IntPtr pNativeData)
  {
    // pNativeData is a pointer to the SafeArray
    // that was passed to the native code.
    // We must now destroy it.
    SafeArrayDestroy(pNativeData);
  }

  public void CleanUpManagedData(object ManagedObj)
  {
    // Nothing to do
  }

  public int GetNativeDataSize()
  {
    return -1;
  }

  public static ICustomMarshaler GetInstance(string cookie)
  {
    // Always return the same instance
    if (marshaler == null)
    {
      marshaler = new ManagedArrayToTwoDSafeArray();
    }

    return marshaler;
  }
}

9.4 The custom marshaler ManagedArrayToTwoDSafeArray essentially uses SafeArray APIs to create a SafeArray that will be passed to any API that takes a SafeArray as input parameter.

9.5 The first method of the marshaler that will be called is MarshalManagedToNative(). This will be called because a managed object (the 2D array) will need to be marshaled to unmanaged code. As part of this marshaling process, the method queries the incoming managed array for bounds information in order to create a SafeArray. It also assigns values to the SafeArray based on data from the managed array itself.

9.6 One limitation of this marshaler is that it limits its use to 2-D arrays. The other limitation is that the integer data type is assumed. However, extending this marshaler for genericity is certainly possible.

9.7 The other method of importance is CleanUpNativeData(). This is where the SafeArray that was created and returned in MarshalManagedToNative() is destroyed. The other methods are trivial and are not used for this example.

9.8 The following is how one would declare an imported API that will use the custom marshaler :

[DllImport("TestDLL.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void Set2DArrayOfInt03
(
  [In][MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ManagedArrayToTwoDSafeArray))]
  int[,] pArrayOfInt
);

9.9 The following is a sample code to call such an API :

static void Call_Set2DArrayOfInt03()
{
  // Create a 3x5 2D array of ints.
  int[,] TwoDArrayOfInt = new int[3, 5];
  int k = 0;
  // Fill up values.
  for (int i = 0; i < 3; i++)
  {
    for (int j = 0; j < 5; j++)
    {
      TwoDArrayOfInt[i, j] = k++;
    }
  }
  // Call the API normally.
  // The custom marshaler will be invoked
  // by the Interop Marshaler due to
  // it being specified as the marshaler
  // for the first parameter of the
  // Set2DArrayOfInt03() API.
  Set2DArrayOfInt03(TwoDArrayOfInt);
}

As written in the comments, the custom marshaler will be invoked when the API is called. This is due to it being declared as the marshaler for the first parameter “pArrayOfInt” of the Set2DArrayOfInt03() API.

10. In Conclusion.

10.1 At the time of this writing, it is unclear why the Microsoft .NET Development Team did not address the “reverse” dimensional and indexing problem that we saw in section 7. Regardless, a solution has been provided in the custom marshaler of section 9. I certainly hope that the reader will benefit from it.

10.2 This concludes our discussion of on the subject of the marshaling of multi-dimensional managed arrays to unmanaged code via SafeArrays.

10.3 I shall be writing more blogs after this in which I will touch on returning multi-dimensional arrays from unmanaged code to managed code. Stay tuned.

Advertisements

About Lim Bio Liong

I've been in software development for nearly 20 years specializing in C , COM and C#. It's truly an exicting time we live in, with so much resources at our disposal to gain and share knowledge. I hope my blog will serve a small part in this global knowledge sharing network. For many years now I've been deeply involved with C development work. However since circa 2010, my current work has required me to use more and more on C# with a particular focus on COM interop. I've also written several articles for CodeProject. However, in recent years I've concentrated my time more on helping others in the MSDN forums. Please feel free to leave a comment whenever you have any constructive criticism over any of my blog posts.

Discussion

One thought on “Passing Multi-Dimensional Managed Array To C++ Part 2

  1. Nothing to add, but your detailed writeup was a huge help – thank you!

    Posted by Al | March 21, 2015, 2:55 pm

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: