none
Passing structure containing safearrays from VB.net to C++ dll RRS feed

  • Question

  • Hi,

     

    I am trying to pass a structure that contains 2D arrays from VB.net to a C++ dll. The dll returns the structure with the 2D arrays filled. I am using SAFEARRAY to pass the arrays from VB.net to C++. The VB.net code is able to link to the dll, but when the control returns to VB the array is not being populated correctly and it contains random values.

    My code is as follows

    C++ DLL

     

    // Header
    
    #include <Windows.h>
    
    #ifdef EXPORT
    #define DLL_EXPORT __declspec(dllexport)
    #else
    #define DLL_EXPORT __declspec(dllimport)
    #endif
    
    struct Solution1
    {
    	SAFEARRAY	*mass,
    			*volume;
    };
    
    extern "C" DLL_EXPORT int PassingStruct1(Solution1 *sol);
    

    <pre lang="x-cpp">// Main Code
    #include "main.h"
    #include <iostream>
    #include <fstream>
    
    using namespace std;
    
    int PassingStruct1(Solution1 *sol)
    {
    	ofstream out("output.txt");
    	out << "Inside dll" << endl;
    
    	long lLbound = 0;
    	long lUbound = 0;
    	int  value = 0;
    
    	// Get the bounds information of the 2nd dimension.
        	// That is, [][x].
    	 SafeArrayGetLBound(sol->mass, 1, &lLbound);
    	SafeArrayGetUBound(sol->mass, 1, &lUbound);
    	long lDim2Size = lUbound - lLbound + 1;
    
    	// Get the bounds information of the 1st dimension.
        	// That is, [x][].
    	 SafeArrayGetLBound(sol->mass, 2, &lLbound);
    	 SafeArrayGetUBound(sol->mass, 2, &lUbound);
    	 long lDim1Size = lUbound - lLbound + 1;
    
    	 out << "lDim2Size = " << lDim2Size << endl;
    	 out << "lDim1Size = " << lDim1Size << endl;
        
    	 // Obtain values from SafeArray and set its values.
    	 // This should normally be lDim1Size.
    	 for (int i = 0; i < lDim1Size; i++)
    	 {
    	   // This should normally be lDim2Size.
    	   for (int j = 0; j < lDim2Size; j++)
    	   {
    	     long rgIndices[2];
    
    	     rgIndices[0] = j; 
    	     rgIndices[1] = i; 
    	     SafeArrayPutElement(sol->mass, rgIndices, (void FAR*)&value);
    	     value++;
    	  }
    
    	}
    
    	 for (int i = 0; i < lDim1Size; i++)
    	{
    		for (int j = 0; j < lDim2Size; j++)
    		{
    				long	rgIndices[2];
    				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].
    				rgIndices[0] = j;
    				rgIndices[1] = i;
    				SafeArrayGetElement(sol->mass,rgIndices,(void FAR*)&value);
    				out << value << endl;
    		}
    	}
    
    	return	1;
    }
    





    VB.net Code

     

     <DllImport("Structs.dll", CallingConvention:=CallingConvention.Cdecl)> _
        Private Shared Function PassingStruct1(ByRef sol As Solution1) As Integer
    
        End Function
    
    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
        Public Structure Solution1
            <MarshalAs(UnmanagedType.SafeArray)> Public mass(,) As Double
            <MarshalAs(UnmanagedType.SafeArray)> Public volume(,) As Double
        End Structure
    
    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
            Dim sol As Solution1
            Dim ret_val As Integer
    
            ReDim sol.mass(2, 4)
            ReDim sol.volume(2, 4)
            ret_val = PassingStruct1(sol)
    
            If ret_val = 1 Then
                For i = 0 To 2
                    For j = 0 To 4
                        MsgBox(sol.mass(i, j))
                    Next j
                Next i
            End If
        End Sub
    

     


    The output in the file output.txt is as

    Inside dll
    lDim2Size = 3
    lDim1Size = 5
    0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    but the value displayed in the msgbox is not from 0-14 but random numbers.

     

    I would like to know what I am doing wrong? Any help is deeply appreciated.

     

    Thanks in advance

    Kaushik


    • Edited by iamreal Friday, October 7, 2011 5:18 PM
    Friday, October 7, 2011 5:13 PM

