【问题标题】:How to write a pure String to String function in Haskell FFI to C++如何在 Haskell FFI to C++ 中编写纯字符串到字符串函数
【发布时间】:2013-05-29 02:18:16
【问题描述】:

我想通过 Haskell FFI 在 C++ 中实现一个函数,它的(最终)类型应该是 String -> String。比如说,是否可以重新实现以下具有完全相同签名的 C++ 函数?

import Data.Char
toUppers:: String -> String
toUppers s = map toUpper s

特别是,我想避免在返回类型中包含 IO,因为在逻辑上没有必要为这个简单的任务引入杂质(我的意思是 IO monad)。到目前为止,我看到的所有涉及 C 字符串的示例都涉及返回一个 IO 东西或 Ptr,它不能转换回纯 String

我想这样做的原因是我的印象是编组与 FFI 混淆。也许如果我能解决上面最简单的情况(除了原始类型,如 int),那么我可以在 C++ 端做任何我想要的数据解析,这应该很容易。

与我想要在编组到/从字符串之间进行的计算相比,解析的成本可以忽略不计。

提前致谢。

【问题讨论】:

  • 您能否提供更多关于您想要完成的任务的详细信息?来自 The RWH book.realworldhaskell.org/read/interfacing-with-c-the-ffi.html “但是,如果我们知道 C 代码是纯的,我们为什么不直接声明它,在导入声明中给它一个纯类型?因为我们必须分配本地内存为了使 C 函数能够使用, 这必须在 IO monad 中完成, 因为它是一个本地副作用. 但是, 这些效果不会逃避外部调用周围的代码, 所以当包装时, 我们使用 unsafePerformIO 重新引入纯度。”
  • @Jonke:更具体地说,我想做简单的计算(对于 C++),例如在 C++ 中求解线性方程组。解决方案需要在 C++ 中完成。这就是为什么我想使用 String 或等效(最终)跨 FFI 传输数据的原因。因此,我正在寻找一个在不引入 IO 的情况下将字符串或等价物编组到 C++ 世界的示例。顺便说一句,当我使用 windows/mingw 时,hmatrix 对我不起作用。所以我认为最可靠的解决方案是按照我上面的描述找出一个有效的 FFI 源。
  • 好吧,我认为你陷入了 C 领域。如果您想将整数或双精度数组(向量)从 haskell 传输到 C/C++ 并返回,则其签名将与传输 C 字符不同。 Haskell 中的字符串与 C char[] 完全不同。

标签: string haskell ffi purely-functional


【解决方案1】:

您至少需要在某个时候让IO 参与进来,以便为 C 字符串分配缓冲区。这里直接的解决方案可能是:

import Foreign
import Foreign.C
import System.IO.Unsafe as Unsafe

foreign import ccall "touppers" c_touppers :: CString -> IO ()
toUppers :: String -> String
toUppers s =
  Unsafe.unsafePerformIO $
    withCString s $ \cs ->
      c_touppers cs >> peekCString cs

我们使用withCString 将 Haskell 字符串编组到缓冲区中,将其更改为大写,最后将(更改!)缓冲区内容解组到新的 Haskell 字符串中。

另一个解决方案可能是将IO 的混乱委托给bytestring 库。无论如何,如果您对性能感兴趣,这可能是一个好主意。解决方案大致如下:

import Data.ByteString.Internal

foreign import ccall "touppers2" 
  c_touppers2 :: Int -> Ptr Word8 -> Ptr Word8 -> IO ()
toUppers2 :: ByteString -> ByteString
toUppers2 s =
  unsafeCreate l $ \p2 -> 
    withForeignPtr fp $ \p1 ->
      c_touppers2 l (p1 `plusPtr` o) p2
 where (fp, o, l) = toForeignPtr s

这更优雅一点,因为我们现在实际上不必进行任何编组,只需转换指针即可。另一方面,C++ 方面在两个方面发生了变化——我们必须处理可能非空终止的字符串(需要传递长度),现在必须写入不同的缓冲区,因为输入不再是副本。


作为参考,这里有两个适合上述导入的快速而简单的 C++ 函数:

#include <ctype.h>
extern "C" void touppers(char *s) {
    for (; *s; s++) *s = toupper(*s);
}
extern "C" void touppers2(int l, char *s, char *t) {
    for (int i = 0; i < l; i++) t[i] = toupper(s[i]);
}

【讨论】:

  • 感谢您的回答。您能否展示如何在 C/C++ 端转换为大写?我希望在 C++ 中“重新实现” toUppers 函数(如问题所示),因为我真正感兴趣的是让 C++ 完成从 Haskell 解析字符串的工作,进行一些计算(在这种情况下为 toUppers一个简单的例子),并将结果以字符串的形式传回给 Haskell。非常感谢。
  • 当然,给你 - 没什么太壮观或华丽的,但它应该让你继续前进。并且根据您对“解析”的含义,您应该尝试像 parsecattoparsec 这样的 Haskell 解析库。只要您使用ByteStringText 作为您的字符串,Haskell 可以非常快地处理此类事情。
  • 谢谢。 BTW,以上两个函数是懒惰还是和(map toUpper)除了效率有什么区别?
  • 即,用 FFI 实现替换 (map toUpper) 以获得相同的行为是否安全?另外,我试图在 windows/mingw 上加载它。 “ghc --make strFFI.hs touppers.cpp”有效,但“ghci strFFI.hs touppers.o”或“ghci strFFI.hs -ltouppers”不起作用。 ghci 说:最终链接... ghc.exe:touppers.o:未知符号“__imp_toupper”。有什么指点吗?
  • 不,他们一点也不懒——在无限列表上使用新的toUpper 不会终止。这个错误听起来你可能缺少一个库,也许添加“-lstdc++”有帮助?
猜你喜欢
  • 1970-01-01
  • 2014-05-20
  • 2021-10-15
  • 2014-02-16
  • 1970-01-01
  • 2017-06-03
  • 2011-01-01
  • 2018-11-27
  • 1970-01-01
相关资源
最近更新 更多