locked
passing array of struct with array from C# to c++ RRS feed

  • Question

  • Hello,

     

    I'm new to C# and C++ dll calls and my problem may seem basic to you but I'm stuck at a point where I can't find any solution.

    Here is my c++ code :

     

    // Link following functions C-style (required for plugins)
    extern "C"
    {
    
    	struct BodyPos
    	{
    		UINT id;
    		float pos[3];
    	};
    
    	Receiver* dtrackReceiver;
    
    	EXPORT_API unsigned int getBodies(BodyPos* table)
    	{
    		unsigned int i = 0;
    		if (dtrackReceiver)	
    		{
    			unsigned int nbBody = dtrackReceiver->getNbBodies();
    			BodiesMap bodiesMap = dtrackReceiver->getBodies();
    			
    			for (BodiesMap::iterator it = bodiesMap.begin(); it != bodiesMap.end() ; ++it)
    			{
    				//BodyPos data;
    				table[i].id = it->second.id;
    				table[i].pos[0] = it->second.pos.X();
    				table[i].pos[1] = it->second.pos.Y();
    				table[i].pos[2] = it->second.pos.Z();
    				//table[i] = data;
    				++i;
    			}
    		}
    		return i;
    	}
    }

     

     

    The aim of this code is to fill the table with the data received from the device.

     

    Here is my C# code:

    using UnityEngine;
    using System;
    using System.Runtime.InteropServices;
    
    [StructLayout(LayoutKind.Sequential, Pack=1)]
    public struct BodyPos
    {
    	
    
    	
    	/// \brief Id of the target these data belong to.
    	public uint id ;
    	
    	[MarshalAs (UnmanagedType.ByValArray, SizeConst=3)] 
    	/// \brief Target's position in 3D space (in meters).
    	public float[] pos;
    }
    
    public class ARTController : MonoBehaviour {
    
        BodyPos[] bodies;
    	
    	[DllImport ("DTrackWrapper")]
        private static extern uint getBodies(IntPtr data);
    
    
    	public Transform leftHandNode = null;
    	public Transform rightHandNode = null;
    	public Transform hipsNode = null;
    	public int leftHandBodyId = 0;
    	public int rightHandBodyId = 1;
    	public int hipsBodyId = 2;
    	
    	// Use this for initialization
    	void Start () {
    	  bodies = new BodyPos[50];
          for (int i = 0; i < 50; ++i)
          {
            bodies[i] = new BodyPos();
            bodies[i].pos = new float[3];
          }
    	}
    
      // Update is called once per frame
      void Update()
      {
    		GCHandle pData = GCHandle.Alloc(bodies, GCHandleType.Pinned);
    		Debug.Log("Alloc OK");
    
    		Debug.Log(getBodies(pData.AddrOfPinnedObject()));
            Debug.Log("getBodies OK");
    
    		foreach (BodyPos body in bodies)
    		{
    			Debug.Log("body ok ?");
                Debug.Log(body.id);
    
    			if (body.id == leftHandBodyId)
    			{
    				Debug.Log("body ok");
    				Debug.Log(body.pos[0]);
    
                    Debug.Log("pos ok");
    				float[] pos = body.pos;
    
    				leftHandNode.position = new Vector3(pos[0],pos[1],pos[2]);
                    Debug.Log("pos copied");
    			}
    			Debug.Log("end body");
    		}
    		pData.Free();
    	}
    }
    

     

     

    The code is supposed to pass an array of BodyPos for the C++ code to fill it.

     

    What I obtain in my log is :

    Alloc OK
    
    8
    
    getBodies OK
    
    body ok ?
    
    0
    
    end body
    
    body ok ?
    
    1070630036 // first problem, I don't know where this value comes from, it should be 1 I think
    
    
    end body
    
    body ok ?
    
    2 // found the good ID !!
    
    
    body ok
    
    NullReferenceException: Object reference not set to an instance of an object
     at ARTController.Update () [0x00000] in <filename unknown>:0 
     
    // problem accessing the pos array
    

     

     

    So it seems that I'm able to fill the data in the C++ code ad I find good (and strange) Ids but the pos array seems to be uninitialized. Despite

    bodies[i].pos = new float [3];
    

    Thanks

     

    • Edited by Flonou Thursday, February 3, 2011 2:36 PM removing lots of </br>
    Thursday, February 3, 2011 2:27 PM

