Programming Windows Maniacs - プログラミング ウィンドウズ マニアックス ■ ご利用に際して ■ 更新履歴 ■ お問い合わせ ■ このホームページについて  
ホーム >> 基本 >> UNICODEプログラミングのススメ

UNICODEプログラミングのススメ

  Visual C++を使う場合、Chicago系(Windows 95/98/98SE/Meのこと。以降Windows 9xとする)では、プロジェクトは_MBCSがデフォルトで定義されており、文字列に関してはMBCSで組むのが普通でした。
  しかし、Windows 9xに対応しなくてもよい時代がもうすぐ到来します。実際、Windows 9xに対応しないソフトも店頭市場でも販売されてきています。

  そこで、Windows 2000以降のWindows NT系のみの動作を考えると、UNICODEプログラミングの方が、圧倒的に問題点が少なくなります。
   Windows NT4もUNICODEプログラミングは可能ですが、ユーザーはほとんどいないので、対応を考えなくてもよい状態です。

  例えば、MBCSでは認識できない文字列も、UNICODEでは認識できるようになります。
  Webから保存したファイルの場合、ファイル名はTITLEタグにある名前がデフォルトですが、これはMBCSとは限らないのです。
  言語が日本語だとしても、記号はUNICODE専用の可能性もあります。
  このようなファイルをMBCSコンパイルしたプログラムから読み込もうとしても、パスを認識できません。
  "?"に置換され、判別できなくなります。UNICODEにすれば、当然読み込むことができます。
  Windows Vistaでは通常のフォントのバージョンが変更となり、日本語でも、UNICODEでないと読み込めない文字も多くなってきました。

  他にも、"\"区切りのとき、"表"のような"\"をASCIIコードで含む文字列の認識もMBCSコンパイルでは失敗しますが、UNICODEコンパイルでは、問題ありません。

  また、NT系は内部的に文字操作がUNICODEで動作しているため、UNICODEコンパイルした方が実行速度は速いです。

  これだけではありません。ActiveXコントロール等は、デフォルトで、UNICODE文字列を使用しているものが多く、MBCSコンパイルでは、毎回変換が必要になってしまいます。
  UNICODEコンパイルであれば、シームレスにプログラムすることが可能です。

  UNICODEでないとバグを誘発するAPIもいくつもあります。

  また、Windows CE系は元々UNICODEコンパイルでしたので、UNICODEでWindowsプログラムを行うと互換性を保てます。

  以上のような理由から、9xに対応しない製品を作成する場合は、UNICODEプログラミングをお勧めいたします。

 

  ただし、UNICODEプログラミングは注意する点がいくつかあります。
  (1) 文字数とバイト数が異なるため、メモリ初期化やAPI呼び出し時の文字数指定を注意すること。

  (2) プリプロセッサの定義で、_MBCSを消し、UNICODEを定義する。

  (3) 文字列を使用するAPIはA形式ではなくW形式がdefineされます。LoadLibrary ()する場合もWの方を呼び出すようにする。
下記は、GetWindowText () APIについて、winuser.hで定義されているAPIの情報です。

■ winuser.h から抜粋
WINUSERAPI
int
WINAPI
GetWindowTextA(
    __in HWND hWnd,
    __out_ecount(nMaxCount) LPSTR lpString,
    __in int nMaxCount);
WINUSERAPI
int
WINAPI
GetWindowTextW(
    __in HWND hWnd,
    __out_ecount(nMaxCount) LPWSTR lpString,
    __in int nMaxCount);
#ifdef UNICODE
#define GetWindowText GetWindowTextW
#else
#define GetWindowText GetWindowTextA
#endif // !UNICODE

  UNICODEを定義していないとき、GetWindowText () を呼び出すと、GetWindowTextA () がコールされます。
  UNICODEを定義しているときは、GetWindowTextW () がコールされます。

  (4) 文字列は、L"..."として記載する。

  以下、サンプルコードになります。
  「ファイルに関連する情報をHDDに保存するには(ファイルストリーム)」のサンプルをUNICODE版に書き換えたソースコードになります。

■■■ hardlink_unicode プロジェクト
main.cpp

// Windows 2000以降 ( CreateHardLink のため )
#define _WIN32_WINNT 0x0500

#include <windows.h>

#include <shlobj.h>

#include <string>

