使用类型编组从C#调用Fortran子程序

3

我正在尝试使用P/invoke从C#代码调用FORTRAN77子例程 - 如果您有兴趣,我正在尝试包装ARPACK库(http://www.caam.rice.edu/software/ARPACK)提供的某些功能。 我有两个问题。


首先,我无法在任何地方找到有关此上下文中类型编组的清晰说明。 更具体地说,这是我FORTRAN子程序中声明的类型:

       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)

我在这里找到了一些有价值的信息: 如何在Fortran中为字符类型进行MarshalAs?,从中我得出(可能是错误的)结论,我应该使用:
  • [MarshalAs(UnmanagedType.I4)] int 传递整数
  • [MarshalAs(UnmanagedType.LPArray)] byte[] 传递字符字符串

然而,我完全不知道该怎么处理Real数组。 有人对此有任何想法吗?


其次,我对是否应该将参数作为引用传递感到困惑。我绝不熟悉FORTRAN - 我知道,这使得任务有些困难; 然而,只有ARPACK才能做我想做的事情。我确实在某个地方读到过FORTRAN子例程默认将所有参数作为引用的说法。因此,我是否应该将所有参数都作为引用传递?

感谢您的帮助! Guillaume


编辑(8/6/11)

这是我的最终看法:

    [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
    );

我在这里做了几件事情:

  • 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.
这是我的最佳尝试 - 既不是这个,也不是我尝试过的所有其他事情都起作用了。方法会执行,但它退出得非常快(当它应该进行一些相当严重的计算时)。此外,我的三个数组被转换为奇怪的单维数组。以下是调试器给我的内容:

enter image description here

任何想法?

ARPACK真是太棒了。我用Fortran 77代码运行它通过f2c-a转换成C代码,然后进行构建。如果您熟悉C语言,那么您可能会发现这更简单。 - David Heffernan
1
请注意,Fortran在每个字符字符串中传递一个长度变量。它可能会紧随字符字符串之后或在结尾处传递,具体取决于您的编译器和编译器选项。 - bdforbes
1
你的marshalas属性可能没有必要,例如int默认为I4。如果这样,你的矩阵肯定不会被正确地编组。你需要在调试器中运行arpack代码并查看参数如何到达。为什么使用utf8? - David Heffernan
1
根据我的经验,如果您在使用“MarshalAs()”时过度指定参数,则会导致更多的失败。只添加绝对必要的属性。例如,float会自动转换为REAL。另外,请查看您的编译器是否具有“!DEC$ ATTRIBUTE VALUE”规范,以减少*C#*中“ref”参数的数量。 - John Alexiou
2
您不能将多维数组作为 float[,] 传递。此外,数组已经是引用,不需要使用 ref。Fortran 中的 REAL :: A(2,2) 会被传递为具有 4 个元素的 float A[]。按顺序,它们是 A11A21A12A22,因为 FORTRAN 在元素排序方面与 C 不同。 - John Alexiou
显示剩余3条评论
2个回答

4

我建议您从一些小的测试代码开始。编译一个带有简单参数子例程的FORTRAN .dll,并使用 C# 进行调用尝试。此外,您可以将具有多个参数的Fortran 包装到单个结构体(TYPE 关键字)中,这样互操作性就会变得更加容易。

以下是一个可工作的示例,您可以使用它来获取许多如何工作的想法。

原始的FORTRAN 代码:

  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

这段内容涉及实数和整数形式的各种标量和数组值。例如,DROP1 是一个输入 1D 数组,PARRAY 将输出一个二维数组作为一维数组,BLKL 是输入浮点数。

请注意 !DEC$ ATTRIBUTES VALUE 的修饰符,以避免将所有内容声明为 ref

C# 中,该代码通过以下方式调用:

    [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);
    }

注意:我没有传递任何字符串。


1
ja72,感谢您所有的评论和建议。您在这里和那里的提示对我帮助最大,所以我会授予您我的积分。最终一切都因为您的建议而成功了,我只不得不放弃字符串参数——对我来说并非绝对必要。 - guidupuy
1
请注意,!DEC$声明不可移植(尽管由于涉及Windows,可能正在使用IFV,它确实知道这些内容)。您可以使用现代Fortran的iso_c_binding功能以可移植的方式重新实现Fortran,这在任何半新编译器中都得到了广泛实现。 - casey
我知道,但这是一个旧的帖子。 - John Alexiou

1

1) 引用维基百科

单精度,在C语言系列中称为“float”,在Fortran中称为“real”或“real*4”。这是一种二进制格式,占用32位(4字节),其有效数字具有24位的精度(约7个十进制数字)。

因此将其编组为浮点数。您可以测试它,它要么是浮点数,要么是双精度浮点数。

2) 引用Fortran 77教程

Fortran 77使用所谓的按引用调用范例。这意味着,与其仅传递函数/子程序参数的值(按值调用),而是传递参数的内存地址(指针)。

通过引用传递每个参数。


2
Fortran矩阵也是列优先的,别忘了! - David Heffernan
那就将它转换为浮点数。你本可以测试一下,它要么是浮点数,要么是双精度浮点数。 - Michael Ratanapintha
总结一下你们所说的,似乎我应该传递 float[,]double[,] 元素,而不要忘记转置原始数组。我会尝试一下并告诉你们进展如何。 - guidupuy
2
不要将数组作为引用传递,因为它们已经是引用。ref float[] A 是不正确的。 - John Alexiou

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接