如何从Haskell传递一个字符串到C语言?

10

我想做的就是将一个纯文本字符串从Haskell传递到C。然而,它说[Char]是不可接受的返回类型。我找不到任何地方说明为什么他们认为它不可接受,也不知道什么样的返回类型是可接受的。

我试图制作一个非常简单的操作系统镜像,可以在QEMU中启动。

有人知道如何做吗?谢谢。

    {-# 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
2个回答

15

你需要一个CString

CString转换为String

peekCString :: CString -> IO String

String 转换为 CString

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

还有关于 模块 Foreign.C.String 的 Haddock 文档。

foreign 声明中可使用的类型的一般列表在 Haskell 报告中的 Foreign Function Interface 中指定。

编辑

好的,以下是一个非常小的示例,基于您的示例代码进行了一些修改。创建包含以下内容的 Haskell 文件 CTest.hs

module CTest where

import Foreign.C

hello :: IO CString
hello = newCString "hello"

foreign export ccall hello :: IO CString

然后创建一个名为ctest.c的C文件,并填入以下内容:

#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;
}

然后按以下方式编译和运行:

$ ghc CTest
[1 of 1] Compiling CTest            ( CTest.hs, CTest.o )
$ ghc -o ctest ctest.c CTest.o -no-hs-main
$ ./ctest
hello

我尝试了这个,但它仍然显示[Char]不可接受,还有其他的问题。我完全是Haskell和函数式编程的新手,但不是C或汇编语言。我把我的当前代码放在第一个帖子中。 - Sean Heiss
2
你的代码存在几个问题。hello 是一个字符串字面量,但你给它了一个函数类型。而且 String 仍然出现在导出实体的类型中。你可能需要先阅读一些关于 Haskell 外部函数接口的介绍。 - kosmikus
问题是,我正在使用的C文件没有main函数,因为它本身是从一个asm文件中调用的,所以我不能在这里使用hs_init()。有没有绕过这个问题的方法?是否可以在不改变GHC并重新构建它的情况下完成? - Sean Heiss
1
你需要在某个时候调用 asm 的 hs_init()。这是无法避免的! - sclv
1
哦,但你也可以做到!http://corp.galois.com/halvm和http://programatica.cs.pdx.edu/House/(后者是一个很好的概念证明,但据我所知,它已经过时了 - 前者现在适用于实际应用) - sclv
显示剩余2条评论

-2

我认为您需要使用 System.IO.Unsafe.unsafePerformIO 将 IO CString 转换为 C 字符串,然后再将该字符串发送到 C 程序中。newCString 可以将 Haskell 字符串转换为 IO CString。因此,System.IO.Unsafe.unsafePerformIO $ newCString a 可以传递给您的 C 程序,该程序将接受类型为 char* 的输入。如果您的 C 程序返回静态 char*,那么 System.IO.Unsafe.unsafePerformIO $ peekCString 将会返回一个 Haskell 字符串。您需要导入 System.IO.UnsafeunsafePerformIOForeign.C.String(或 Foreign.C.Types?)中有一个实现,但已经被弃用,因此您必须使用完整路径。在我能够找到 unsafePerformIO 之前,我花费了大量时间 - 可能是因为人们对这种将不纯的声明转化为纯的东西过于敏感。如果反复使用 newCString 而不清理,可能会导致内存泄漏。使用 withCString 可能是更好的选择 - 我会在以后学习。


1
这对我来说听起来不是一个好主意。withCString有助于管理内存属性。如果您使用newCString,则必须手动释放它。这并不一定是坏事,但是如果您调用unsafePerformIO(newCString s),情况会变得更糟:您很可能会发现很难弄清何时可以安全地释放该字符串。 - dfeuer
2
顺便说一句:作为一个使用unsafePerformIO和类似函数进行过一些非平凡工作的人,我想强烈表示,只有在真正必要的情况下才应该使用它们,而且几乎不应该由初学者使用(是的,有FFI例外,但这不是其中之一)。即使是GHC的主要开发人员这样的合格的Haskell专家,在使用unsafePerformIO时也往往会犯微妙但严重的错误。人们对它过敏,因为它具有高度的致敏性! - dfeuer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接