Какие существуют альтернативы для возврата строки, содержащей нулевые значения из общей библиотеки Haskell, для использования в C?

Возможный дубликат:
может FFI имеет дело с массивами? Если да, то как?

У меня есть крошечный ассемблер, написанный на Haskell, который принимает строку с ассемблерным кодом и возвращает строку двоичного машинного кода. Я хочу иметь возможность использовать эту функцию в C, создав эту библиотеку Haskell как разделяемую библиотеку. Двоичный машинный код может содержать нулевые значения, поэтому я не могу использовать CString в качестве возвращаемого типа, так как это обычная строка с завершающим нулем. И поскольку я не могу использовать CStringLen в качестве возвращаемого значения в FFI.

Какой тип следует использовать для этого?

Сигнатура типа внутренней функции сборки:

assembly :: String -> ByteString 

Вот пример ввода и вывода этой функции:

Вход:

decl r0 0x02
decl r1 0x10
add r0 r1 
mov rr rs

Вывод (двоичные данные, представленные в шестнадцатеричном формате с 3 байтами в строке):

01 00 02
01 01 10
03 00 01
02 05 04

person rzetterberg    schedule 30.09.2012    source источник
comment
Я не силен в GHC FFI, но можете ли вы вручную манипулировать памятью и вернуть указатель на CStringLen? (Т.е. есть функция convert :: ByteString -> IO (Ptr CStringLen)? Или что-то в этом роде.)   -  person huon    schedule 30.09.2012
comment
@dbaupp Да, но я считаю, что мне нужно создать пользовательскую структуру и реализовать маршалинг с использованием Storable. Я читаю на эту тему, но не нашел прямого решения.   -  person rzetterberg    schedule 30.09.2012


Ответы (3)


Если бы я писал это на C, я мог бы дать ему такой прототип:

void assemble(char **out, size_t *outlen, const char *in);

Это переводится примерно так (не проверено):

import qualified Assemble -- your module with the "assemble" function

import Foreign.Ptr (Ptr)
import Foreign.Storable (poke)
import Foreign.Marshal.Utils (copyBytes)
import Foreign.Marshal.Alloc (mallocBytes)
import Foreign.C.Types (CSize, CChar)
import Foreign.C.String (CString, peekCString)
import Data.ByteString.Unsafe (unsafeUseAsCStringLen)

foreign export ccall assemble :: Ptr (Ptr CChar) -> Ptr CSize -> CString -> IO ()

assemble :: Ptr (Ptr CChar) -> Ptr CSize -> String -> IO ()
assemble out outlen instrptr = do
  instr <- peekCString instrptr
  unsafeUseAsCStringLen (Assemble.assemble instr) $ \(p, n) -> do
    outval <- mallocBytes n
    copyBytes outval p n
    poke out outval
    poke outlen (fromIntegral n)

Это копирует данные в область malloc, что хорошо, потому что она «безопасна», и коду C не нужно делать ничего особенного, чтобы освободить ее (кроме free()).

person Dietrich Epp    schedule 30.09.2012
comment
Это именно та прямолинейность, которую я искал в примере! Есть одна вещь, которую мне трудно понять, и вот почему out это void **, а не char **. Кажется, что компиляция тоже в убытке. Он ожидает Ptr () в качестве типа во втором аргументе poke, но теперь получает Ptr Foreign.C.Types.CChar. - person rzetterberg; 30.09.2012
comment
@rzetterberg: я написал код навскидку. Использование void ** - это привычка из C, когда я работаю с двоичными данными, я думаю об этом скорее как о подсказке отладчику, чем о чем-либо еще. Использование char ** также нормально. - person Dietrich Epp; 30.09.2012
comment
Спасибо за отличный ответ. Мне просто нужно было адаптировать типы с подсказками от компилятора, тогда ваш код работал отлично :) Вот что я изменил: Ptr (Ptr ()) на Ptr (Ptr CChar)) и String на CString и добавил код для преобразования ввода CString в обычный String для передачи в мой assemble функция. - person rzetterberg; 30.09.2012

Можете ли вы что-то сделать с необработанными указателями и ручным выделением памяти? (См. Foreign.Marshal.Alloc.) Похоже, вы могли бы просто malloc выделить кусок памяти и записать туда свои двоичные данные...

person MathematicalOrchid    schedule 30.09.2012
comment
Да, похоже, это выход. Однако хороших примеров я не нашел. Те, которые я нашел, использовали hsc2cs и структуры маршалинга, которые стали слишком сложными для понимания и казались немного чрезмерными для этой проблемы. - person rzetterberg; 30.09.2012
comment
@rzetterberg Да, это не совсем моя специальность. Я просто пытался дать несколько полезных советов о том, с чего начать. - person MathematicalOrchid; 30.09.2012
comment
Ну, тем не менее, я ценю вклад! Как вы можете видеть в принятом ответе, именно то, что вы предложили, сработало для меня. - person rzetterberg; 30.09.2012

Я недостаточно знаю Haskell, чтобы быть уверенным, но не могли бы вы передать дополнительную длину параметра out в функцию haskell? По возвращении из функции длина сообщит программе c размер возвращаемой строки. Я считаю, что делал подобные вещи между c и python.

С другой стороны, вы не можете вернуть пользовательский объект, такой как строка С++, которая имеет поле длины. Даже если вы используете чистый c, если это способ совместного использования типов между c и haskell (который, я считаю, должен существовать), вы можете написать небольшую строковую структуру с массивом символов и полями длины и вернуть этот объект из haskell.

person fkl    schedule 30.09.2012
comment
Да, что-то в этом духе. Но я хотел бы узнать более конкретно, какие альтернативы есть в Haskell для этого. Каковы наилучшие методы, можем ли мы решить эту проблему с помощью манипуляций с указателями в Haskell и т. д. и т. д. - person rzetterberg; 30.09.2012
comment
Это может помочь stackoverflow.com/questions/6140348 / - person fkl; 30.09.2012
comment
Спасибо, но я это уже видел. И он имеет дело с вводом, а не выводом. Как я уже говорил в своем вопросе, CStringLen нельзя возвращать через FFI, поскольку это кортеж. Можно использовать только скалярные типы. - person rzetterberg; 30.09.2012