Какви алтернативи има за връщане на низ, който съдържа нулеви стойности от споделена библиотека на 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 достатъчно, за да съм сигурен, но не можете ли да предадете допълнителна дължина на параметъра на функцията haskell? При връщане от функцията length ще каже на програмата c размера на върнатия низ. Вярвам, че съм правил подобни неща между c и python.

Алтернативно, не можете ли да върнете потребителски обект като c++ string, който има поле за дължина. Дори ако използвате чист 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