你能帮我使用一个来自 F# 的需要回调的 C 函数吗?我在基于 ARM 处理器的 Raspberry Pi 2 上使用 F#、mono 和 Arch Linux。
我最初的问题,理解用于回调的签名,是 answered您可以阅读以获取更多上下文。
我陷入了僵局。使用该库时,程序因段错误而崩溃。
我相信这是因为编码不匹配。根据 Expert F# 3.0 一书,“clr 仅支持编码函数指针的 std 调用约定”。 CLR 假定被调用者将清理堆栈(StdCall),C 库期望调用者将根据 MSDN 上的定义清理堆栈(Cdecl)。 .因此内存泄漏。
可悲的是,我无法让 CLR 使用 Cdecl 作为该委托(delegate),也无法让 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 是正确的。 native 代码绝对不会返回 Win32 错误,它无论如何都在 Linux 上运行,所以我只会从错误代码中得到垃圾。
设置CallingConvention.Cdecl
在 DLLImport 属性上有效。也许“clr 只支持编码函数指针的 std 调用约定”这句话对我的系统(Linux 上的单声道)是错误的,或者我误解了这句话。
设置[<UnmanagedFunctionPointer(CallingConvention.Cdecl)>]
与 EntryPoint = "wiringPiISR"
一样,委托(delegate)似乎是多余的DLLImport
的一部分属性。为了清楚起见,我删除了两者。
OldNewThing blog at MSDN 上有一系列关于 DDLImport 和 DLLExport 的文章
感谢@FyodorSoikin、@ildjarn、@BentTranberg、@CurtNichols 和 @vcsjones
关于.net - 在 Linux/ARM 上使用 pinvoke 和 C 函数回调的 F# 委托(delegate),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37843094/