You need to involve IO
at least at some point, to allocate buffers for the C-strings. The straightforward solution here would probably be:
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
Where we use withCString
to marshall the Haskell string into a buffer, change it to upper-case and finally un-marshall the (changed!) buffer contents into the new Haskell string.
Another solution could be to delegate messing with IO
to the bytestring
library. That could be a good idea anyways if you are interested in performance. The solution would look roughly like follows:
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
This is a bit more elegant, as we now don't actually have to do any marshalling, just convert pointers. On the other hand, the C++ side changes in two respects - we have to handle possibly non-null-terminated strings (need to pass the length) and now have to write to a different buffer, as the input is not a copy anymore.
For reference, here are two quick-and-dirty C++ functions that fit the above imports:
#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]);
}