6

我想通过 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++ 端进行任何我想要的数据解析,这应该很容易。

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

提前致谢。

4

1 回答 1

7

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 字符串中。

另一种解决方案可能是将混乱委托IObytestring图书馆。无论如何,如果您对性能感兴趣,这可能是一个好主意。解决方案大致如下所示:

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]);
}
于 2013-06-03T11:01:04.400 回答