UnicodeからShift-JISへの変換、どうする?

なし崩し的にDelphi Advent Calendar 2012に記事を書く羽目になった件について。(ぉぃ
まぁ、実業務でのちょっとした覚え書きな件もあるので、いい機会だし久しぶりに更新してみる。

ここ数年でプログラミング環境はUnicodeを意識せざるを得なくなった。DBのエンコーディングUTF-8であることなんて良くあること。コードを書く側としてはエンコーディング変換とかはフレームワークの類いがよろしくやってくれるから余り気にする必要は無いはず。
それに、C++11でUnicodeリテラルが導入されたから、こんな感じで、いろいろアレなことが出来る。

#include <vcl.h>
#include <stdio.h>
#pragma hdrstop

#include <tchar.h>

#pragma argsused
int _tmain(int argc, _TCHAR* argv[])
{
    UnicodeString str(U"\u264aジェミニ\u264aの黄金聖闘士ご来席!\u264aジェミニ\u264aの黄金聖闘士ご来席!");
    ShowMessage(str);
    return 0;
}

こいつを実行するとこんな感じで、しっかりと星座記号が出力される。

アプリケーションの内部はUnicodeだけど、それを加工してCSVに吐き出す場合、お客さんはUTF-8UTF-16なんてことは、わけわかめな世界であることが多々あることなので、要件定義として「CSVファイルのエンコーディングはShiftーJISとすること」ってのが追加されているはず。

VCLUnicodeからShiftーJISに変換する場合、素直にUnicodeStringからAnsiStringにキャストするのが定番と言えば定番。
以下のコードを追加して、Shift-JISに無い文字を出力するとどうなるか?

    AnsiString sjStr(str);
    printf("%s",  sjStr.c_str());

こんな感じで、コンソールに「?」マークとして出力される。

結果をodでダンプすると、こんな感じ。

0x3fなので、文字としてはShift-JISに存在しない文字は「?」に変換される。

でも、お客さんは「?」じゃ気に入らない。他の文字にしてくれとなるとどうするか?例えば、「_」(アンダースコア)にしてくれとか。
「s/?/_/g」じゃ駄目だよね?元々あった「?」までも置換されてしまう。

そんな場合は仕方ないので、WideCharToMultiByte APIを直呼びをする。

int _tmain(int argc, _TCHAR* argv[])
{
    UnicodeString str(U"\u264aジェミニ\u264aの黄金聖闘士ご来席!\u264aジェミニ\u264aの黄金聖闘士ご来席!");
    ShowMessage(str);

    // 変換出来無かった場合のデフォルトの文字(スペースに変換)
    const char* DefStr = " ";

    // バッファのサイズを取得
    int BufferSize = ::WideCharToMultiByte(932, 0, str.c_str(), -1, NULL, 0, DefStr, NULL);
    std::unique_ptr<char[]> szBuffer(new char[BufferSize]);

    // 文字列の変換
    int ret = ::WideCharToMultiByte(932, WC_NO_BEST_FIT_CHARS, str.c_str(), -1, szBuffer.get(), BufferSize, DefStr, NULL);

    printf("%s",  szBuffer.get());
}

でも、こいつには罠があって、変換できなかった文字に下駄文字(「〓」)のような全角文字は指定できない模様。その場合はどうしよう…。1文字単位でWideCharToMultiByteを呼び出してlpUsedDefaultCharの値をチェックするしか無いかも。

追記:
Delphiのコードが無い。ん〜!? なんのことかな フフフ…