Haskell C FFI: доступ к статическим структурам данных

У меня есть вопрос о Haskell C FFI, в частности о доступе к статическим структурам данных, экспортированным библиотекой C.

Библиотека C-библиотеки Im имеет статические структуры данных, такие как FOO_GEORGE ниже, экспортируется следующим образом:

static struct foo_struct foo_table[] = { /* list of foo structs */ }
typedef struct foo_struct *foo_t;
foo_t FOO_GEORGE = &foo_table[0];
foo_t FOO_HARRY  = &foo_table[1];
foo_t FOO_SUSAN  = &foo_table[2];
/* ... */

Значение, которое мне нужно в моей библиотеке Haskell, - это адрес foo_struct (&foo_table[n]), то есть содержимое FOO_GEORGE, которое помещается в непрозрачную оболочку newtype обычным способом (конструкторы не экспортируются из библиотеки, только тип):

newtype Foo = Foo { getFoo :: (Ptr Foo) }

Вот что я сейчас делаю:

foreign import ccall "&FOO_GEORGE" fooGeorgeHandle :: Ptr (Ptr Foo)
FooGeorge = Foo . unsafeDupablePerformIO . peek $ fooGeorgeHandle

Я думаю, что это подходящее использование unsafePerformIO, так как C API и реализация говорят, что это использование peek чистое и не имеет побочных эффектов. Кроме того, я считаю, что мне не нужно принимать какие-либо меры предосторожности, указанные в пунктах bullet в документации (начиная с {-# NOINLINE foo #-}).

Мой общий вопрос: правильно ли я это делаю? Правильны ли биты анализа? Есть ли лучший или предпочтительный способ сделать это? Если предложение foreign import позволило мне сделать отсрочку указателя, это было бы неплохо, но, похоже, это не так. я что-то упускаю? Можно утверждать, что это было бы плохой особенностью, так как это может быть segfault, если указатель bad — но тогда то же самое верно для peek, которое я должен использовать вместо этого, поэтому оно приходит к одному и тому же.

Спасибо!

Ответы

Ответ 1

Джон Л. предложил расширение CApiFFI, которое делает именно то, что я хочу: позволяет импортировать значения, а не местоположения. Сейчас:

{-# LANGUAGE CApiFFI #-}
newtype {-# CTYPE "foo.h" "struct foo_struct" #-} Foo = Foo { getFoo :: (Ptr Foo) }
foreign import capi "foo.h value FOO_GEORGE" fooGeorgePtr :: Ptr a
fooGeorge = Foo fooGeorgePtr

Другим преимуществом является то, что это работает независимо от того, является ли FOO_GEORGE переменной C или макросом препроцессора. C API Im, работающий с использованием как двух, так и разных реализаций одного и того же API, с которым я ссылаюсь, делает это по-другому, поэтому он не зависит от этого.

Тем не менее, theres камнем преткновения: CApiFFI не работает с ghci! Проблема известна, и она не будет исправлена ​​до GHC 8.0.1 (когда я впервые написал это, она должна была появиться 7.10, и затем был продвинут вперед). У меня очень хакерское решение. CApiFFI работает, создавая библиотеку C "на лету", компилируя и связывая программу Haskell с ней. Он удаляет библиотеку по завершении; проблема ghci, кажется, в том, что файл .so ушел к тому моменту, когда ghci нужно связать с ним. Я просто хватаю файл .c во время компиляции, прежде чем он удаляется, затем скомпилируйте его и скажите ghci, чтобы загрузить его. Поскольку я не очень часто меняю эту часть программы, она работает для меня достаточно хорошо.

Мой метод для ловли временного файла .c - запустить ghci в Emacs compilation-mode, с (setq compilation-auto-jump-to-first-error t). Emacs видит ошибку и загружает файл .c в буфер до того, как GHC доберутся до его удаления - к тому времени, как я вижу, файл пропал, но Ive получил содержимое в буфере.

Обновление: ghci -fobject-code Foo работает, но может видеть только имена, экспортированные из модуля.