Win32 API の WideCharToMultiByteを使って UTF-16 ⇔ Shift-JIS 変換する。

UTF-16 ⇔ Shift-JIS の変換は計算によって行うことが出来ません。
そこで Win32 APIのMultiByteToWideChar、WideCharToMultiByteを使って変換します。

  • shiftJisToUtf16

Haskell 文字列をWindowsワイド文字列に変換したあとにワイド文字列からpeekCWStringでHaskell文字列を作っています。
そこでその変換した値を返す前にcSysFreeStringでWindowsワイド文字列の領域をクリアするのですが、ふとポインタで指している値が消えると返す値まで消えてしまうのではと心配しました。
Haskell文字列の1文字1文字はヒープにメモリを確保したデータではなくUnboxedな値です。Haskellは常に新しい値を作って返しますから、ポインタが指している値が消えてもHaskell の文字列が消えるなんてないはので開放しても良いはずです。

  • utf16ToShiftJis

C で確保したメモリは free で開放しています。

{-# LANGUAGE ForeignFunctionInterface #-}
-- ghc --make -Wall utf8.hs bstr.c -loleaut32 -o utf8

module Main where

import Foreign.Marshal.Alloc 
import Foreign.C.String

shiftJisToUtf16 :: String -> IO String
shiftJisToUtf16 string = do
    bstr <- withCString string c_CStringToBSTR
    str <- peekCWString bstr
    cSysFreeString bstr  -- ※※※※
    return str

utf16ToShiftJis :: String -> IO String
utf16ToShiftJis string = do 
    cstring <- withCWString string c_BSTRtoCString
    str     <- peekCString cstring
    free cstring -- ※※※※
    return str

-- C の関数を呼ぶための定義
foreign import ccall unsafe "CStringToBSTR"      c_CStringToBSTR   :: CString    -> IO CWString
foreign import ccall unsafe "BSTRtoCString"      c_BSTRtoCString   :: CWString   -> IO CString
foreign import stdcall "windows.h SysFreeString"     cSysFreeString      :: CWString -> IO ()

main :: IO ()
main = putStrLn =<< utf16ToShiftJis "こんにちは"

WideChatToMultiByteは2番目の引数にWC_NO_BEST_FIT_CHARSというフラグを指定することができます。指定するとShift_JISの文字が存在しない場合は特定の文字(デフォルトでは '?')に変換されます。指定されていないとよく似た文字に変換されます。

#include <malloc.h>
#include <windows.h>

BSTR CStringToBSTR(char* cstring ){
    int    cstringlen, out_size;
    BSTR   wstr;
    cstringlen = strlen(cstring);
    // Shift-JIS文字列からUTF-16に変換したときの文字列長を求める。
    out_size   = MultiByteToWideChar(CP_ACP, 0, cstring, cstringlen, NULL, 0);
    // UTF-16文字列の領域を確保する。
    wstr       = SysAllocStringLen(NULL, out_size);
    // Shift-JIS文字列からUTF-16に変換する。
    MultiByteToWideChar(CP_ACP, 0, cstring, cstringlen, wstr, out_size);
    return wstr;
}

char* BSTRtoCString(BSTR bstr){
    int    out_size;
    char   *cstring;
    // UTF-16文字列からShift-JISに変換したときの文字列長を求める。
    out_size = WideCharToMultiByte(CP_ACP, 0, (OLECHAR*)bstr, -1,NULL, 0, NULL, NULL);
    // Shift-JIS文字列の領域を確保する。
    cstring  = (char*)malloc((out_size+1) * sizeof(char));
    // UTF-16文字列からShift-JISに変換する。
    WideCharToMultiByte(CP_ACP, 0, (OLECHAR*)bstr, -1, cstring,out_size, NULL, NULL);
    return cstring;
}

参考