如何将非托管dll添加到C#项目

时间:2018-07-31 15:18:53

标签: c# dll unmanaged

这个问题以前曾被问过,我从回顾中获得了很多好处。但是,我仍然缺少该过程中的一些关键步骤。 我已经开发了一个Windows Forms应用程序,该应用程序由在使用C#编写的gui的控制下运行的几个Fortran可执行文件组成。使用.NET 4.5由Visual Studio 2015使用Intel Visual Fortran和C#代码编译Fortran程序。我现在正在尝试将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参数之后,按如下所示对子例程PlanetState进行C#调用:

   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时程序中断,并显示错误消息“发生FatalExecutionEngineError”。给出了错误代码0xc0000005,我相信这是AccessViolationException的代码。调用后的catch子句未执行。

很显然,我在正确地将dll合并到项目中的过程中搞砸了或忽略了一些步骤。任何人都可以指出问题的原因。感谢您提供的任何帮助。

1 个答案:

答案 0 :(得分:0)

经过几天的进一步实验,我已经解决了大多数问题。解决该问题的早期关键是意识到我需要设置调试器属性以允许本机代码调试。该属性在VS项目属性/调试窗口中设置。有了此设置,当调试器从主程序(C#)过渡到dll时,我就可以跟踪其执行情况。这使我可以将错误跟踪到读取直接访问文件的第一条记录。读取的语句如下所示:

read (3, rec = 1) ttl, ((Ipt(I, J), I = 1, 3), J = 1, 12), ...

其中ttl是整数倍数组,而Ipt是3 x 13整数数组。在检查中断发生后的值时,我发现ttl包含正确的值,而Ipt没有。这意味着隐含的do-loop失败了。为了对此进行测试,我声明了一个新数组Jpt,其尺寸为3 x 12,并更改了read语句,如下所示:

read (3, rec = 1) ttl, Jpt, ...

可以正常工作。我认为这可能代表了英特尔运行时系统中的错误,因为带有隐式do循环的代码在直接从Fortran主程序中调用时可以工作。消除DLL中所有隐含的do循环后,代码可以正常工作,直到到达子例程的末尾。在返回调用程序的步骤中,引发了AccessViolationException。我怀疑此问题是由out字符数组ErrMsg引起的,因此为了将该问题推迟到以后,我修改了程序以将所有错误消息写入磁盘上的文件,并从子例程签名中删除了该参数。完成后,程序将运行到完成。但是,然后注意到,即使在DLL子例程的末尾保留了正确的值,双精度数组S中的值也都为零。在进一步的实验中,我们了解到,通过在调用程序中初始化S的值并删除子例程签名中的out关键字,可以将正确的值返回给主程序。在Intel RT或与VS 2015的集成中,这似乎也是一个错误。

所以我现在有一个正常工作的DLL,它将错误消息写入外部文件。我希望找到一种通过调用参数返回消息的方法,但是在尝试了几种不同方法后,我没有成功。我打算向社区发布一个单独的问题,以寻求帮助。无论如何,我希望这种讨论对遇到类似问题的其他人有所帮助。