C# array of strings to a Fortran DLL

C# array of strings to a Fortran DLL

===================

I am trying to pass a C# array of strings to a Fortran DLL.

So far, passing a single string is successful. The problem arises when the using a string[] array, in that only the first array index can be read. I can successfully pass an array in type int[] or float[] and iterate over the arrays in the Fortran code.

I have tried using the StringBuilder but this has the same result. Ideally, I would declare a new struct type with a mixture of types (including string) and pass a struct array as a parameter.

Any help, source code examples would be greatly appreciated.

====================

Panos P

6 posts / 0 new
Last post
For more complete information about compiler optimizations, see our Optimization Notice.

Panos,

Are you trying to pass fixed-size strings?  If yes, you can try along the lines of the snippet below.  If not, please provide specific details including code sample on the Fortran side.

Say you've in Fortran:

SUBROUTINE MyFortranProc(MyElems, MyString)
    INTEGER(4), PARAMETER :: MYLEN = xx
    INTEGER(4), INTENT(IN) :: MyElems
    CHARACTER(LEN=MYLEN), INTENT(INOUT) :: MyString(MyElems)
     ! Do some processing on MyString
END SUBROUTINE MyFortranProc
 

 

Then in C#, you can do:

 private const Int32 MYLEN = xx;
 
 [DllImport("MyFortran.DLL", CallingConvention = CallingConvention.Cdecl)]
 private static extern void MyFortranProc(ref Int32 MyElems, StringBuilder MyString);
   .
 
   NumElems = yy;
   StringBuilder MyNewString = new StringBuilder(MYLEN*NumElems);
   // build this new StringBuilder string using the existing C# array of strings
   // pad every "element" (up to NumElems) with blanks on the right to a length of MYLEN as necessay
 
   // You should be able to call the Fortran procedure as follows:
   MyFortranProc(ref NumElems, MyNewString); 
 

 

I have not tried the above code, so you may need to "tweak a little" if you run into any errors.

Hope this helps,

 

 

Hi Panos,

I've found passing anything other than simple types between C# and Fortran quite difficult! 

If your struct it not too complex why not add a wrapper function, in C#, which takes the struct and simply passes all the components of the struct as simple types to fortan. Once in fortran you can then reassemble them back to a struct/derived type. Clearly this only allows you to pass element at a time, so to "pass" an array you would first need to allocate it in fortran (as a module variable) then populate it one at a time.

Clearly there is an overhead here - how frequently are you calling this function? If its many, many times from within a loop perhaps its not the approach for you, but if its not so many times its a much simpler way to achieve what you want.

Michael

Thank you for your suggestions.  The data I plan to pass from C# to Fortran dll are quite a lot 55000 rows. Each row has four items. The length of the   items is not constant (it varies 1-10 characters). At the moment, I use the attached subroutine at Fortran side, but this requires 55000 calls from C#!!! .Could I pass them with an easier/faster way?

Thanks

Panos

Attachments: 

AttachmentSize
Downloadtext/plain froutine.txt1.36 KB

Panos,

Since you have a large amount of data to pass through P/Invoke feature in .NET on to the unmanaged memory in Fortran, you may want to investigate .NET Marshal class further, particularly AllocHGlobal method and consider whether use of direct pointers to large chunks of memory make data exchange faster.

For fixed size strings to be used in Fortran (similar to your CHARACTER(10) declaration), here is one approach I have taken for small data exchanges between .NET and Fortran.  For various compiler-related constraints, I have not had a chance to explore ISO_C_BINDING features of modern Fortran,  especially for strings; however, it should work too possibly with minor adjustments.  Also, as I explained earlier, I prefer to use char[] on the C# side for interoperability with Fortran.

Say on the Fortran side, things are defined as:

 

MODULE ProcessStructVecMod
 
   IMPLICIT NONE
  
   PRIVATE
  
   INTEGER, PARAMETER :: LENSTRING = 60
 
   INTEGER, PARAMETER :: NUMELEMS = 10
  
   TYPE MYTYPE_V   
      
       CHARACTER(LEN=LENSTRING) :: StringElem
      
   END TYPE MYTYPE_V
   
CONTAINS
 
   SUBROUTINE ProcessStructVec(StructData, NumElem)
!DEC$ IF DEFINED (WIN64)
   !DEC$ ATTRIBUTES DLLEXPORT, REFERENCE, ALIAS: "ProcessStructVec" :: ProcessStructVec
!DEC$ ELSE
   !DEC$ ATTRIBUTES DLLEXPORT, STDCALL, REFERENCE, ALIAS: "_ProcessStructVec" :: ProcessStructVec
