【问题标题】:How to add unmanaged dll to c# project如何将非托管dll添加到c#项目
【发布时间】: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) 正确初始化。

标签: c# dll unmanaged


【解决方案1】:

经过几天的进一步实验,我已经解决了大部分问题。解决这个问题的一个早期关键是意识到我需要设置一个调试器属性来允许本地代码调试。此属性在 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 失败了。为了测试这一点,我声明了一个尺寸为 3 x 12 的新数组 Jpt,并将读取语句更改如下:

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

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

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

【讨论】:

    猜你喜欢
    • 2018-04-04
    • 2016-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-14
    相关资源
    最近更新 更多