Answers

  • Hello Flonou,

     

    1. The structure of BodyPos is such that it does not contain blittable types :

    [StructLayout(LayoutKind.Sequential, Pack=1)]
    public struct BodyPos
    {
     public uint id ;
     [MarshalAs (UnmanagedType.ByValArray, SizeConst=3)]
     public float[] pos;
    }

    1.1 The "pos" field is an array of floats. Such an array is blittable on its own but when used as a field within a struct, it causes the containing struct to be non-blittable.

    1.2 This is so despite the fact that you have used the MarshalAsAttribute to indicate that "pos" is an array of 3 floats. The MarshalAsAttribute would be useful if you used the Marshal class methods to perform the marshaling of the "bodies" array from the managed memory to the unmanaged memory before calling the getBodies() function.

    1.3 Hence I found it strange that the code to create a pinned GCHandle to the "bodies" array actually succeeded :

    GCHandle pData = GCHandle.Alloc(bodies, GCHandleType.Pinned);

    This would normally result in a "System.ArgumentException" exception with the additional message : "Object contains non-primitive or non-blittable data.".

    However, I noted that you have derived the ARTController class from MonoBehaviour. I am not familiar with the MonoBehaviour class but the word "Mono" suggests that you may be developing an application for a non-Windows platform in which the CLR actually approves of the above GCHandle.Alloc() call, I'm really not sure. Please clarify if possible.

     

    2. Given the complexity of the data to be marshaled to the getBodies() function (namely an array of structs in which each struct contains an array of floats), it would normally be necessary to manually perform marshaling to transfer data from a managed object (the array of BodyPos structs) to an unmanaged memory location (allocated via Marshal.AllocHGlobal()).

    2.1 After the data of the managed array of BodyPos structs has been transferred to the unmanaged memory, the getBodies() function is called, with a pointer to the unmanaged memory being passed as parameter.

    2.2 The getBodies() function declaration must also be specified such that data is to be transferred into and out of the function. This is achieved via the use of the [In] and [Out] attributes. More on this later.

    2.3 After getBodies() has been called, the data from the unmanaged memory (which has been filled with new data by the getBodies() function) will need to be transferred back to the managed memory of the "bodies" array.

    2.4 After this last memory transfer is done, the unmanaged memory buffer may be freed.

     

    3. The following is a sample C# function that performs the marshaling of data from the bodies array to an unmanaged memory buffer created via Marshal.AllocHGlobal() :

            unsafe private IntPtr MarshalArray(ref BodyPos[] bodies)
            {
                int iSizeOfOneBodyPos = Marshal.SizeOf(typeof(BodyPos));
                int iTotalSize = iSizeOfOneBodyPos * bodies.Length;
                IntPtr pUnmanagedBodies = Marshal.AllocHGlobal(iTotalSize);
                byte* pbyUnmanagedBodies = (byte*)(pUnmanagedBodies.ToPointer());

                for (int i = 0; i < bodies.Length; i++, pbyUnmanagedBodies += (iSizeOfOneBodyPos))
                {
                    IntPtr pOneBodyPos = new IntPtr(pbyUnmanagedBodies);
                    Marshal.StructureToPtr(bodies[i], pOneBodyPos, false);
                }

                return pUnmanagedBodies;
            }

    3.1 This function requires the use of the "unsafe" keyword and also requires that the /unsafe option be used for the C# project.

    3.2 Basically, we allocate a memory buffer (from the unmanaged heap of the current application) via Marshal.AllocHGlobal(). The size of the buffer is the size of one BodyPos struct multiplied by the number of BodyPos structs inside the "bodies" array. The Marshal.AllocHGlobal() function returns an IntPtr which we need to further manipulate later (more on this next).

    3.3 The returned IntPtr (from Marshal.AllocHGlobal()), i.e. pUnmanagedBodies, needs to be temporarily transformed into a byte pointer. This is necessary because we need to navigate through the unmanaged buffer and manually store copies of BodyPos structs (one each from the "bodies" array) into this buffer.

    3.4 This is the logic behind the for loop :

                for (int i = 0; i < bodies.Length; i++, pbyUnmanagedBodies += (iSizeOfOneBodyPos))
                {
                    IntPtr pOneBodyPos = new IntPtr(pbyUnmanagedBodies);
                    Marshal.StructureToPtr(bodies[i], pOneBodyPos, false);
                }

    as "pbyUnmanagedBodies" is moved to point to the start address of a new BodyPos struct, we transform it back into an IntPtr "pOneBodyPos" and use this IntPtr as the target for the Marshal.StructureToPtr() function.

     

    4. The signature of the getBodies() function also needs to be additionally decorated with the [In, Out] attributes :

            private static extern uint getBodies
                (
                  [In, Out] IntPtr data
                );

    This is so that when getBodies() is called, data is transferred two-ways : that is, original data from the "bodies" array is passed into getBodies() and when the function ends, modified data is transferred back to the unmanaged memory buffer.

     

    5. The following is a sample C# function that performs the unmarshaling of data from the unmanaged memory buffer (which now contains data received from getBodies()) to the "bodies" array :

            unsafe private void UnMarshalArray(IntPtr pUnmanagedBodies, ref BodyPos[] bodies)
            {
                int iSizeOfOneBodyPos = Marshal.SizeOf(typeof(BodyPos));
                byte* pbyUnmanagedBodies = (byte*)(pUnmanagedBodies.ToPointer());

                for (int i = 0; i < bodies.Length; i++, pbyUnmanagedBodies += (iSizeOfOneBodyPos))
                {
                    IntPtr pOneBodyPos = new IntPtr(pbyUnmanagedBodies);
                    bodies[i] = (BodyPos)(Marshal.PtrToStructure(pOneBodyPos, typeof(BodyPos)));
                }

                return;
            }

    5.1 The basic principle of the UnMarshalArray() function is similar to the MarshalArray() but in the opposite direction.

    5.2 Once again we temporarily transform the "pUnmanagedBodies" IntPtr into a byte pointer. This byte pointer is used to navigate through the unmanaged memory buffer. It is always made to point to the start address of a BodyPos struct.

    5.3 As we arrive at a new BodyPos struct item, we create an IntPtr from its address and then use the Marshal.PtrToStructure() function to copy the BodyPos struct contents into the corresponding element of the "bodies" struct.

     

    6. The following listing demonstrates how the MarshalArray() and the UnMarshalArray() functions may be used together with the getBodies() function :

                IntPtr pUnmanagedBodies = MarshalArray(ref bodies);

                getBodies(pUnmanagedBodies);

                UnMarshalArray(pUnmanagedBodies, ref bodies);

                Marshal.FreeHGlobal(pUnmanagedBodies);

     

    Hope the above will be helpful.

    - Bio.

     

    • Marked as answer by eryang Thursday, February 10, 2011 4:15 AM
    Sunday, February 6, 2011 8:28 AM