int WINAPI wWinMain ( HINSTANCE hThisInstance,
HINSTANCE,
LPTSTR,
int )
{
    enum
    {
        MY_MAX_PATH = 2048
    };
    wchar_t szDesktopPath[ MY_MAX_PATH ];
    SHGetSpecialFolderPath ( NULL, szDesktopPath, CSIDL_DESKTOPDIRECTORY, TRUE );

    wchar_t szSrcPath[ MY_MAX_PATH ];
    lstrcpy ( szSrcPath, szDesktopPath );
    lstrcat ( szSrcPath, L"\\a.txt" );

    wchar_t szDstPathBuf[ MY_MAX_PATH ];
    lstrcpy ( szDstPathBuf, szDesktopPath );
    lstrcat ( szDstPathBuf, L"\\a" );

// すでにファイルがあれば、ファイル名を変えて、10個まではハードリンクを作成する
// "a00.txt","a01.txt", ... ,"a09.txt"
    UINT i = 0;
    while ( 10 > i )
    {
        HANDLE hFindFile;
        WIN32_FIND_DATA win32FindData;
        std::basic_string < wchar_t > strPath ( szDstPathBuf );
        wchar_t szNum[ 80 ];
        wsprintf ( szNum, L"%02u", i );
        strPath += szNum;
        strPath += L".txt";

        hFindFile = FindFirstFile ( strPath.c_str (), &win32FindData );
        if ( INVALID_HANDLE_VALUE == hFindFile )
        {
            // ファイルがないので作成
            if ( CreateHardLink ( strPath.c_str (), szSrcPath, NULL ) )
            {
                MessageBox ( NULL,
                    L"ハードリンクを作成しました",
                    L"確認",
                    MB_OK );
            }
            else
            {
                MessageBox ( NULL,
                    L"ハードリンクの作成に失敗しました",
                    L"エラー",
                    MB_OK | MB_ICONSTOP );
            }

            return 0;
        }
        else
        {
            FindClose ( hFindFile );
        }

        i++;
    }

    MessageBox ( NULL,
        L"ハードリンクをこれ以上作成できません",
        L"確認",
        MB_OK | MB_ICONSTOP );

    return 0;
}

  もし、すぐMBCSから切り替えない場合は、UNICODEでもコンパイルできるようにコードを書いておくと後々ラクになります。
  ※ これは慣れなので、慣れれば開発速度はあまり落ちません。

  方法としては下記の通りです。
  (1) _TCHAR 型を使用する。
  (2) 文字列は_TEXT ( "..." )として記載する。
  (3) APIなどは、MBCS・UNICODEの両方で動作するように記載する。
  たとえば、メモリ初期化するときは下記のように記載する。

    enum
    {
        MY_MAX_BUFFER = 2048
    };
    _TCHAR sz[ MY_MAX_BUFFER ];
    memset ( sz, 0, MY_MAX_BUFFER * sizeof ( _TCHAR ) );

  Cの関数は、たいてい_tXXXX ()として定義されているので、そちらを使います。
  たとえば、ファイルを開くには、下記のように記載する。

  int nHandle;
  nHandle = _topen ( _TEXT ( "c:\\sample.txt" ), .....

  APIでは、A形式とW形式は #ifndef UNICODE ... #else ... #endif で切り替えてあるので、AやWを指定しないで呼び出す。
  たとえば、ウィンドウの文字列を取得したいのであれば、GetWindowTextAでもGetWindowTextWでもなく、GetWindowText () を使用します。

(4) どうしてもMBCSとUNICODEでコードが異なるときは、#ifndef UNICODE ... #else ... #endif のように記載し、UNICODEと_MBCS両方わけてコードを記載する。

  以下、「カスタムリソースを使用するには」のサンプルから抜粋しています。

    _TCHAR *psz;

#ifndef UNICODE // _MBCS のとき
    psz = new char[ lstrlen ( reinterpret_cast < char * > ( pRes ) ) + 1 ];
    wsprintf ( psz, _TEXT ( "%s" ), pRes );
#else // _UNICODE のとき
    UINT nSize;
    nSize = mbstowcs ( NULL, reinterpret_cast < char * > ( pRes ), 0 );
    psz = new wchar_t[ nSize + 1 ];
    memset ( psz, 0, sizeof ( wchar_t ) * ( nSize + 1 ) );
    mbstowcs ( psz, reinterpret_cast < char * > ( pRes ), nSize );
#endif
    MessageBox ( NULL,
        psz,
        _TEXT ( "リソースの中身" ),
        MB_OK );
    delete[] psz;

  ■ ご利用に際して ■ 更新履歴 ■ お問い合わせ ■ このホームページについて Copyright © 2014 A.Morita