繁体   English   中英

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

[英]How to add unmanaged dll to c# project

之前已经有人问过这个问题,而我对它们的审查使我受益匪浅。 但是,我仍然缺少该过程中的一些关键步骤。 我已经开发了一个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合并到项目中的过程中搞砸了或忽略了一些步骤。 任何人都可以指出问题的原因。 感谢您提供的任何帮助。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM