Type marshalling to call a fortran subroutine from C#

笑着哭i 提交于 2019-12-20 03:33:09

问题


I'm trying to call a FORTRAN77 subroutine from C# code using P/invoke - in case you're interested, I'm trying to wrap some of the functionality offered by the ARPACK library (http://www.caam.rice.edu/software/ARPACK). I have 2 questions.


First off, I couldn't find clear instructions anywhere regarding type marshalling in this context. More specifically, here are the types that are declared in my FORTRAN subroutine:

       subroutine getEigenVectors
      &   ( Matrix, n, which, nev, ncv, maxn, maxnev, maxncv, ldv, v, d)

 c     %------------------%
 c     | Scalar Arguments |
 c     %------------------%

       character        which*2
       integer          n, nev, maxn, maxnev, maxncv, ldv

 c     %-----------------%
 c     | Array Arguments |
 c     %-----------------%
 c
       Real           
      &                 Matrix(n,n), v(ldv,maxncv), d(maxncv,2)

I found some valuable info here: What Should I MarshalAs for Character Type in Fortran?, from which I implied (I might be wrong) that I should use:

  • [MarshalAs(UnmanagedType.I4)] int to pass in integers
  • [MarshalAs(UnmanagedType.LPArray)] byte[] to pass in character strings

However, I have absolutely no idea what to do with the Real arrays. Does anyone have any idea on that?


Secondly, I am confused as to whether I should pass my arguments as reference or not. I am by no means familiar with FORTRAN - I know, that makes the task a little difficult; however, only ARPACK does what I would like to do.I did read somewhere though that FORTRAN subroutines take all their arguments as reference by default. Should I therefore pass all arguments as references?

Thanks in advance for your help! Guillaume


EDIT (8/6/11)

So here's my final take:

    [DllImport("Arpack.dll", EntryPoint = "#140")]
    private static extern void getEigenVectors(
        [MarshalAs(UnmanagedType.LPArray)] ref float[,] matrix, 
        [MarshalAs(UnmanagedType.I4)] ref int n,
        [MarshalAs(UnmanagedType.LPArray)] ref byte[] which,
        [MarshalAs(UnmanagedType.I4)] int whichLength,
        [MarshalAs(UnmanagedType.I4)] ref int nev, 
        [MarshalAs(UnmanagedType.I4)] ref int ncv,
        [MarshalAs(UnmanagedType.I4)] ref int maxn,
        [MarshalAs(UnmanagedType.I4)] ref int maxnev,
        [MarshalAs(UnmanagedType.I4)] ref int maxncv,
        [MarshalAs(UnmanagedType.I4)] ref int ldv,
        [MarshalAs(UnmanagedType.LPArray)] ref float[,] v,
        [MarshalAs(UnmanagedType.LPArray)] ref float[,] d
    );

Several things I did here:

  • Pass int objects marshalled as UnmanagedType.I4 to match FORTRAN integer objects
  • Pass float[,] objects of size (m, n) and marshalled as UnmanagedType.LPArray to match FORTRAN Real(n,m) objects
  • Pass byte[] objects obtained marshalled as UnmanagedType.LPArray to match FORTRAN Character*n objects. The byte[] objects are computed as follows:

    System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
    byte[] which = encoding.GetBytes(myString);
    
  • Pass an int object BY VALUE and marshalled as UnmanagedType.I4 to indicate the length of the string. Note that I tried to put that argument right after the string as well as at the end of the arguments list.

This is my best shot - neither this nor all the other things I tried worked. The method will execute, however it exits super fast (when it's supposed to be doing some pretty severe computations). Moreover, my 3 arrays are transformed into weird uni-dimensional arrays. Here what the debugger gives me:

Any idea?


回答1:


I suggest you start with some small test code. Compile a FORTRAN .dll with some subroutines with simple parameters and play around with C# to get the calling to work. Also you may wish to wrap the Fortran with many arguments into a single structure (TYPE keyword), which makes the interop so much easier.

Here is a working example that you can use the get many ideas of how it works.

Original FORTRAN code:

  SUBROUTINE CALC2(BLKL,BLKW, N_LAMINA,N_SLICE, LOAD, SLOP,SKW,    &
                    DIA1,DIA2, Y1, Y2, N1, N2, DROP1, DROP2,        &
                    PARRAY, P_MAX, P_MAX_INDEX, ENDEFCT)
  !DEC$ ATTRIBUTES DLLEXPORT :: CALC2
  !DEC$ ATTRIBUTES ALIAS:'CALC2' :: CALC2
  !DEC$ ATTRIBUTES VALUE :: BLKL, BLKW, N_LAMINA, N_SLICE, LOAD, SLOP, SKW
  !DEC$ ATTRIBUTES VALUE :: DIA1, DIA2, Y1, Y2, N1, N2
  IMPLICIT NONE
  INTEGER*4, INTENT(IN) ::N_LAMINA, N_SLICE
  REAL*4, INTENT(IN) :: BLKL, BLKW, LOAD, SLOP, SKW,     &
                        DIA1, DIA2, Y1, Y2, N1, N2,   &
                        DROP1(MAX_LAMINA), DROP2(MAX_LAMINA)
  REAL*4, INTENT(OUT):: PARRAY(MAX_PATCH), P_MAX
  INTEGER*4, INTENT(OUT) :: P_MAX_INDEX, ENDEFCT
  INTEGER*4 :: NDIAG, N_PATCH
  REAL*4 :: SLNG, SWID
  REAL*4 :: DROPS_1(MAX_LAMINA), DROPS_2(MAX_LAMINA)

...

  END SUBROUTINE CALC2

with various scalar and array values in real and integer form. For example DROP1 is input 1D array. PARRAY is outputing a 2D array as a 1D array. BLKL are input floats.

Notice the !DEC$ ATTRIBUTES VALUE decoration to avoid declaring everything as ref.

In C# the code is called by

    [DllImport("mathlib.dll")]
    static extern void CALC2(float major_dim, float minor_dim, 
        int N_lamina, int N_slices, float total_load, 
        float slope, float skew, float diameter_1, float diameter_2, 
        float youngs_1, float youngs_2, float nu_1, float nu_2, 
        float[] drops_1, float[] drops_2, float[] pressures, 
        ref float p_max, ref int p_max_index, ref EndEffect end_effect);

...
   {
        float max_pressure = 0;
        int max_pressure_index = 0;
        float[] pressures = new float[Definition.MAX_PATCH];
        EndEffect end_effect = EndEffect.NO;

        CALC2(length, width, lamina_count, slice_count, load, slope, skew, 
            dia_1, dia_2, y_1, y_2, n_1, n_2, drops_1, drops_2, pressures, 
            ref max_pressure, ref max_pressure_index, ref end_effect);
    }

note I do not pass any strings.




回答2:


1) Quoting Wikipedia:

Single precision, called "float" in the C language family, and "real" or "real*4" in Fortran. This is a binary format that occupies 32 bits (4 bytes) and its significand has a precision of 24 bits (about 7 decimal digits).

So marshal it as a float. You could have tested that one, it's either a float or a double.

2) Quoting this Fortran 77 Tutorial:

Fortran 77 uses the so-called call-by-reference paradigm. This means that instead of just passing the values of the function/subroutine arguments (call-by-value), the memory address of the arguments (pointers) are passed instead.

Pass every parameter by reference.



来源:https://stackoverflow.com/questions/6961410/type-marshalling-to-call-a-fortran-subroutine-from-c-sharp

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!