Answers

  • Hello Kaushik,

     

    1. The Problems.

    1.1 There are 2 problems here :

    1.1.1 The declaration of the SAFEARRAYs in the Solution1 structure has been declared as containing Double's :

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
        Public Structure Solution1
            <MarshalAs(UnmanagedType.SafeArray)> Public mass(,) As Double
            <MarshalAs(UnmanagedType.SafeArray)> Public volume(,) As Double
        End Structure
    
    


    However, the C++ code that is listed in the OP shows that you are putting in integer values :

    int  value = 0;
    	
    ...
        
    for (int i = 0; i < lDim1Size; i++)
    {
      // This should normally be lDim2Size.
      for (int j = 0; j < lDim2Size; j++)
      {
        long rgIndices[2];
    
        rgIndices[0] = j; 
        rgIndices[1] = i; 
        SafeArrayPutElement(sol->mass, rgIndices, (void FAR*)&value);
        value++;
      }
    }
    
    

    This will cause a serious problem because the data types are not the same as already mentioned by webJose. Hence decide on the correct data type to use and make sure that both the VB side and the C++ side use the same type.

    1.1.2 The second problem has to do with the way the .NET Interop Marshaler reads the dimensional information of the SafeArray. It is completely opposite of that expected by COM conventions. The dimensional indexing expected by the Interop Marshaler is also completely opposite to that expected by COM standards. This is why the data received on the VB end seemed to be inconsistent with the way the values were inserted in C++ using SafeArray APIs.

     

    2. Solution.

    2.1 I have previously written about this particular (array dimensional and indexing) problem and have emerged a solution to it.

    2.2 Please refer to : http://limbioliong.wordpress.com/2011/06/22/passing-multi-dimensional-managed-array-to-c-part-2/

     

    - Bio.

     


    Please visit my blog : http://limbioliong.wordpress.com/
    • Marked as answer by iamreal Wednesday, October 12, 2011 2:24 PM
    Saturday, October 8, 2011 3:23 PM

