问题 Haskell FFI支持具有可变参数的函数


任何人都可以向我展示一个使用具有可变参数的C函数的例子(例如 printf)与Haskell的外部函数接口?我试着搜索HaskellWiki,但没有找到这样的例子。

谢谢!


12244
2018-05-13 08:49


起源

你可以看看如何 printf 从Haskell调用(在 Printf-TH 要么 printf-mauke 包)。 - Thomas M. DuBuisson
@TomMD:我很确定这两个都只是在printf概念的Haskell中重新创建,而不是绑定到C实现。 - mokus
@mokus:啊,谢谢。我以为它把引擎盖下的工作搞得一团糟。 - Thomas M. DuBuisson


答案:


您可以将Haskell接口用于libffi(http://hackage.haskell.org/package/libffi),因为此代码已逐字逐句复制出我正在处理的项目(您可以在上下文中看到它) https://github.com/mokus0/bindings-hdf5/blob/master/src/Bindings/HDF5/Raw/H5E.hsc)。此特定函数还检查无参数情况,并在可能的情况下直接调用C函数,以避免与libffi相关的小开销。

-- libffi to the rescue!  I have no idea how I'd wrap this without it, and there
-- doesn't appear to be a non-deprecated non-private non-varargs equivalent.
-- 
-- |Pushes a new error record onto error stack for the current
-- thread.  The error has major and minor IDs 'maj_id' and
-- 'min_id', the name of a function where the error was detected,
-- the name of the file where the error was detected, the
-- line within that file, and an error description string.  The
-- function name, file name, and error description strings must
-- be statically allocated.
-- 
-- Returns non-negative on success/Negative on failure.
-- 
-- > herr_t H5Epush2(hid_t err_stack, const char *file, const char *func, unsigned line,
-- >     hid_t cls_id, hid_t maj_id, hid_t min_id, const char *msg, ...);
--
-- (msg is a printf format string, the varargs are the format parameters)
h5e_push2 :: HId_t -> CString -> CString -> CUInt -> HId_t -> HId_t -> HId_t -> CString -> [Arg] -> IO HErr_t
h5e_push2 err_stack file func line cls_id maj_id min_id fmt [] =
    h5e_push2_no_varargs err_stack file func line cls_id maj_id min_id fmt
h5e_push2 (HId_t err_stack) file func line (HId_t cls_id) (HId_t maj_id) (HId_t min_id) fmt varargs =
    callFFI p_H5Epush2 retHErr_t args
    where 
        argHId_t = arg#type hid_t
        retHErr_t = fmap HErr_t (ret#type herr_t)

        args = argHId_t err_stack : argPtr file : argPtr func : argCUInt line
             : argHId_t cls_id : argHId_t maj_id : argHId_t min_id : argPtr fmt
             : varargs

foreign import ccall "H5Epush2"
    h5e_push2_no_varargs :: HId_t -> CString -> CString -> CUInt -> HId_t -> HId_t -> HId_t -> CString -> IO HErr_t
foreign import ccall "&H5Epush2"
    p_H5Epush2 :: FunPtr (HId_t -> CString -> CString -> CUInt -> HId_t -> HId_t -> HId_t -> CString -> IO HErr_t)

5
2018-05-13 14:23



此外,还有一个Haskell绑定到C / Invoke(hackage.haskell.org/package/cinvoke),但我总是对libffi个人有更好的印象。 - mokus


我不认为这是可能的。但是,您可以使用相同的C函数进行多次外部导入,并为其提供不同的Haskell名称和Haskell类型。不过,我不确定它是100%便携式的。


7
2018-05-13 09:08



这也是我采取的方法。 - Don Stewart
FFI规范没有正式支持它,虽然我们在GHC中遇到了一些问题以确保它有效(特别是x86_64 ABI使得绑定到varargs函数有点痛苦)。 - Simon Marlow
从Haskell 2010规范(8.5.1):“请注意,对于定义为接受可变数量参数的C函数,除了显式类型参数之外的所有参数都会受到参数提升。但是,因为C允许调用约定为一般来说,Haskell系统不能使用变量参数函数。因此,在便携式代码中不推荐使用它们。“ - Simon Marlow


在最近的GHC版本中,您可以使用 CApiFFI 扩展导入变量参数C函数。

GHC用户指南 - CAPI呼叫惯例


1
2017-09-09 17:56