从C#调用Fortran DLL

3
我有一个关于从C#(使用VS 2010)调用Fortran DLL的问题。
我无法使程序运行。当从C#进入Fortran代码并计算“z”(即“x”和“y”的和)的那一行时,会弹出一个消息框。
An unhandled exception of type 'System.AccessViolationException' occurred in WinApp_FortranDLLStruct2.exe
Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt"

我该怎么修复这个问题?

Fortran DLL 的名称为 "TestDLL.DLL"。

Fortran 代码:

MODULE TESTING
    TYPE Point      
            REAL*8 :: x(10)
            REAL*8 :: y(10)
            REAL*8 :: z(10)
    ENDTYPE Point
    end module

    !DEC$ ATTRIBUTES DLLEXPORT::CalcPoint
    !DEC$ ATTRIBUTES ALIAS : "CalcPoint" :: CalcPoint

    SUBROUTINE CalcPoint(myPts)          
    use TESTING
    IMPLICIT NONE 
    INTEGER*4 I,J,NPTS
    REAL*8 Sum_Out
    TYPE(Point) :: myPts

    do i = 1,10
        myPts.z(i) = myPts.x(i) + myPts.y(i)
    enddo

    END SUBROUTINE CalcPoint

C# 代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace WinApp_FortranDLLStruct2 {
    public partial class Form1 : Form {
        [StructLayout(LayoutKind.Sequential)]
        public unsafe struct myPoint {
            public fixed double x[10];
            public fixed double y[10];
            public fixed double z[10];
        }
        public class FortranCall {
            [DllImport("TestDLL.dll")]
            public unsafe static extern void CalcPoint([Out] myPoint t);     
        }

        public Form1()
        {
            InitializeComponent();
        }

        private  unsafe  void  button1_Click(object sender, EventArgs e) {
            int i;
            double d1 = 1.0;

            myPoint T = new myPoint();
            for (i = 0; i < 10; i++) {
                T.x[i] = (i+1)*d1;
                T.y[i] = (i+2)*d1;
            }
            FortranCall.CalcPoint(T);
        }
    }
}

可能是在 [Out] 属性上出现了问题。尝试使用指针代替。 - Dai
@Dai所说的,但是可以将CalcPoint方法包装在一个有标准返回值的方法中。 - Magus
不确定在C#中fixed关键字的工作原理,但是你是否希望将myPoint T作为固定对象?我理解fixed可以防止GC将对象在内存中移动(从而保持指针有效),所以也许当你的Fortran代码尝试从内存中读取时,你的对象已经不存在了。只是一个想法。 - Pedro
我遇到了一个不平衡的栈错误。我试图不使用固定数组,而是使用一个“结构体”的数组代替一个“数组的结构体”。 - John Alexiou
1
你用的是哪个Fortran编译器呢? - Dai
显示剩余5条评论
2个回答

3

加入C调用约定解决了我的堆栈不平衡问题。同时将[Out]改为ref以便指向同一内存,避免复制数值。以下是我的代码:

FORTRAN

MODULE TESTING
    INTEGER, PARAMETER :: SIZE = 10
    TYPE Point
    SEQUENCE
        REAL*8 :: x(SIZE), y(SIZE) , z(SIZE)
    ENDTYPE Point
end module


SUBROUTINE CalcPoint(myPts)
!DEC$ ATTRIBUTES DLLEXPORT::CalcPoint
!DEC$ ATTRIBUTES ALIAS : 'CalcPoint' :: CalcPoint
use TESTING
IMPLICIT NONE 
! Arguments
TYPE(Point), INTENT(INOUT) :: myPts
! Local variables
INTEGER*4 I

do i = 1,SIZE
    myPts%z(i) = myPts%x(i) + myPts%y(i)
enddo

END SUBROUTINE CalcPoint

C#

[StructLayout(LayoutKind.Sequential)]
public unsafe struct myPoint
{        
    public const int size = 10;
    public fixed double x[size];
    public fixed double y[size];
    public fixed double z[size];

    public void Initialize(double d1)
    {
        fixed (double* x_ptr=x, y_ptr=y, z_ptr=z)
        {
            for (int i=0; i<size; i++)
            {
                x_ptr[i]=(i+1)*d1;
                y_ptr[i]=(i+1)*d1;
                z_ptr[i]=0.0;
            }
        }
    }
}

class Program
{
    [DllImport(@"FortranDll1.dll", CallingConvention=CallingConvention.Cdecl)]
    public unsafe static extern void CalcPoint(ref myPoint t);

    unsafe Program()
    {
        double d1=1.0;
        var T=new myPoint();
        T.Initialize(d1);
        Program.CalcPoint(ref T);
        // T.z = {2,4,6,...}
    }

    static void Main(string[] args)
    {
        new Program();
    }
}

附录

要将固定数组转换为托管数组或反之,请使用以下代码

public unsafe struct myPoint
{
    public const int size=10;
    public fixed double x[size];
    ...
    public double[] X
    {
        get
        {
            double[] res=new double[size];
            fixed (double* ptr=x)
            {
                for (int i=0; i<size; i++)
                {
                    res[i]=ptr[i];
                }
            }
            return res;
        }
        set
        {
            if (value.Length>size) throw new IndexOutOfRangeException();
            fixed (double* ptr=x)
            {
                for (int i=0; i<value.Length; i++)
                {
                    ptr[i]=value[i];
                }
            }
        }
    }
}

我修改了Initialize()函数,因为它没有使用int n作为数组长度。 - John Alexiou

0

尝试:

[DllImport("TestDLL.dll")] public unsafe static extern void CalcPoint(ref myPoint t);

如果这不起作用,请尝试创建一个非常简单的FORTRAN方法,它接受X/Y值并将它们相加并返回总和。这将允许您诊断整个过程是否正常工作。从那里开始增加更多的复杂性,直到它出现问题。这将告诉您是否有其他问题。


这是一个回答还是猜测? - PVitt
感谢您对这个问题的所有回复。由于我是C#的新手,我将尝试所有这些选项。我确实尝试了“ref”选项,但它产生了相同的错误。再次感谢。我会让您知道情况。 - user3307346

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