【问题标题】:AccessViolationException calling Fortran from C#AccessViolationException 从 C# 调用 Fortran
【发布时间】:2013-04-12 00:57:39
【问题描述】:

我正在尝试包装 glmnet 库 (http://cran.r-project.org/web/packages/glmnet/index.html),以便我可以在 C# 中解决 models 稀疏一般线性模型。然而,原始函数有大约 20 个参数,所以我开始(对于 Fortran 来说是全新的)用一个很小的子例程来测试如何传递数据。不幸的是,我总是收到 AccessViolationException。

代码如下:

Fortran 子例程。我使用 Rtools (http://cran.r-project.org/bin/windows/Rtools/) 附带的 gfortran 编译器,使用 -m64 选项将其编译为 dll(是的,64 位是必需的,因为我处理了相当大的数据块)。是的,使用 i 可能会导致越界……但这只是为了测试。

subroutine testaaa  (f,i,fa,ia)
real fa(i)                                                      
integer ia(i)
ia(1) = 1337
ia(i) = 666
fa(1) = 8.15
fa(i) = 333
end subroutine testaaa

C# PInvoke 代码:

[DllImport("ftest.dll", EntryPoint = "testaaa_", CallingConvention = CallingConvention.StdCall)]
public static extern void Test(
    [MarshalAs(UnmanagedType.R4)] float f,
    [MarshalAs(UnmanagedType.I4)] int i,
    IntPtr fa,
    IntPtr ia);

这就是它的名称:

var fa = new float[4];
var ia = new int[4];
IntPtr faPtr = Marshal.AllocHGlobal(fa.Length * sizeof(float));
Marshal.Copy(fa, 0, faPtr, fa.Length);

IntPtr iaPtr = Marshal.AllocHGlobal(ia.Length * sizeof(float));
Marshal.Copy(ia, 0, iaPtr, ia.Length);

GlmnetDllWrapper.Test(0.4f, 4,faPtr,iaPtr);

我还尝试直接传递数组并赋予它们 [MarshalAs(UnmanagedType.LPArray)] 属性。没有什么对我有用。

你有什么建议从哪里开始或改变什么?

更新 1: 即使只传递 float 和 int 也已经导致异常:

subroutine testbbb  (f,i)
i = 815
f = 8.15
return
end subroutine testbbb

C# Pinvoke 和 call 相应更改。我做错了什么?

【问题讨论】:

  • 您的 Fortan 库是否有可能通过指针接收参数?另外,您为什么要一次测试所有内容。为什么不简化?你能传递int 类型的单个参数吗?类型为float?然后尝试一个数组。不要先测试最复杂的东西。你怎么知道哪个部分失败了?
  • 好的,你的更新和我的评论交叉了。简化工作做得很好。尝试作为指针传递:public static extern void Test(ref float f, ref int i)
  • 一旦你克服了简单类型的障碍,我们可以向你展示如何做数组。并且不需要IntPtr。我们可以让 pinvoke marshaller 固定数组并使调用代码非常简单。
  • @DavidHeffernan ref 完成了这项工作。传递没有任何属性的数组有效。添加LPArray 属性也一样好。操作数组也很好。现在尝试更复杂的场景。当有更多要说的时候会发表评论。

标签: c# fortran pinvoke marshalling access-violation


【解决方案1】:

主要问题是您的 Fortran 库需要通过引用传递标量参数。所以你需要声明你的 p/invoke 来匹配。

数组参数可以非常简单地作为数组传递,p/invoke marshaller 将为您固定它们。

所以,你的 p/invoke 声明应该是这样的:

[DllImport("ftest.dll", EntryPoint = "testaaa_")]
public static extern void Test(
    ref float f,
    ref int i,
    [In, Out] float[] fa,
    [In, Out] int[] ia
);

您可以调整[In, Out] 属性以满足您的需求。

【讨论】:

  • 你最好把 CalingConvention 放回去,Fortran 几乎总是标准调用。更多关于 gfortran here
  • @HansPassant StdCall 是默认设置,这就是我删除它的原因。还是我误解了你?
  • 是的,你是对的,我弄错了,抱歉。我留下的链接应该给 OP 提示什么调用约定以及 Fortran 源代码是否需要 ref。 VALUE 和 USE 语句很重要。
  • 是的,我想就是这样!非常感谢你的帮助! glmnet 库正在工作......现在我必须处理它的输出。但是,我省略了 InOut 参数。无论有没有它们,它都可以工作。也许将它们重新添加会使代码更具可读性 - 或者这个还有更多?
  • 还有一点点。阅读@Hans 对这个问题的回答:stackoverflow.com/questions/14366066/…
【解决方案2】:

请看 http://msdn.microsoft.com/en-en/library/chfa2zb8%28v=VS.80%29.aspx(不安全代码) 并使用您的项目设置项目/属性/构建:允许不安全的代码。 但要注意后果。 :)

更新:不要“玩弄” - 我的意思是:“检查“不安全”的功能”。 “不安全”并不意味着“危险”。

【讨论】:

  • 不,请不要这样做。不需要不安全的代码。并且“玩弄你的设置”并不是一个答案。
  • 是的,对不起。我的意思是:“尝试用不安全代码包装该区域并允许不安全代码一次”以查看问题是否存在。这是一种处理指针的方法,尤其是在使用 ext 时。非 .Net 程序集,如果“ref”不起作用。但你是对的。没必要,因为一个简单的“ref”就可以了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-11-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-11
  • 2014-01-14
  • 2016-03-25
相关资源
最近更新 更多