【发布时间】:2019-01-08 01:15:08
【问题描述】:
这个问题以前有人问过,我从复习中受益匪浅。尽管如此,我仍然错过了这个过程中的一些关键步骤。 我开发了一个 Windows 窗体应用程序,其中包含几个在 C# 编写的 gui 控制下运行的 Fortran 可执行文件。 Fortran 程序使用 Intel Visual Fortran 编译,Visual Studio 2015 使用 .NET 4.5 编译 C# 代码。我现在正试图将其中一个 Fortran 程序转换为 DLL。这是我第一次尝试开发和实现 DLL,但我无法正确处理所有问题。希望这个社区中的某个人可以引导我朝着正确的方向前进。
DLL 中唯一公开的元素是具有以下接口的子例程:
subroutine PlanetState(path, n, date, id, mu, s, errmsg)
character(n), intent(in) :: path ! the path name to an ephemeris file
integer, intent(in) :: n ! the length of path
real(8), intent(in) :: date ! a Julian date
integer, intent(in) :: id ! a planet number
real(8), intent(out) :: emu ! a parameter used in the calculations
real(8), intent(out) :: s(9) ! an array of state variables
character(256), intent(out) :: errmsg ! 256-byte error message that may be returned
end subroutine PlanetState
这个和几个支持例程都嵌入在 Fortran 模块中。开发了一个单独的 Fortran 主程序来调用和测试 PlanetState 子例程,以确保代码在直接调用中正常工作。完成后,intel 编译器的编译器指令被添加到子例程的声明部分的顶部,如下所示:
!DEC$ ATTRIBUTES DLLEXPORT :: PlanetState
!DEC$ ATTRIBUTES ALIAS: 'PlanetState' :: PlanetState
!DEC$ ATTRIBUTES REFERENCE :: Path, N, Date, PlanID, Emu, S, ErrMsg
该模块随后被重新编译并成功链接到名为 DEeph.dll 的 dll 文件。
在 C# 应用程序中,添加了一个类作为 P/Invoke 代码的包装器,如下所示:
using System.Runtime.InteropServices;
namespace myNamespace
{
public static class FortranDlls
{
[DllImport("DEeph.dll", EntryPoint = "PlanetState", CallingConvention = CallingConvention.Cdecl)]
extern public static void PlanetState(char[] path, ref int n, ref double date, ref int planId,
out double emu, out double[] s, out char[] errMsg);
}
}
在声明和初始化in参数和声明out参数之后,C#调用子程序PlanetState如下:
try
{
FortranDlls.PlanetState(dePath, ref n, ref date, ref id, out emu, out state, out errorMsg);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "DLL Error", MessageBoxButtons.OK);
}
最后,将 DLL 本身添加到项目解决方案中,并将属性“如果较新则复制”分配为构建操作。然后我成功地重建了解决方案,但在执行时,程序在调用 PlanetState 时中断,并显示错误消息“发生致命执行引擎错误”。给出了 0xc0000005 的错误代码,我相信这是 AccessViolationException 的代码。调用后的 catch 子句未执行。
显然,在将 dll 正确合并到项目中时,我搞砸了或忽略了一些步骤。谁能指出问题的原因。感谢您提供的任何帮助。
【问题讨论】:
-
在构建部分的项目属性下,有一个“允许不安全代码”复选框,我相信如果您在 .net dll 之外调用它可能会进行直接内存调用,所以我会尝试将此设置为 true(选中)。
-
我检查了构建属性,发现复选框被选中了。
-
双方的
OUT语义是否相同?谁分配 OUT 数组?调用者还是被调用者?如果它是指向指针的 OUT 指针,则被调用者将进行分配。但我不确定数组,我对 Fortran 几乎一无所知。 -
@BitTickler out 参数的语义两边是一样的。 out 参数在调用程序中声明。 Fortran 通过引用传递所有参数,我知道 C# 默认通过引用传递数组。 out char 数组 ErrMsg 是 256 字节的固定长度,两边都这样定义。
-
您不能随意省略 [DllImport] 声明中的最后 3 个参数。它需要出双,出双和StringBuilder。最后一个必须不出来,并在调用站点使用 new StringBuilder(257) 正确初始化。