F# 委托在 Linux / ARM 上使用带有 C 函数回调的 pinvoke

F# delegate using pinvoke with C function callback on Linux / ARM

你能帮我在 F# 中使用一个需要回调的 C 函数吗?我在基于 Raspberry Pi 2.

的 ARM 处理器上使用 F#、mono 和 Arch Linux

我最初的问题是理解用于回调的签名, 您可以阅读它以了解更多上下文。

我陷入了僵局。使用库时,程序因分段错误而崩溃。

我认为这是因为编组不匹配。 "the clr supports only the std calling convention for marshalling function pointers"根据Expert F# 3.0一书。 CLR 假定 callee 将清理堆栈 (StdCall),C 库期望 caller 将清理堆栈 (Cdecl) MSDN 上的定义。因此内存泄漏。

遗憾的是,我找不到让 CLR 为此委托使用 Cdecl 或让 C 在 ARM 处理器上使用 StdCall 的方法。我没有编写 C 库,但我可以访问源代码。

这是 C 中的函数签名

int wiringPiISR (int pin, int edgeType,  void (*function)(void)) ;

以及我的部分 F# 代码:

type ISRCallback = delegate of unit -> unit

[<DllImport(wiringPiLib, EntryPoint = "wiringPiISR", CallingConvention = CallingConvention.Cdecl, SetLastError=true)>]
    extern int wiringPiISR(int pin, int mode, [<MarshalAs(UnmanagedType.FunctionPtr)>]ISRCallback callBack);

[<UnmanagedFunctionPointer(CallingConvention.Cdecl)>] 修饰委托没有帮助。编译器默默地忽略它,我认为这就是这些属性设计的工作方式。

是否有另一种方法可以做到这一点 - 也许是编辑 C 代码,或者以另一种方式包装它? (也许是 C++?)

注意:mono-project 网站上有一篇有趣的文章,标题为 Interop with Native Libraries

与此同时,我编写了一个小循环来轮询 GPIO 引脚。不理想,但它有效。

编辑:我的内存泄漏假设是错误的,但是由于评论和它提示的研究,我学到了很多东西并找到了答案。

如评论中所述和我最初问题的答案中所建议的那样,分段错误是由回调函数的垃圾回收引起的。

在我的代码中的正确位置添加 GC.StayAlive (MyFunctionDoingCallBack) 可以让任何审查代码的人——我——清楚地知道何时收集回调垃圾是安全的。这个解决方案比找出回调超出范围的原因以及如何将其保持在范围内更容易。我意识到这对我作为程序员的影响很糟糕;-(.

This answer 解释了为什么 SetLastError=false 是正确的。本机代码绝对不是 return Win32 错误,无论如何它是 运行 on Linux,所以我只会从错误代码中得到垃圾。

在 DLLImport 属性上设置 CallingConvention.Cdecl 有效。也许引用“The clr supports only the std calling convention for marshalling function pointers”对我的系统来说是错误的(Linux 上的单声道)或者我误解了引用。

在委托上设置 [<UnmanagedFunctionPointer(CallingConvention.Cdecl)>] 似乎是多余的,DLLImport 属性的 EntryPoint = "wiringPiISR" 部分也是如此。为了清楚起见,我删除了两者。

OldNewThing blog at MSDN

上有一系列关于 DDLImport 和 DLLExport 的文章

感谢@FyodorSoikin、@ildjarn、@BentTranberg、@CurtNichols 和@vcsjones