Hatena::ブログ(Diary)

sirocco の書いてもすぐに忘れるメモ

2012-11-06

COM を学ぶ(10) : SysAllocString を使って BSTR を作る。

| 04:56 |

Haskell から COM を呼ぶ ために MultiByteToWideCharを使わないでSysAllocStringで行うためのテストです。

SysAllocString で BSTR を作りますが、SysAllocStringはバイナリの文字列長を含まないワイド文字列の先頭アドレスを返します。読み取るときはCWStringと同等に扱っても問題ありません。

   4byte     可変長               2byte
  ┌───┬──────────┬─┐
  │length│ string             │\0│
  └───┴──────────┴─┘
          ↑
          SysAllocString はここのアドレスを返します。
--  ghc -Wall --make bstr.hs -loleaut32  -o bstr

module Main where

import Foreign.C.String      (newCWString, CWString, peekCWString)
import Foreign.Marshal.Alloc (free) -- 20130821 追記
import Foreign.C.Types       (CUInt)
import Cinnamon.Ucs          (ucs4ToSjis)


-- C の関数を呼ぶための定義
-- SysAllocString は BSTR を作りますが、CWStringと同等に扱っても問題ない。
foreign import stdcall "windows.h SysAllocString"    cSysAllocString     :: CWString -> IO CWString
foreign import stdcall "windows.h SysStringLen"      cSysStringLen       :: CWString -> IO CUInt
foreign import stdcall "windows.h SysStringByteLen"  cSysStringByteLen   :: CWString -> IO CUInt
foreign import stdcall "windows.h SysFreeString"     cSysFreeString      :: CWString -> IO ()

main :: IO()
main = do
  -- ファイルはUTF8で記述されていますがコンパイルすると ucs4になります。
  let ucs4 = "123あいう"
  putStrLn $ "ucs4->sjis:" ++ (ucs4ToSjis ucs4) -- > ucs4->sjis:123あいう

  -- Haskell文字列(リスト)からCのワイド文字列に変換し、BSTRを作る。
  -- 2013/08/21 指摘により修正
  -- bstr <- cSysAllocString =<< newCWString ucs4
  cwstr <- newCWString ucs4
  bstr  <- cSysAllocString cwstr
  free cwstr

  -- BSTR APIを使用してみる。
  len  <- cSysStringLen bstr
  print ("len:" ,len)                           -- > ("len:",6)

  -- バイト数は文字列長の2倍。
  byte <- cSysStringByteLen bstr
  print ("byte:",byte)                          -- > ("byte:",12)

  -- BSTR は単純に C のワイド文字列として扱える。
  str <- peekCWString bstr

  -- 最初の文字列とBSTRに変換し、BSTRから生成した文字列は等しい
  print ("ucs4 == str:", ucs4 == str)          -- > ("ucs4 == str:",True)

  putStrLn $ "str->sjis:" ++ (ucs4ToSjis str)  -- > str->sjis:123あいう

  -- BSTRはメモリを解放する必要がある。
  cSysFreeString bstr
  return()

char <=> wchar_t <=> BSTR 文字変換テスト

| 07:08 |

Haskell から COM を呼ぶ場合 HaskellからSJISに変換した後に MultiByteToWideChar、 WideCharToMultiByteを使用していましたが、Haskell はUcs4、COMはUTF16を使っていて、日本語で使う分には文字コードを変換しなくても良いはずです。

そこで、WideCharToMultiByteを使用しないでBSTRを作るために以下のサイトを参考にchar <=> wchar_t <=> BSTR 文字変換テストをしてみました。

/*
gcc mbs.c -o mbs  -loleaut32
*/
// http://simd.jugem.jp/?eid=120
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <locale.h>

int main(void)
{
  char *ms = "char  <=> wchar_t <=> BSTR 文字変換テスト";
  char    newms[100];
  wchar_t ws[100];
  int len;
  BSTR bstr;

  setlocale( LC_CTYPE, "jpn" );

  len = mbstowcs( ws, ms, 100 );
  printf( "ワイド文字数は %d文字\n", len );
  printf( "ワイド文字は %ls\n", ws );

  len = wcstombs( newms, ws, 100 );
  printf( "マルチバイト文字長は %dバイト\n", len );
  printf( "マルチバイト文字は %s\n", newms );

  /* ここでワイド文字列からBSTRを作ります。 */
  bstr = SysAllocString(ws);

  printf("BSTR文字長は%d\n",SysStringLen(bstr));
  len = wcstombs( newms, bstr, 100 );
  printf("BSTR文字は %s\n", newms);
  SysFreeString( bstr );

  return 0;
}
PS C:\HaskellPlatform\test\BSTR> gcc mbs.c -o mbs  -loleaut32
PS C:\HaskellPlatform\test\BSTR> ./mbs
ワイド文字数は 34文字
ワイド文字は char  <=> wchar_t <=> BSTR 文字変換テスト
マルチバイト文字長は 41バイト
マルチバイト文字は char  <=> wchar_t <=> BSTR 文字変換テスト
BSTR文字長は34
BSTR文字は char  <=> wchar_t <=> BSTR 文字変換テスト

happynowhappynow 2013/08/21 00:10 不躾ですいませんが、

> COM を学ぶ(10) : SysAllocString を使って BSTR を作る。
>
> -- Haskell文字列(リスト)からCのワイド文字列に変換し、BSTRを作る。
> bstr <- cSysAllocString =<< newCWString ucs4

newCWString の返すポインタは free しなくて良いのですか。

ところで、いつも有用な記事ありがとうございます。
大変助かっております。

siroccosirocco 2013/08/21 05:35 id:happynow
指摘ありがとうございます。
bstr <- cSysAllocString =<< newCWString ucs4
のところですね。
指摘されて気が付きました。
この場合は確認していないのですが、Cの文字列からHaskellの文字列へ変換してすぐfreeしてしまうとその文字列を使おうとするとsegmentationfaultを起こしてしまっていました。
最後にfreeにするのが良いと思います。

HaskellをVBのように使うのを目標に書いてきました。
お役にたつことがあれば嬉しいです。

siroccosirocco 2013/08/21 06:24 id:happynow
修正しました。
cSysAllocString の直後にfreeにしてもOKでした。Haskellの文字列からCの文字列へ変換し、さらに領域を確保して変換しているから大丈夫なんですね。