Я настоятельно рекомендую использовать препроцессор.Мне нравится c2hs, но hsc2hs очень распространен, потому что он включен в ghc.Гринкарта, похоже, заброшена.
Чтобы ответить на ваши вопросы:
1) Да, через определение сохраняемого экземпляра.Использование Storable является единственным безопасным механизмом для передачи данных через FFI.Экземпляр Storable определяет, как выполнить маршалинг данных между типом Haskell и необработанной памятью (либо Haskell Ptr, ForeignPtr, либо StablePtr, либо указатель C).Вот пример:
data PlateC = PlateC {
numX :: Int,
numY :: Int,
v1 :: Double,
v2 :: Double } deriving (Eq, Show)
instance Storable PlateC where
alignment _ = alignment (undefined :: CDouble)
sizeOf _ = {#sizeof PlateC#}
peek p =
PlateC <$> fmap fI ({#get PlateC.numX #} p)
<*> fmap fI ({#get PlateC.numY #} p)
<*> fmap realToFrac ({#get PlateC.v1 #} p)
<*> fmap realToFrac ({#get PlateC.v2 #} p)
poke p (PlateC xv yv v1v v2v) = do
{#set PlateC.numX #} p (fI xv)
{#set PlateC.numY #} p (fI yv)
{#set PlateC.v1 #} p (realToFrac v1v)
{#set PlateC.v2 #} p (realToFrac v2v)
Фрагменты {# ... #}
являются кодом c2hs.fI
- это fromIntegral
.Значения во фрагментах get и set относятся к следующей структуре из включенного заголовка, а не к типу Haskell с тем же именем:
struct PlateCTag ;
typedef struct PlateCTag {
int numX;
int numY;
double v1;
double v2;
} PlateC ;
c2hs преобразует это в следующий простой Haskell:
instance Storable PlateC where
alignment _ = alignment (undefined :: CDouble)
sizeOf _ = 24
peek p =
PlateC <$> fmap fI ((\ptr -> do {peekByteOff ptr 0 ::IO CInt}) p)
<*> fmap fI ((\ptr -> do {peekByteOff ptr 4 ::IO CInt}) p)
<*> fmap realToFrac ((\ptr -> do {peekByteOff ptr 8 ::IO CDouble}) p)
<*> fmap realToFrac ((\ptr -> do {peekByteOff ptr 16 ::IO CDouble}) p)
poke p (PlateC xv yv v1v v2v) = do
(\ptr val -> do {pokeByteOff ptr 0 (val::CInt)}) p (fI xv)
(\ptr val -> do {pokeByteOff ptr 4 (val::CInt)}) p (fI yv)
(\ptr val -> do {pokeByteOff ptr 8 (val::CDouble)}) p (realToFrac v1v)
(\ptr val -> do {pokeByteOff ptr 16 (val::CDouble)}) p (realToFrac v2v)
Смещения, конечно, зависят от архитектуры, поэтому использование препроцессора позволяет писать переносимый код.
Вы используете это, выделяя место для вашего типа данных (new
, malloc
и т. д.) и poke
ввод данных в Ptr (или ForeignPtr).
2) Это реальный макет памяти.
3) Существует штраф за чтение / записьс peek
/ poke
.Если у вас много данных, лучше конвертировать только то, что вам нужно, например, чтение только одного элемента из массива C вместо сортировки всего массива в списке Haskell.
4) Синтаксис зависит от препроцессоратвой выбор. c2hs docs . hsc2hs docs .Смущает то, что hsc2hs использует синтаксис #stuff
или #{stuff}
, в то время как c2hs использует {#stuff #}
.
5) Предложение @ sclv я бы тоже сделал.Написать экземпляр Storable и сохранить указатель на данные.Вы можете либо написать функции C, чтобы выполнить всю работу, и вызывать их через FFI, либо (менее хорошо) написать низкоуровневый Haskell, используя peek и poke, чтобы работать только с теми частями данных, которые вам нужны.Маршаллинг всего этого вперед и назад (то есть вызов peek
или poke
всей структуры данных) будет дорогостоящим, но если вы только передадите указатели, стоимость будет минимальной.
Вызов импортированных функций черезу FFI есть существенное наказание, если они не отмечены как "небезопасные".Объявление импорта «небезопасным» означает, что функция не должна вызывать обратно в Haskell или неопределенные результаты поведения.Если вы используете параллелизм или параллелизм, это также означает, что все потоки Haskell с одинаковыми возможностями (например, ЦП) будут блокироваться до тех пор, пока не будет возвращен вызов, поэтому он должен вернуться довольно быстро.Если эти условия приемлемы, «небезопасный» вызов выполняется относительно быстро.
В Hackage много пакетов, которые имеют дело с подобными вещами.Я могу порекомендовать hsndfile и hCsound как демонстрирующие хорошую практику с c2hs.Возможно, будет проще, если вы посмотрите на привязку к небольшой библиотеке C, с которой вы знакомы.