Подтвердить что ты не робот

Поддержка Haskell FFI функций с аргументами Variadic

Может ли кто-нибудь показать мне пример использования функции C с переменными аргументами (например, printf) с помощью интерфейса внешней функции Haskell? Я попытался найти HaskellWiki, но не нашел таких примеров.

Спасибо!

4b9b3361

Ответ 1

Вы можете использовать интерфейс 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)

Ответ 2

Я не думаю, что это возможно. Вы можете, однако, сделать несколько иностранных импортов одной и той же функции C и присвоить им разные имена Haskell и типы Haskell. Я не уверен, что на 100% переносится.

Ответ 4

https://nek0.eu/posts/2016-04-19-Interfacing-variadic-functions-from-Haskell.html

Признаюсь, я поклонник Haskell. Всякий раз, когда я программирую что-то для удовольствия, я обычно предпочитаю этот язык из-за его элегантности.

В настоящее время я работаю над привязками Haskell к библиотеке GEGL. Мотивация этого - мое желание заниматься разработкой игр, и мне нужна библиотека для рисования на поверхностях SDL. Я, очевидно, не очень люблю простые решения и стараюсь учиться чему-то новому. Как и использование Haskell FFI.

При написании привязок я столкнулся с проблемой того, что GEGL предоставляет в своем заголовке переменные функции, с которыми мне нужно взаимодействовать. Это создает серьезную проблему для Haskell, потому что число аргументов функции должно быть постоянным. Просто невозможно определить функцию, не зная, сколько у нее аргументов и какого типа у каждого аргумента. Это остается верным даже для моего решения. Единственная причина, по которой мое решение работает, заключается в том, что я могу ограничить случаи, когда интерфейс этих функций варьируется до приемлемого уровня.

Для построения своих привязок я не использую стандартную FFI Haskell, а встроенную библиотеку Haskell для вызова функций C напрямую без использования жестких привязок. Это достигается в inline-c путем помещения вызова функции в QuasiQuoter. Как я уже говорил ранее, это все еще требует от вас написания QuasiQuoter для каждого случая, когда вызывается эта функция, но вам не нужно загромождать свой код объявлениями зарубежного импорта ccall.

Для ограничения ваших случаев я рекомендую использовать тип суммы в качестве аргумента функции. Тип суммы - это тип, который имеет несколько конструкторов. Вы можете иметь конструктор для каждого случая, который вам необходим для взаимодействия и различения, используя сопоставление с шаблоном Haskells. Вы можете увидеть пример того, как сделать все это в моих привязках.