All replies

  • Hello Flonou,

     

    1. The structure of BodyPos is such that it does not contain blittable types :

    [StructLayout(LayoutKind.Sequential, Pack=1)]
    public struct BodyPos
    {
     public uint id ;
     [MarshalAs (UnmanagedType.ByValArray, SizeConst=3)]
     public float[] pos;
    }

    1.1 The "pos" field is an array of floats. Such an array is blittable on its own but when used as a field within a struct, it causes the containing struct to be non-blittable.

    1.2 This is so despite the fact that you have used the MarshalAsAttribute to indicate that "pos" is an array of 3 floats. The MarshalAsAttribute would be useful if you used the Marshal class methods to perform the marshaling of the "bodies" array from the managed memory to the unmanaged memory before calling the getBodies() function.

    1.3 Hence I found it strange that the code to create a pinned GCHandle to the "bodies" array actually succeeded :

    GCHandle pData = GCHandle.Alloc(bodies, GCHandleType.Pinned);

    This would normally result in a "System.ArgumentException" exception with the additional message : "Object contains non-primitive or non-blittable data.".

    However, I noted that you have derived the ARTController class from MonoBehaviour. I am not familiar with the MonoBehaviour class but the word "Mono" suggests that you may be developing an application for a non-Windows platform in which the CLR actually approves of the above GCHandle.Alloc() call, I'm really not sure. Please clarify if possible.

     

    2. Given the complexity of the data to be marshaled to the getBodies() function (namely an array of structs in which each struct contains an array of floats), it would normally be necessary to manually perform marshaling to transfer data from a managed object (the array of BodyPos structs) to an unmanaged memory location (allocated via Marshal.AllocHGlobal()).

    2.1 After the data of the managed array of BodyPos structs has been transferred to the unmanaged memory, the getBodies() function is called, with a pointer to the unmanaged memory being passed as parameter.

    2.2 The getBodies() function declaration must also be specified such that data is to be transferred into and out of the function. This is achieved via the use of the [In] and [Out] attributes. More on this later.

    2.3 After getBodies() has been called, the data from the unmanaged memory (which has been filled with new data by the getBodies() function) will need to be transferred back to the managed memory of the "bodies" array.

    2.4 After this last memory transfer is done, the unmanaged memory buffer may be freed.

     

    3. The following is a sample C# function that performs the marshaling of data from the bodies array to an unmanaged memory buffer created via Marshal.AllocHGlobal() :

            unsafe private IntPtr MarshalArray(ref BodyPos[] bodies)
            {
                int iSizeOfOneBodyPos = Marshal.SizeOf(typeof(BodyPos));
                int iTotalSize = iSizeOfOneBodyPos * bodies.Length;
                IntPtr pUnmanagedBodies = Marshal.AllocHGlobal(iTotalSize);
                byte* pbyUnmanagedBodies = (byte*)(pUnmanagedBodies.ToPointer());

                for (int i = 0; i < bodies.Length; i++, pbyUnmanagedBodies += (iSizeOfOneBodyPos))
                {
                    IntPtr pOneBodyPos = new IntPtr(pbyUnmanagedBodies);
                    Marshal.StructureToPtr(bodies[i], pOneBodyPos, false);
                }

                return pUnmanagedBodies;
            }

    3.1 This function requires the use of the "unsafe" keyword and also requires that the /unsafe option be used for the C# project.

    3.2 Basically, we allocate a memory buffer (from the unmanaged heap of the current application) via Marshal.AllocHGlobal(). The size of the buffer is the size of one BodyPos struct multiplied by the number of BodyPos structs inside the "bodies" array. The Marshal.AllocHGlobal() function returns an IntPtr which we need to further manipulate later (more on this next).

    3.3 The returned IntPtr (from Marshal.AllocHGlobal()), i.e. pUnmanagedBodies, needs to be temporarily transformed into a byte pointer. This is necessary because we need to navigate through the unmanaged buffer and manually store copies of BodyPos structs (one each from the "bodies" array) into this buffer.

    3.4 This is the logic behind the for loop :

                for (int i = 0; i < bodies.Length; i++, pbyUnmanagedBodies += (iSizeOfOneBodyPos))
                {
                    IntPtr pOneBodyPos = new IntPtr(pbyUnmanagedBodies);
                    Marshal.StructureToPtr(bodies[i], pOneBodyPos, false);
                }

    as "pbyUnmanagedBodies" is moved to point to the start address of a new BodyPos struct, we transform it back into an IntPtr "pOneBodyPos" and use this IntPtr as the target for the Marshal.StructureToPtr() function.

     

    4. The signature of the getBodies() function also needs to be additionally decorated with the [In, Out] attributes :

            private static extern uint getBodies
                (
                  [In, Out] IntPtr data
                );

    This is so that when getBodies() is called, data is transferred two-ways : that is, original data from the "bodies" array is passed into getBodies() and when the function ends, modified data is transferred back to the unmanaged memory buffer.

     

    5. The following is a sample C# function that performs the unmarshaling of data from the unmanaged memory buffer (which now contains data received from getBodies()) to the "bodies" array :

            unsafe private void UnMarshalArray(IntPtr pUnmanagedBodies, ref BodyPos[] bodies)
            {
                int iSizeOfOneBodyPos = Marshal.SizeOf(typeof(BodyPos));
                byte* pbyUnmanagedBodies = (byte*)(pUnmanagedBodies.ToPointer());

                for (int i = 0; i < bodies.Length; i++, pbyUnmanagedBodies += (iSizeOfOneBodyPos))
                {
                    IntPtr pOneBodyPos = new IntPtr(pbyUnmanagedBodies);
                    bodies[i] = (BodyPos)(Marshal.PtrToStructure(pOneBodyPos, typeof(BodyPos)));
                }

                return;
            }

    5.1 The basic principle of the UnMarshalArray() function is similar to the MarshalArray() but in the opposite direction.

    5.2 Once again we temporarily transform the "pUnmanagedBodies" IntPtr into a byte pointer. This byte pointer is used to navigate through the unmanaged memory buffer. It is always made to point to the start address of a BodyPos struct.

    5.3 As we arrive at a new BodyPos struct item, we create an IntPtr from its address and then use the Marshal.PtrToStructure() function to copy the BodyPos struct contents into the corresponding element of the "bodies" struct.

     

    6. The following listing demonstrates how the MarshalArray() and the UnMarshalArray() functions may be used together with the getBodies() function :

                IntPtr pUnmanagedBodies = MarshalArray(ref bodies);

                getBodies(pUnmanagedBodies);

                UnMarshalArray(pUnmanagedBodies, ref bodies);

                Marshal.FreeHGlobal(pUnmanagedBodies);

     

    Hope the above will be helpful.

    - Bio.

     

    • Marked as answer by eryang Thursday, February 10, 2011 4:15 AM
    Sunday, February 6, 2011 8:28 AM
  • Bio

    Thanks for this post I have spent quite a bit of time trying to figure out how to pass arrays in structures to unmanaged code.  Your description above was very helpful and I can now pass 1d arrays from c# to my unmanaged dll.  Specifically I'm using a Fortran dll rather than c or c++ and trying to call it from .net.  I have a couple questions. 

    Can you pass 2d arrays inside structures?  If I declare a 2d array in the structure like the following I get garbage back using your method above to pass the structure.

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct BodyPos
        {
          public int id;
          [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] public float[] pos;
          [MarshalAs(UnmanagedType.ByValArray, SizeConst = 9)] public float[,] pos2d;
        }
       
       BodyPos[] bodies = new BodyPos[1];
       bodies[0].pos2d = new float[3, 3];
    

     

    Also, can you pass strings inside structures?  It seems like you can't modify strings in the unmanaged code and you can't include a stringbuilder class inside a structure.

    I don't need an array of structures just a structure containing arrays and strings.

    Thanks

    Monday, June 27, 2011 9:48 PM
  • Hello craig.h,

     

    1. Using 2-Dimensional Arrays in Structures.

    >> Can you pass 2d arrays inside structures? 

    Yes, this is possible. But there are several issues involved :

    1.1 The 2D-array struct field, when declared as follows :

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 9)] public float[,] pos2d;

    will, at runtime, be transformed into a C-style 2D-array. This C-style array data storage matches the C#-style array data storage.

    An important issue, therefore, is how the Fortran compiler arranges its multi-dimensional arrays. If its arrangement matches that of the C/C++ standards, then there will be no problems. There will be trouble otherwise. Please refer to section 2 for a short primer on C-style arrays.

    1.2 Another problem is with regards to how the array is to be passed to the unmanaged API :

    1.2.1 Is it to be passed by value (i.e. the containing struct is an "in" parameter) ?

    1.2.2 Is it to be passed as a return parameter (i.e. the containing struct is an "out" parameter) ?

    1.2.3 Is it to be passed by reference parameter (i.e. the containing struct is an "in, out" or "ref" parameter) ?

    These factors are important. They are further compounded by the C-style array problem.

     

    2. A Short Primer on C-style Arrays.

    2.1 Note that a C-style array is such that all its elements are stored sequentially in memory. When it comes to being multi-dimenional, a C-style array stores its data exactly the same way as a single-dimensional array of the same size. For example :

    int int_array_1[3][5];
    int int_array_2[15];

    Here, "int_array_1" and "int_array_2" are logically the same. Their data contents are stored in exactly the same manner.

    2.2 For example, a 2-D C-style array with values like the following :

    int_array_1[0][0] = 0;
    int_array_1[0][1] = 1;
    int_array_1[0][2] = 2;
    int_array_1[0][3] = 3;
    int_array_1[0][4] = 4;
    int_array_1[1][0] = 5;
    int_array_1[1][1] = 6;
    int_array_1[1][2] = 7;
    int_array_1[1][3] = 8;
    int_array_1[1][4] = 9;
    int_array_1[2][0] = 10;
    int_array_1[2][1] = 11;
    int_array_1[2][2] = 12;
    int_array_1[2][3] = 13;
    int_array_1[2][4] = 14;

    will have its data laid out in memory as follows :

    0x0000 0x0001 0x0002 0x0003 0x0004 0x0005 0x0006 0x0007 0x0008 0x0009 0x000A 0x000B 0x000C 0x000D 0x000E

    This will be no different from the memory layout of a single-dimensional array of the same size :

    int_array_2[0] = 0;
    int_array_2[1] = 1;
    int_array_2[2] = 2;
    int_array_2[3] = 3;
    int_array_2[4] = 4;
    int_array_2[5] = 5;
    int_array_2[6] = 6;
    int_array_2[7] = 7;
    int_array_2[8] = 8;
    int_array_2[9] = 9;
    int_array_2[10] = 10;
    int_array_2[11] = 11;
    int_array_2[12] = 12;
    int_array_2[13] = 13;
    int_array_2[14] = 14;

    In C/C++, multidimensional array syntax (i.e. the use of "[]" square brackets for index referencing) merely forms an abstraction for programmers. This abstraction is provided by the compiler for convenience purposes. This, however, often gives the illusion of complexity of storage which is not true. Index-wise, the data is simply laid out in memory in sequence.

     

    3. How to Proceed.
    3.1 Concerning the Fortran style of array data storage, it would be good if you could determine how this is done. If the storage method differs from C-style, the following are suggestions :

    3.1.1 Write a C/C++ DLL that serves as a bridge between C# and Fortran (assuming that you have had success in passing 2-D arrays from C/C++ to Fortran).

    3.1.2 Manually marshal the C# 2-D array data across to Fortran. This could be quite challenging but is ultimately possible.

    3.1.3 Use SAFEARRAYs. However, I would not recommend using SAFEARRAYs for multi-dimensional arrays. This is because the dimensional and bounds information of a SAFEARRAY generated by the interop marshaler is completely in reverse of standard COM conventions.

    3.2 Also important is how the containing struct is to be passed to the unmanaged API (see point 1.2 above). Please advise us on this.

     

    - Bio.

     


    Please visit my blog : http://limbioliong.wordpress.com/
    Tuesday, June 28, 2011 4:53 AM
  • I am not sure that the Pack=1 is the right setting.
    Normally it is 4 for 32 bit systems and 8 for x64 bit systems.
    But of course it depends on yur C++ dll.

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    Tuesday, June 28, 2011 9:49 AM
  • Fortran does handle arrays different.  Fortran has a reverse order for rows vs columns.  See the following site for the order:
    http://support.microsoft.com/kb/27780
    It seems like I need to be careful and switch my rows and columns in Fortran vs c# but does this completely explain the problem?  I was passing a 3 x 3 array so the number of rows and columns is the same and there should have been a spot in memory for each element even though the values might not have been in the correct place.  I got garbage back from Fortran.

    I have several programs I need to use this for so I will possibly need to pass by all three "in", "out", "ref".  It seems like if I could get the "ref" to work that would handle the other two.  Is this bad practice?  Will the "ref" be significantly more difficult?  If it's easier to do either "in" or "out" and not "ref" I would need to look through some code to see if this would cover what I need.

    Maybe we should focus on one thing at a time but do you have comments on the question of passing strings in structures?

    Thanks
    Craig

    Tuesday, June 28, 2011 4:59 PM
  • Hello Craig,

     

    1. Some Words of Advise For Now.

    1.1 Thanks for the link to the documentation on Fortran 2-D arrays.

    1.2 For now, there are 2 advise that I believe may be helpful to you.

    1.3 These are outlined in the sections that follow.

     

    2. Ensure Matching Struct Member Alignment.

    2.1 Ensure that the struct member alignment used in your Fortran program matches that used in the C# program.

    2.2 The C# BodyPos struct is declared to be of 1 byte alignment. Ensure this is the case for the Fortran program.

     

    3. Test with a Simple 2-D array of Integers.

    2.1 Use a simple 2-D array of integers for testing.

    2.2 The purpose of this is to become familiar with the Fortran style of array data arrangement.

    2.3 Furthermore, use sequential values for the 2-D integer array. Listed below is a simple C++ example that displays the array data. Emulate it in Fortran.

    // Emulate the following C++ function in Fortran.
    // The purpose is to simply display all values
    // of the arrays (including the 2-D array).
    void __stdcall DisplayTestStruct01(TestStruct01 test_struct_01)
    {
      int i, j;
     
      printf ("test_struct_01.m_id : [%d].\r\n", test_struct_01.m_id);
     
      for (i = 0; i < 3; i++)
      {
        printf ("test_struct_01.m_pos[%d] : [%d].\r\n", i, test_struct_01.m_pos[i]);
      }
     
      for (i = 0; i < 3; i++)
      {
        for (j = 0; j < 3; j++)
        {
          printf ("test_struct_01.m_pos2d[%d][%d] : [%d].\r\n", i, j, test_struct_01.m_pos2d[i][j]);
        }
      } 
    }

    2.4 The following is a sample C# program that calls the unmanaged API :

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct TestStruct01
    {
      public int m_id;
      [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
      public int[] m_pos;
      [MarshalAs(UnmanagedType.ByValArray, SizeConst = 9)]
      public int[,] m_pos2d;
    };

     

    [DllImport("CraigHDLL.dll", CallingConvention = CallingConvention.StdCall)]
    public static extern void DisplayTestStruct01(TestStruct01 test_struct_01);

    static void DisplayTestStruct01InUnmanagedCode()
    {
      int i, j;
      TestStruct01 test_struct_01 = new TestStruct01();

      test_struct_01.m_id = 0;
      test_struct_01.m_pos = new int[3];
      test_struct_01.m_pos2d = new int[3, 3];

      for (i = 0; i < 3; i++)
      {
        test_struct_01.m_pos[i] = i;
      }

      // Assign sequential values to the elements of
      // the 2-D array.
      for (i = 0; i < 3; i++)
      {
        for (j = 0; j < 3; j++)
        {
          test_struct_01.m_pos2d[i, j] = j + (i * 3);
        }
      }

      DisplayTestStruct01(test_struct_01);
    }

    2.5 The DisplayTestStruct01InUnmanagedCode() C# function above will set sequential values into the m_pos2d 2-D array member of the TestStruct01 structure which is later passed to the unmanaged API.

    2.6 The idea is observe the values of the m_pos2d array. Now, with the m_pos2d array being of integer type and with values set sequentially, the values of the TestStruct01.m_pos2d array is arranged in C# memory as follows :

    test_struct_01.m_pos2d[0][0] : [0]
    test_struct_01.m_pos2d[0][1] : [1]
    test_struct_01.m_pos2d[0][2] : [2]
    test_struct_01.m_pos2d[1][0] : [3]
    test_struct_01.m_pos2d[1][1] : [4]
    test_struct_01.m_pos2d[1][2] : [5]
    test_struct_01.m_pos2d[2][0] : [6]
    test_struct_01.m_pos2d[2][1] : [7]
    test_struct_01.m_pos2d[2][2] : [8]

    Observe how the Fortran program interprets the data of this array. The use of the integer type ensures that 4 bytes are used for each element and the actual in-memory hex values are esy to read and identify.

    2.7 Thereafter, gradually move your way towards using the float type. Perhaps start with an array of just 2 floats and ensure that they are not garbage when received on the Fortran side.

    2.8 Once the Fortran storage of floats on a 2-D array is mastered, we can gradually move on to more challenging stuff. I do forsee that eventually, manual marshaling may be necessary to ensure that the Fortran style 2D-array data arrangement standards are met.

     

    3. Concerning Strings.

    3.1 I'll post again with comments on using strings in structures.

     

    - Bio.

     


    Please visit my blog : http://limbioliong.wordpress.com/


    Wednesday, June 29, 2011 4:47 PM
  • Hello Craig,

     

    1. Concerning Strings Contained in a Struture.

    1.1 There are basically 3 ways that a struct which contains strings can be marshaled from managed to unmanaged code :

    • As inline character array.
    • As a BSTR.
    • As a pointer to a character array.

    These are summarised in the sections that follow.

    1.4 It is also important that the managed structure be decorated with the StructLayoutAttribute together with the "CharSet" argument. This will indicate the character type to use for inline character arrays.

     

    2. As Inline Character Array.

    2.1 To use this technique, declare the string member with the following MarshalAsAttribute :

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=<count of characters>)]

    2.2 In this case, the string member is marshaled across as a character array that is contained within the unmanaged version of the structure.

    2.3 The string is thus fixed in size.

    2.4 As mentioned in point 1.4, the character set to use is specified by the "CharSet" argument for the StructLayoutAttribute (which is applied to the structure).

     

    3. As a BSTR.

    3.1 To use this technique, declare the string member with the following MarshalAsAttribute :

    [MarshalAs(UnmanagedType.BStr)]

    3.2 In this case, the string member is marshaled across as a COM BSTR.

    3.3 The length of the BSTR is not fixed.

    3.4 In the unmanaged code, BSTR APIs must be used for processing the BSTR.

    3.5 If the containing structure is to be an "out" parameter and the containing string is to be allocated from inside unmanaged code, then the ::SysAllocString() API must be used to allocate the BSTR memory for the string.

     

    4. As a Pointer to a Character Array.

    4.1 To use this technique, declare the string member with the following MarshalAsAttribute :

    [MarshalAs(UnmanagedType.LPStr)]

    4.2 In this case, the string member is marshaled across as a pointer to a NULL-terminated ANSI character array.

    4.3 The length of the string is also not fixed. It is NULL-terminated.

    4.4 If the containing structure is to be an "out" parameter and the containing string is to be allocated from inside unmanaged code, then the ::CoTaskMemAlloc() API must be used to allocate the memory for the string. The unmanaged language's internal memory allocation facilities (e.g. "new" in C++ or malloc() in C) cannot be used for the memory allocation.

     

    5. Example Code.

    5.1 The following is a sample managed structure which contains strings :

    [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet=CharSet.Ansi)]
    public struct TestStruct02
    {
      // Marshaled as an inline character array.
      [MarshalAs(UnmanagedType.ByValTStr, SizeConst=21)]
      public string m_szStr;
      // Marshaled as a BSTR.
      [MarshalAs(UnmanagedType.BStr)]
      public string m_bstr;
      // Marshaled as a NULL-terminated character array string allocated by ::CoTaskMemAlloc().
      [MarshalAs(UnmanagedType.LPStr)]
      public string m_lpszString;
    };

     

    5.2 The following is a declaration of a sample function takes a TestStruct02 struct as an "out" parameter :

    [DllImport("CraigHDLL.dll", CallingConvention = CallingConvention.StdCall)]
    public static extern void SetValueForTestStruct02(out TestStruct02 test_struct_02);

    Being an "out" parameter, when the SetValueForTestStruct02() API returns, the "test_struct_02" struct will be filled with values assigned from the unmanaged API.

     

    5.3 The following is a C# method that prepares a TestStruct02 struct to be passed to the SetValueForTestStruct02() API.

    static void SetValueForTestStruct02FromUnmanagedCode()
    {
      TestStruct02 test_struct_02 = new TestStruct02();

      SetValueForTestStruct02(out test_struct_02);

      Console.WriteLine("{0:S}", test_struct_02.m_szStr);
      Console.WriteLine("{0:S}", test_struct_02.m_bstr);
      Console.WriteLine("{0:S}", test_struct_02.m_lpszString);
    }

    After SetValueForTestStruct02() returns, the values test_struct_02 struct is displayed.

     

    5.4 Finally, the code for the SetValueForTestStruct02() itself :

    void __stdcall SetValueForTestStruct02(/*[out]*/ TestStruct02* ptest_struct_02)
    {
      strcpy(ptest_struct_02 -> m_szStr, "Hello World.");
      ptest_struct_02 -> m_bstr = ::SysAllocString(L"Hello Craig. I'm a BSTR.");
     
      char szSomeString[] = "Hello Craig. I'm a string allocated via ::CoTaskMemAlloc().";
      size_t stSize = sizeof(szSomeString);
      ptest_struct_02 -> m_lpszString = (LPSTR)::CoTaskMemAlloc(stSize);
      strcpy(ptest_struct_02 -> m_lpszString, szSomeString);
    }

     

    5.5 Note that when the SetValueForTestStruct02() API returns, the "m_bstr" BSTR member of the unmanaged TestStruct02 struct will be used to construct the managed string for the "m_bstr" member of the managed TestStruct02  structure. After the managed string is constructed, the BSTR will be freed by the interop marshaler using Marshal.FreeBSTR() (which internally calls ::SysFreeString()).

     

    5.6 The "m_lpszString" ANSI character array will be used to construct the managed string for the "m_lpszString" member of the managed TestStruct02 structure. After the managed string has been constructed, the ANSI character array will be freed by the interop marshaler using Marshal.FreeCoTaskMem() (which internally calls ::CoTaskMemFree()).

     

    - Bio.

     


    Please visit my blog : http://limbioliong.wordpress.com/
    Wednesday, June 29, 2011 5:38 PM
  • Bio
    I changed my code to do something similar to what you have above.  I'm now passing a structure with only a 2d array of integers.  In Fortran I print the values of this array which were set in C# to a file.  They are coming in fine.  I'm still passing the structure as an intptr.  Also I found in Fortran where I can declare the structure with PACK = 1.

    The array values are changed in Fortran but still don't get passed back to C#.  In C# before the Fortran call the values in the array are arranged as a 3 x 3 array showing the elements as [0,0] [0,1] etc. when I look at the variable.  After the Fortran call the array just looks like 9 question marks.  Could the problem be in the marshaling as "SizeConst=9" and so it thinks the returned array is a 1d array of 9 values and can't look at it as 3 x 3?  I realize in memory it's the same thing but do I need to do something different in your "UnMarshalArray" function.  I have changed it slightly to only have a single structure rather than an array of structures.

    Thanks
    Craig

    Thursday, June 30, 2011 4:44 PM
  • Bio
    Thanks for the description regarding strings.  It was very informative and nice to have this summarized in one place.

    I was able to get a string to pass in a structure to and from Fortran using the "inline character array" option.  The strings contain a null character so I need to be careful about the lengths of the strings and accounting for that character.  

    I now realize I need to pass an array of strings in a structure.  Is this possible?  Will I have to use a different method than the "inline character array".  I've tried declaring an array of ByValTStr but haven't had any luck.  Do you have suggestions or think this is possible?  I get an error when the "MarshalArray" function tries to determine the size of the structure.

    Craig

    Thursday, June 30, 2011 8:34 PM
  • Hello Craig,

     

    1. Array of C-Style String Pointers  in a Structure.

    1.1 Sincere apologies for my late reply.

    1.2 Yes, this is possible. However, the use of inline character arrays for storing such array of strings is not supported by the interop marshaler.

    1.3 The interop marshaler, however, does support array of strings inside structures. With the right MarshalAsAttributes, such an array can be marshaled across as an array of C-Style character array pointers.

    1.4 The following is an example of such a C# structure :

    [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
    public struct TestStructWithStringArray
    {
      [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.LPStr, SizeConst = 10)]
      public string[] strStringArray;
    };

    1.5 The above C# structure will be marshaled across to C++ as such a structure :

    struct TestStructWithStringArray
    {
      LPCTSTR szStringArray[10];
    };

    Note that the array size of "10" is just for arbitrary example use.

    In effect, the C# string array structure member becomes an inline array of C-style string pointers. Note well that in such a situation, it is the C# side that owns the memory occuppied by the C-style strings. The unmanaged side cannot modify the string pointers or their contents. They are read-only. The interop marshaler will take charge of freeing their memory after the call to the unmanaged side.

    1.6 N.B. However, note well that this marshals across the strings as C-Style NULL-terminated character array pointers. I do not know whether such C-style string pointers are compatible with Fortran.

    1.7 Because of this, I recommend that you use an array of BSTRs instead of C-style strings. The next section demonstates this.

     

    2. Array of BSTRs  in a Structure.

    2.1 As an alternative, the interop marshaler also supports marshaling an array of strings (contained inside a structure) as an array of BSTRs.

    2.2 The following is an example of such a C# structure :

    [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
    public struct TestStructWithBSTRArray
    {
      [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.BStr, SizeConst = 10)]
      public string[] strStringArray;
    };

    Just set the ArraySubType argument to "UnmanagedType.BStr".

    2.3 The above C# structure will be marshaled across to C++ as such a structure :

    struct TestStructWithBSTRArray
    {
      BSTR BSTRArray[10];
    };

    Just like the LPCTSTR case that we saw in the last section, the C# string array structure member becomes an inline array of BSTRs. Similarly, it is the C# side that owns the memory occuppied by the BSTRs. The unmanaged side cannot modify the BSTRs. They are read-only. Again, the interop marshaler will take charge of freeing the BSTR memories after the call to the unmanaged side.

    2.4 The way to proceed next is to use the BSTR-management tools in Fortran to convert the BSTRs into Fortran strings. At minimum, BSTRs must be supported by all COM-aware programming languages and so very likely there would be various Fortran intrinsic functions to manipulate them.

     

    - Bio.

     


    Please visit my blog : http://limbioliong.wordpress.com/
    Wednesday, July 6, 2011 3:26 AM
  • Bio
    No problem with the late reply I understand if you were busy.

    It sounds like my only options with string arrays are Read Only.  Did you see my other post (I had two in a row)?  My post (Thursday, June 30, 2011 4:44 PM) was a follow-up to the 2 dimensional array in structures question.

    In addition I have a couple other questions.
    1. Since we are using the "unsafe" context and your MarshalArray function to pass the structure with arrays am I correct that this is only possible in C#, not VB.net?  Some of my colleagues are more familiar with VB syntax and were asking if it can be done in VB.net.

    2. Do you know the difference between the memory management of .net vs the old VB5 or VB6?  My colleagues are also asking why we could call our Fortran dlls from the old VB5 and not VB.net.  I don't have a very good understanding of the differences and couldn't provide a very good answer.  All I've been able to say is that the memory management is probably significantly different.

    Thanks
    Craig

    Wednesday, July 6, 2011 4:04 PM
  • Bio
    Anymore thoughts on the 2 d arrays?  See my posts:
    Thursday, June 30, 2011 4:44 PM
    Wednesday, July 06, 2011 4:04 PM

    I understand if you can't think of anything else to try.  Do you think I need to resort to trying safearrays?

    Thank
    Craig

    Wednesday, July 20, 2011 6:29 PM