!DEC$ ENDIF
   !DEC$ ATTRIBUTES VALUE :: NumElem
  
      IMPLICIT NONE
    
      !.. Argument list
      INTEGER, INTENT(IN)           :: NumElem
      TYPE(MYTYPE_V), INTENT(INOUT) :: StructData(NumElem)
     
      !.. Locals
      INTEGER :: I
      INTEGER :: LenElem
     
      !.. Process string
      ProcessString: DO I = 1, NumElem
     
          LenElem = LEN_TRIM(StructData(I)%StringElem)
          IF ((LenElem + 39) < LENSTRING) THEN
             StructData(I)%StringElem(LenElem+1:LenElem+39) = ' has traveled to Fortran Dll and back.'
          END IF
         
      END DO ProcessString
     
      !..
      RETURN
     
   END SUBROUTINE ProcessStructVec
 
END MODULE ProcessStructVecMod
 
 

 

C# can invoke this as:

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
 
namespace TestStruct
{
    static class FortranStuff
    {
 
        internal const Int32 LENSTRING = 60;
        internal const Int32 NUMELEMS = 10;
 
        [StructLayout(LayoutKind.Sequential)]
        internal struct MyType_V
        {
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = LENSTRING)]
            internal char[] ShortCharArray;
        }
 
        [DllImport("MyFortran.dll")]
        internal static extern void ProcessStructVec([In, Out] MyType_V[] StructVec, Int32 NumElem);
 
    }
 
    static class PassStruct
    {
        static void Main()
        {
 
            const int LENSTRING = FortranStuff.LENSTRING;
            const int NUMELEMS = FortranStuff.NUMELEMS;
 
            string tmpstring = "";
 
            FortranStuff.MyType_V[] MyVecStruct = new FortranStuff.MyType_V[NUMELEMS];
 
 
 
            for (int i = 0; i < NUMELEMS; i++)
            {
 
                // Load data 
                MyVecStruct[i].ShortCharArray = new char[LENSTRING];
                tmpstring = " Vector Element " + i.ToString();
                tmpstring = tmpstring.PadRight(LENSTRING, ' ');
 
                MyVecStruct[i].ShortCharArray = tmpstring.ToCharArray();
 
            }
 
 
            try 
            {
 
                Console.WriteLine("n" + " --- Pass Struct Test ---" + "n");
 
                Console.WriteLine(" pass struct[]: first five elements before invoking Fortran:");
                for (int i = 0; i < 5; i++)
                {
                    Console.WriteLine(new string(MyVecStruct[i].ShortCharArray, 0, LENSTRING));
                }
 
                // Call Fortran procedure
                FortranStuff.ProcessStructVec(MyVecStruct, MyVecStruct.Length);
 
                Console.WriteLine("n");
                Console.WriteLine(" pass struct[]: first five elements AFTER invoking Fortran:");
                for (int i = 0; i < 5; i++)
                {
                    Console.WriteLine(new string(MyVecStruct[i].ShortCharArray, 0, LENSTRING));
                }
 
                Console.WriteLine("n");
 
            } 
            catch (Exception ex) 
            { 
                Console.WriteLine(ex.Message); 
            } 
 
        }
    }
}

Note if the data are simply being consumed on the Fortran (i.e., the INTENT can be IN) and C# is not expecting modified values back, you can remove [In, Out] attribute on the declaration for Fortran procedure in C#.

Hope this helps,

Hi Panos,

Out of interest are you noticing any significant delay when passing in the "simple" way you currently use (ie how long does it take to pass all 55000 rows?). I routinely pass much larger amounts of data between C# and Fortran (although not usually strings) using just simple types, which will result in well over your 55,000 calls. I’ve never noticed a problem with speed.

I think it probably depends on the nature of the application, but passing in this simple way, while perhaps not being quite as elegant may be fast enough, and will almost certainly end up being simpler to debug and maintain in future. Passing all the data in one go may sound better, but you might encounter other difficulties.

I created a very simple test app which compared the amount of time it took to pass an integer*4 array sized 2,200,000 (10x4x55,000 so the same amount of data as you are passing, but in a form we already know how to pass) in two different ways. Passing it in chunks of 10 (so 4x55000 calls to C#) took 3ms. Passing it all in one go took 0ms. If you make these calls 1000’s of times those milliseconds could add up to something substantial, but if you pass this data once then do something computationally expensive with the data in Fortran (which takes a few orders of magnitude longer than 3ms) then I suspect you can probably spend time optimizing the code elsewhere and leave the passing between languages with the simple method you currently use.

Maybe not the answer to your question, but something to consider…

Cheers,

Michael

Leave a Comment

Please sign in to add a comment. Not a member? Join today