简体   繁体   English

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

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

Can you help me use a C function, from F#, that expects a callback? 你能帮我使用F#的C函数,它需要一个回调吗? I am using F#, mono and Arch Linux, on the ARM processor based Raspberry Pi 2. 我在基于ARM处理器的Raspberry Pi 2上使用F#,mono和Arch Linux。

My original problem, understanding the signature to use for the callback, was answered which you can read for more context. 我的原始问题,理解用于回调的签名,已被回答 ,您可以阅读更多上下文。

I have reached an impasse. 我陷入了僵局。 When using the library, the program crashes with a segmentation fault. 使用库时,程序会因分段错误而崩溃。

I believe that this is because of a marshalling mismatch. 我认为这是因为编组不匹配。 The "the clr supports only the std calling convention for marshalling function pointers" according to the book Expert F# 3.0 . 根据专家F#3.0一书,“clr仅支持编组函数指针的std调用约定”。 The CLR assumes that the callee will clean the stack (StdCall) and the C library expects that the caller will clean the stack (Cdecl) according to the definition on MSDN . CLR假定被调用者将清理堆栈 (StdCall),并且C库期望调用者根据MSDN上的定义清理堆栈 (Cdecl)。 Hence the memory leak. 因此内存泄漏。

Sadly, I can find no way to make the CLR use Cdecl for this delegate nor for C to use StdCall on an ARM processor. 遗憾的是,我无法让CLR使用Cdecl代表这个委托,也不能让C在ARM处理器上使用StdCall。 I haven't written the C library but I do have access to the source code. 我没有编写C库但我可以访问源代码。

Here is the function signature in C 这是C中的函数签名

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

and part of my F# code: 和我的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);

Decorating the delegate with [<UnmanagedFunctionPointer(CallingConvention.Cdecl)>] doesn't help. 使用[<UnmanagedFunctionPointer(CallingConvention.Cdecl)>]装饰[<UnmanagedFunctionPointer(CallingConvention.Cdecl)>]没有帮助。 The compiler silently ignores it, which I think is how these attributes are designed to work. 编译器默默地忽略它,我认为这些属性是如何设计的。

Is there another way to do this - perhaps editing the C code, or wrapping it in another way? 有没有其他方法可以做到这一点 - 也许编辑C代码,或以其他方式包装它? (C++ perhaps?) (也许是C ++?)

Note: there is an interesting article on the mono-project website titled Interop with Native Libraries 注意:单项目网站上有一篇有趣的文章,名为Interop with Native Libraries

In the meantime, I have written a little loop to poll the GPIO pins. 与此同时,我编写了一个小循环来轮询GPIO引脚。 Not ideal but it works. 不理想,但它的工作原理。

EDIT: I was wrong in my memory leak assumption but, thanks to the comments and the research it prompted, I learnt a lot and found answers. 编辑:我的内存泄漏假设错了,但是,由于评论和它提出的研究,我学到了很多东西并找到了答案。

The segmentation fault, as noted in the comments and as suggested in the answer to my original question, is caused by the garbage collection of the callback function. 如评论中所述并且在我原始问题的答案中所建议的,分段错误是由回调函数的垃圾收集引起的。

Adding GC.StayAlive (MyFunctionDoingCallBack) at the right place in my code makes it clear to anyone reviewing the code - me - when it is safe for the callback garbage collected. 在我的代码中的正确位置添加GC.StayAlive (MyFunctionDoingCallBack)使任何人都可以清楚地查看代码 - 我 - 当收集回调垃圾是安全的时候。 This solution was easier than working out why the callback was going out of scope and thus how to keep it in scope. 这个解决方案比解决回调超出范围的原因更容易,因此如何将其保持在范围内。 I realise this reflects badly on me as a programmer ;-(. 我意识到这对我作为程序员反映得很糟糕;-(。

This answer explains why SetLastError=false is correct. 这个答案解释了为什么SetLastError = false是正确的。 The native code definitely does not return a Win32 Error, it's running on Linux anyway, so I'd only be getting garbage from the error code. 本机代码肯定不会返回Win32错误,它无论如何都在Linux上运行,所以我只会从错误代码中获取垃圾。

Setting CallingConvention.Cdecl on the DLLImport attribute works. 在DLLImport属性上设置CallingConvention.Cdecl有效。 Perhaps the quote " The clr supports only the std calling convention for marshalling function pointers " is wrong for my system (mono on Linux) or I misunderstood the quote. 也许引用“ clr仅支持编组函数指针的std调用约定 ”对我的系统来说是错误的(Linux上的mono)或者我误解了引用。

Setting [<UnmanagedFunctionPointer(CallingConvention.Cdecl)>] on the delegate appears to be redundant as is EntryPoint = "wiringPiISR" part of the DLLImport attribute. 在委托上设置[<UnmanagedFunctionPointer(CallingConvention.Cdecl)>]似乎是冗余的,因为DLLImport属性的EntryPoint = "wiringPiISR"部分。 For clarity I removed both. 为清楚起见,我删除了两者

There is a series of articles about DDLImport and DLLExport on the OldNewThing blog at MSDN 在MSDNOldNewThing博客上有一系列关于DDLImport和DLLExport的文章

Thanks @FyodorSoikin, @ildjarn, @BentTranberg, @CurtNichols and @vcsjones 谢谢@FyodorSoikin,@ ijjarn,@ BirdTranberg,@ CornNichols和@vcsjones

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

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