10

All I want to do is pass a plain-text string from Haskell to C. However, it says that [Char] is an unacceptable return type. I can't find anywhere why they think it is, nor what acceptable return types are.

I'm trying to make a very simple OS image that I can boot with Qemu.

Does anyone know how to do this? Thanks.

    {-# LANGUAGE ForeignFunctionInterface #-}

    module Hello where

    import Foreign
    import Foreign.C.String
    import Foreign.C.Types

    hello :: String -> (CString -> IO a) -> IO a
    hello = "Hello, world!"

    foreign export ccall hello :: String -> (CString -> IO a) -> IO a
AndrewC
  • 32,300
  • 7
  • 79
  • 115
Sean Heiss
  • 780
  • 2
  • 9
  • 25

2 Answers2

15

You want a CString.

Going from CString to String:

peekCString :: CString -> IO String

Going from String to CString:

withCString :: String -> (CString -> IO a) -> IO a

There's also Haddock documentation for module Foreign.C.String.

The general list of types that can be used in foreign declarations is specified as part of the Foreign Function Interface in the Haskell Report.

Edit

Ok, here's a very small example of a thing you can do, somewhat based on your sample code. Create a Haskell file CTest.hs with the following contents:

module CTest where

import Foreign.C

hello :: IO CString
hello = newCString "hello"

foreign export ccall hello :: IO CString

Then create a C file ctest.c with the following contents:

#include <stdio.h>
#include "CTest_stub.h"

int main (int argc, char *argv[]) {
  hs_init(&argc, &argv);
  printf("%s\n", hello());
  hs_exit();
  return 0;
}

Then compile and run as follows:

$ ghc CTest
[1 of 1] Compiling CTest            ( CTest.hs, CTest.o )
$ ghc -o ctest ctest.c CTest.o -no-hs-main
$ ./ctest
hello
kosmikus
  • 19,549
  • 3
  • 51
  • 66
  • I tried this, but it still says [Char] is unacceptable, among other things. I am completely new to Haskell and functional programming, but not C or assembly. I put my current code in the first post. – Sean Heiss Jul 23 '13 at 14:02
  • 2
    There are several problems with your code. `hello` is a string literal, but you've given it a function type. And `String` still occurs in the type of the exported entity. You might want to read some introduction to the Haskell Foreign Function Interface first. – kosmikus Jul 23 '13 at 14:05
  • I made hello a function so I could export it and call it from C and have it return a string/character array. I read the introduction to FFI but don't quite understand it since they don't explain how to export Haskell functions. – Sean Heiss Jul 23 '13 at 14:23
  • The problem is, that the C file I'm using doesn't have a main, since it itself is called from an asm file, so I can't use hs_init() here. Is there a way around that? Is that possible to do without changing GHC and rebuilding it? – Sean Heiss Jul 23 '13 at 14:35
  • 1
    you need to make your asm call hs_init() at some point then. There's no way around it! – sclv Jul 23 '13 at 16:46
  • @sclv Well that's a shame. Guess I won't be attempting to make an OS in Haskell then! – Sean Heiss Jul 23 '13 at 21:08
  • 1
    oh, but you can do that too! http://corp.galois.com/halvm and http://programatica.cs.pdx.edu/House/ (the latter is a great proof of concept, but researchy and bitrotted afaik -- the former is suitable for real world use now) – sclv Jul 23 '13 at 21:27
-2

I think what you need is System.IO.Unsafe.unsafePerformIO to convert IO CString to CString before sending the CString to C. newCString will convert a Haskell String to IO CString. Thus System.IO.Unsafe.unsafePerformIO $ newCString a can be passed to your C routine which will accept input of type char*. If your C routine returns static char* then System.IO.Unsafe.unsafePerformIO $ peekCString will give you back a Haskell string. You need to import System.IO.Unsafe. unsafePerformIO has an implementation in Foreign.C.String (or Foreign.C.Types ?) which is deprecated, so you have to use the full path. I had a hell of a time before I could find unsafePerformIO - probably because people are allergic to something that is so dangerous as to force declaration of impure to pure. newCString can lead to memory leaks if used repeatedly without cleaning. withCString may be a better option - will learn that later.

georgeawg
  • 48,608
  • 13
  • 72
  • 95
  • 1
    This doesn't sound like a good idea to me. `withCString` helps manage memory property. If you use `newCString`, you have to free it manually. That's not necessarily bad, but if you call `unsafePerformIO (newCString s)` things get much worse: you're likely to find it very tricky to figure out when it's safe to free the string. – dfeuer Sep 08 '18 at 05:03
  • 2
    By the way: as someone who's done some non-trivial work with `unsafePerformIO` and similar functions, I'd like to say in the strongest possible terms that they should only be used when truly necessary, and almost never by beginners (yes, there are FFI exceptions, but this is not one). Even bona fide Haskell experts like the lead developers of GHC tend to make subtle but serious mistakes when they use `unsafePerformIO`. People are allergic to it because it's highly allergenic! – dfeuer Sep 08 '18 at 05:09