All replies

  • They are arrays of doubles but you use an int in C++ to fill them.  Since SafeArrayPutElement() will read 8 bytes, the arrays will be filled with 4 bytes-worth of trash.  the variable "value" in your C++ needs to be of type double.
    Jose R. MCP
    Saturday, October 8, 2011 5:08 AM
  • Hello Kaushik,

     

    1. The Problems.

    1.1 There are 2 problems here :

    1.1.1 The declaration of the SAFEARRAYs in the Solution1 structure has been declared as containing Double's :

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
        Public Structure Solution1
            <MarshalAs(UnmanagedType.SafeArray)> Public mass(,) As Double
            <MarshalAs(UnmanagedType.SafeArray)> Public volume(,) As Double
        End Structure
    
    


    However, the C++ code that is listed in the OP shows that you are putting in integer values :

    int  value = 0;
    	
    ...
        
    for (int i = 0; i < lDim1Size; i++)
    {
      // This should normally be lDim2Size.
      for (int j = 0; j < lDim2Size; j++)
      {
        long rgIndices[2];
    
        rgIndices[0] = j; 
        rgIndices[1] = i; 
        SafeArrayPutElement(sol->mass, rgIndices, (void FAR*)&value);
        value++;
      }
    }
    
    

    This will cause a serious problem because the data types are not the same as already mentioned by webJose. Hence decide on the correct data type to use and make sure that both the VB side and the C++ side use the same type.

    1.1.2 The second problem has to do with the way the .NET Interop Marshaler reads the dimensional information of the SafeArray. It is completely opposite of that expected by COM conventions. The dimensional indexing expected by the Interop Marshaler is also completely opposite to that expected by COM standards. This is why the data received on the VB end seemed to be inconsistent with the way the values were inserted in C++ using SafeArray APIs.

     

    2. Solution.

    2.1 I have previously written about this particular (array dimensional and indexing) problem and have emerged a solution to it.

    2.2 Please refer to : http://limbioliong.wordpress.com/2011/06/22/passing-multi-dimensional-managed-array-to-c-part-2/

     

    - Bio.

     


    Please visit my blog : http://limbioliong.wordpress.com/
    • Marked as answer by iamreal Wednesday, October 12, 2011 2:24 PM
    Saturday, October 8, 2011 3:23 PM
  • Thank you webJose and Lim Bio Liong for your help. I was able to get it working. I do have another question in regard to what I am trying to achieve.

    I need to return from my C++ dll a structure that would contain a 4D array of values to the calling program. In order to achieve this I decided use SAFEARRAY and I am able to achieve it in the sample program I posted. I would like to know if there is way to do this by just using an 4D array of double within the structure rather than marshalling it as a SAFEARRAY.

    Thanks

    Kaushik

    Monday, October 10, 2011 2:10 PM
  • Hello Kaushik,

     

    1. >> I would like to know if there is way to do this by just using an 4D array of double within the structure rather than marshalling it as a SAFEARRAY...

    1.1 This is possible but there will be a problem when this array is passed from unmanaged code to managed code as an "out" parameter.

    1.2 This problem will be higjlighted in section 2 below.

     

    2. The Problem with Receiving a Multi-Dimensional Array From Unmanaged Code.

    2.1. It is possible to directly define an array inside a structure, e.g. :

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _
        Public Structure Solution1
        <MarshalAs(UnmanagedType.ByValArray, SizeConst:=24)> Public m_4D_double_array(,,,) As Double
    End Structure
    

    2.1 However, the array must be inline (i.e. it is embedded as part of the memory layout of the structure).

    2.2 The array must also be fixed in size. In the above example, "m_4D_double_array" is the array member and it has been declared to be multi-dimensional. When marshaled across to unmanaged code, it will be fixed to 24 elements. The idea is to treat m_4D_double_array as a 4-D array with dimensional sizes as follows :

    m_4D_double_array(1, 2, 3, 4)
    

    2.3 All this is fine when we want to pass the Solution1 structure as an "in" parameter to some API.

    2.4 However, when the structure is an "out" paramater, there will be trouble. Let's say we have the following API that takes Solution1 as an "out" parameter :

    int __cdecl PassingStruct1(/*[out]*/ Solution1* sol)
    {
      if (sol)
      {
        double m = 0;
       
        for (int i = 0; i < 1; i++)
        {
          for (int j = 0; j < 2; j++)
          {
            for (int k = 0; k < 3; k++)
            {
              for (int l = 0; l < 4; l++)
              {
                (sol -> m_4D_double_array)[i][j][k][l] = (double)m;
                m += 1;
              }
            }
          }
        }
      }
     
      return 0;
    }

    2.5 Let's say we have the following VB code that calls PassingStruct1() :

    Sub Main()
            Dim sol As Solution1
            Dim ret_val As Integer
    
            ret_val = PassingStruct1(sol)
    
            For i = 0 To 0
                For j = 0 To 1
                    For k = 0 To 2
                        For l = 0 To 3
                            MsgBox(sol.m_4D_double_array(i, j, k, l))
                        Next
                    Next
                Next j
            Next i
    
    End Sub
    
    

    Now the problem is : when PassingStruct1() returns, the "m_4D_double_array" member will be transformed into a 1-D array of doubles. Hence there will be a problem when the following code is called :

    MsgBox(sol.m_4D_double_array(i, j, k, l))
    

    The System.IndexOutOfRangeException will be thrown.

    2.6 This is very strange and unexplained. I have also seen it happen in C# code. I am currently thinking of reporting this issue to Microsoft Connect.

    2.7 Hence until we know for sure what is happening, I recommend that you stick with SAFEARRAYs.

    - Bio.

     


    Please visit my blog : http://limbioliong.wordpress.com/
    Tuesday, October 11, 2011 5:46 AM
  • Thanks Bio. I did also try to pass a 4D array of the structure to the API, but when the unmanaged code returned the structure, I was facing the problem you highlighted. The structure got converted to a 1D array and I was receiving an exception.

    I will use safearrays in order to implement my API.


    Thanks once again for your help!

    -Kaushik

    Wednesday, October 12, 2011 2:23 PM