第2.0版 (自作)文字列変換関数を追加
#はじめに
C# では文字列型は System.String だけです。一方、Visual C++ では、C 言語との互換性、Win16 との互換性、Win32 との互換性、テンプレート等々の関連で文字列とみなされる型はいろいろあります。
文字列は Visual Studio 2003 までは SJIS が標準だったらしいですが、Visual Studio 2005 からは Unicode (ワイド文字列) が標準になったそうで、混乱に輪がかかっています。よって、古いソースを Visual Studio 2017 でビルドすると、エラーがずらりと表示されます。
ここでは、それらについてまとめてみました。
#どんな「文字列」があるか?
Visual C++ にはどんな「文字列」があるか、ざっくり見てみましょう。もしかしたら、もっとあるかもしれませんが、比較的、目にするのはこんな感じです。
- char*, wchar_t* (C 言語互換の文字列型)
- LPSTR などの Windows.h で定義されたマクロ (多数あり)
- string, wstring (STL の文字列)
- CString (MFC の文字列)
- CStringT (MFC のテンプレート文字列)
- BSTR (COM の文字列)
- CComBSTR (ATL の文字列)
- String ^ (CLI の文字列)
#2つの Win32 API 関数
Win32 API 関数には、関数名の最後に A が付くものと W が付くものがあります。前者はマルチバイト文字列 (SJIS) 対応バージョン、後者がワイド文字列 (Unicode) 対応バージョンです。
これらの関数で関数名の最後の A や W のないバージョンがあって、現在は W 付きのバージョンの別名になっています。「現在は」の意味は、過去は A 付きのバージョンの別名になっていたためです。これは、Windows 9x までは OS 内部でも文字列のコードが Unicode でなく、日本語版の場合は、SJIS になっていたためです。
次のサンプルは、コンピュータ名を表示するものですが、最初の方がワイド文字列 (Unicode) バージョン、後の方が マルチバイト文字列 (SJIS) バージョンです。
こちらは GetComputerNameW を使用しています。stdafx.h には #include <Windows.h> を追加しておく必要があります。
#include "stdafx.h"
int main()
{
WCHAR compName[80];
DWORD buffSize = sizeof(compName);
if (GetComputerNameW(compName, &buffSize) != 0)
{
wprintf_s(L"%s\n", compName);
}
else
{
printf_s("エラー\n");
}
getchar();
return 0;
}
こちらは GetComputerNameA を使用しています。stdafx.h には #include <Windows.h> を追加しておく必要があります。
#include "stdafx.h"
int main()
{
CHAR compName[80];
DWORD buffSize = sizeof(compName);
if (GetComputerNameA(compName, &buffSize) != 0)
{
printf_s("%s\n", compName);
}
else
{
printf_s("エラー\n");
}
getchar();
return 0;
}
#tchar.h
tchar.h はプロジェクトを作成すると stdafx.h に自動的に含まれています。これに含まれるマクロを使用することで、同じソースからマルチバイト文字列 (SJIS)、ワイド文字列 (Unicode) のアプリをビルドできます。
このヘッダーファイルには次のようなマクロが含まれています。
##汎用テキストマッピング用のプリプロセッサ ディレクティブ
次のディレクティブはプロジェクトのプロパティで設定します。
#define | コンパイル後のアプリ状態 |
---|---|
_UNICODE | ワイド文字列 (Unicode) 対応 |
_MBCS | マルチバイト文字列 (SJIS) 対応 |
なし | ASCII 文字列対応 |
##汎用テキストのデータ型のマップ
次のマクロを使うと、_UNICODE, _MBCS の定義に従って(上の表のような)アプリがビルドされます。
マクロ | なし | _MBCS | _UNICODE |
---|---|---|---|
_TCHAR | char | char | wchar_t |
_TINT | int | unsigned int | wint_t |
_TSCHAR | signed char | signed char | wchar_t |
_TUCHAR | unsigned char | unsigned char | wchar_t |
_TXCHAR | char | unsigned char | wchar_t |
_T | 影響なし (プリプロセッサによって削除される) | 影響なし (プリプロセッサによって削除される) | L (後続の文字または文字列を対応する Unicode の文字または文字列に変換する) |
※ _T は _TEXT の省略形なので、_TEXT と書くこともできる。
例えば、_UNICODE が定義されている場合、次の2行は同じ結果になります。_UNICODE が定義されていない場合は、最初のほうはエラーとなるが、2行目のほうは正しくコンパイルされます。
wprintf_s(L"%s\n", str);
wprintf_s(_T("%s\n"), str);
#Windows.h
Win32 API 関数を使う場合、下のソースのように stdafx.h に Windows.h を手動で追加する必要があります。
// stdafx.h : 標準のシステム インクルード ファイルのインクルード ファイル、または
// 参照回数が多く、かつあまり変更されない、プロジェクト専用のインクルード ファイル
// を記述します。
//
#pragma once
#include "targetver.h"
#include <stdio.h>
#include <tchar.h>
// TODO: プログラムに必要な追加ヘッダーをここで参照してください
#include <Windows.h>
これにより Win32 API 関数を使う際に使用されるマクロ(型名)も使用可能になります。これらの型名は Win16 からのもので、16 / 32/ 64 ビットでコンパイルする場合に互換性があります。
これらの型には PSTR, LPSTR, LPCSTR, LPCTSTR など (その他多数) があります。これは推測ですが、P はポインタ, L は Long、C は定数、T は Text または Typed を意味します。L (Long) は、Win16 時代に関連するもので Win32 では L がなくても同じになります (たぶん)。T が付いた型は、Unicode、SJIS に関わらず正しくコンパイルできる型と思われます。
これらマクロの定義ですが、マルチバイト文字列の場合、次のような感じになるはずです(推測ですが)。ワイド文字列では char が wchar_t に変わります。
- PSTR char*
- LPSTR char*
- LPCSTR const char*
- LPCTSTR _TEXT(const char*)
因みに、Win16 時代だと PSTR は near char*、LPSTR は far char* とか near, far キーワードを使って表現されていたはずです。
#MFC, ATL ヘッダー
Visual Studio でプロジェクトを作成するとき、MFC や ATL の使用の有無を指定できます。これにより、CString などのクラスが使用できるようになります。
##CString
これは、MFC に含まれる文字列クラスです。MFC に含まれるクラスのメソッドのパラメータなどとしてよく使われます。これは推測ですが、旧型 Visual Basic の文字列をクラス化したもののように見えます。
C の文字列 (char*) を直接、扱うより便利なのですが、スレッドセーフではないようなので注意が必要です。
テンプレート版の CStringT というのもありますが、MFC のメソッドでは使われていないので、わざわざ覚える必要はないと思います。
##BSTR と CComBSTR
COM (Common Object Model) での文字列型は常に BSTR (ワイド文字列) です。ATL ではこれをカプセル化した CComBSTR というクラスでよく扱います。
#マルチバイト文字とワイド文字の変換
マルチバイト文字とワイド文字の変換のために、下のリンク先のようなマクロが用意されています。
ATL and MFC String Conversion Macros
マクロは ATL3.0 と ATL7.0 のものがあり、どちらも利用可能です。
特徴ですが、ATL3.0 は関数のような使い勝手、ATL7.0 は宣言のような使い勝手です。
ATL3.0 のマクロを使う場合は、USES_CONVERSION マクロを事前に実行しておく必要があります。
USES_CONVERSION;
BSTR bstr = A2W(szMsg);
#(自作)文字列変換関数
Visual C++ には、いろいろな文字列がありますが、たまに相互に変換したいときがあります。
そんなときのために変換関数を作りました。
自作かつ検証も十分でないので、どれだけ実用になるのかも問題ですが。
- Char^ CCharToClrChar(wchar_t c)
- wchar_t ClrCharToCChar(Char ^c)
- String^ BSTRToString(BSTR bstr)
- BSTR ClrStringToBSTR(String ^str)
- void ClrStringToBSTR(String ^str, BSTR* pbstr)
- BSTR AToW(LPSTR mbcs)
- void AToW(LPSTR mbcs, BSTR* wcs)
- LPSTR WToA(BSTR wcs)
- void WToA(BSTR wcs, LPSTR* mbcs)
- String^ CStrToCliStr(CString str)
- CString CliStrToCStr(String ^str)
- String^ StdToCliStr(std::wstring str)
- std::wstring CliStrToStd(String ^str)
##使用方法
後述する StringConvertLib.h をインクルードするだけで使用できます。stdafx.h の内容は特に修正しません。プロジェクトの文字コードは必ずワイド文字にします。
##関数一覧
宣言
Char^ CCharToClrChar(wchar_t c)
機能
ワイド文字を CLR 文字に変換する。
パラメータ
[in] wchar_t c: ワイド文字
戻り値
変換された CLR 文字
宣言
wchar_t ClrCharToCChar(Char ^c)
機能
CLR 文字からワイド文字に変換する。
パラメータ
[in] Char ^c: CLR 文字
戻り値
変換されたワイド文字
宣言
String^ BSTRToString(BSTR bstr)
機能
ワイド文字列 (BSTR) から CLR 文字列に変換する。BSTR は wchar_t* と同じ。
パラメータ
[in] BSTR bstr: ワイド文字列
戻り値
変換された CLR 文字列
宣言
BSTR ClrStringToBSTR(String ^str)
機能
CLR 文字列からワイド文字列 (BSTR) に変換する。BSTR は wchar_t* と同じ。
パラメータ
[in] String ^str: CLR 文字列
戻り値
変換されたワイド文字列 (BSTR)
注意
戻り値は不要になったら SysAllocString 関数を使って解放すること。
宣言
void ClrStringToBSTR(String ^str, BSTR pbstr)*
機能
CLR 文字列からワイド文字列 (BSTR) に変換する。BSTR は wchar_t* と同じ。
パラメータ
- [in] String ^str: CLR 文字列
- [out] 変換されたワイド文字列 (BSTR)
戻り値
なし
注意
戻り値は不要になったら SysAllocString 関数を使って解放すること。
宣言
BSTR AToW(LPSTR mbcs)
機能
マルチバイト文字列からワイド文字列に変換する。
パラメータ
[in] LPSTR mbcs: マルチバイト文字列 (日本語版 Windows では Shift_JIS 文字列)
戻り値
変換されたワイド文字列。BSTR は wchar_t* と同じ。
注意
戻り値は不要になったら delete すること。
宣言
LPSTR WToA(BSTR wcs)
機能
ワイド文字列からマルチバイト文字列 (日本語版 Windows では Shift_JIS 文字列) に変換する。
パラメータ
[in] BSTR wcs: ワイド文字列。BSTR は wchar_t* と同じ。
戻り値
変換されたマルチバイト文字列
注意
戻り値は不要になったら delete すること。
宣言
void AToW(LPSTR mbcs, BSTR wcs)*
機能
マルチバイト文字列 (日本語版 Windows では Shift_JIS 文字列) からワイド文字列に変換する。
パラメータ
- [in] LPSTR mbc: マルチバイト文字列
- [out] BSTR* wcs: 変換されたワイド文字列。BSTR* は wchar_t** と同じ。
戻り値
なし
注意
wcs は不要になったら delete すること。
宣言
void WToA(BSTR wcs, LPSTR mbcs)*
機能
ワイド文字列からマルチバイト文字列 (日本語版 Windows では Shift_JIS 文字列) に変換する。
パラメータ
- [in] BSTR wcs: ワイド文字列 (BSTR は wchar_t* と同じ)
- [out] LPSTR* mbcs: 変換されたマルチバイト文字列 (日本語版 Windows では Shift_JIS 文字列)。LPSTR* は char** と同じ
戻り値
なし
注意
mbcs は不要になったら delete すること。
宣言
String^ CStrToCliStr(CString str)
機能
CStringT から CLI String に変換する。
パラメータ
[in] CString str: MFC 互換 CString 文字列
戻り値
変換された CLR 文字列
宣言
CString CliStrToCStr(String ^str)
機能
CLI String から CStringT に変換する。
パラメータ
[in] String ^str: CLR 文字列
戻り値
変換された MFC 互換 CString 文字列
宣言
String^ StdToCliStr(std::wstring str)
機能
STL のワイド文字列を CLR 文字列に変換する。
パラメータ
[in] std::wstring str: STL のワイド文字列
戻り値
変換された CLR 文字列
宣言
std::wstring CliStrToStd(String ^str)
機能
CLR 文字列を STL のワイド文字列に変換する。
パラメータ
[in] String ^str: CLR 文字列
戻り値
変換された STL のワイド文字列
##使用例
// TestConvertString.cpp : メイン プロジェクト ファイルです。
#include "stdafx.h"
#include "StringConvertLib.h"
using namespace System;
using namespace StringConvertLib;
int main(array<System::String ^> ^args)
{
Console::WriteLine(L"==== StringConvertLib のテスト ====\n");
// C スタイル文字から CLR 文字へ
Console::WriteLine("C スタイル文字から CLR 文字へ");
Char ^c = CCharToClrChar('C');
Console::WriteLine(c);
// CLR 文字からCスタイル文字へ
char cc = ClrCharToCChar(c);
printf_s("%c\n", cc);
// Cスタイル文字列から CLR 文字列へ
String ^clistr = BSTRToString(L"Cスタイル文字列");
Console::WriteLine(clistr);
// CLR 文字列から C スタイル文字列へ
String ^clistr2 = L"CLR 文字列";
setlocale(LC_ALL, "ja-JP");
BSTR bstr = ClrStringToBSTR(clistr2);
wprintf_s(L"%ls\n", bstr);
SysFreeString(bstr);
// CLR 文字列から C スタイル文字列へ (marshaling バージョン)
clistr2 = L"CLR 文字列 marshal";
ClrStringToBSTR(clistr2, &bstr);
wprintf_s(L"%ls\n", bstr);
// マルチバイト文字列からワイド文字列へ
BSTR bstr2 = AToW("AToW");
wprintf_s(L"%ls\n", bstr2);
delete bstr2;
// ワイド文字列からマルチバイト文字列へ
LPSTR pstr = WToA(L"WToA");
printf_s("%s\n", pstr);
delete pstr;
// CStringT から CLI String へ
CString atlstr = L"CStringT";
String ^clistr3 = CStrToCliStr(atlstr);
Console::WriteLine(clistr3);
// CLI String から CStringT へ
String ^clistr4 = L"CLI String";
CString atlstr2 = CliStrToCStr(clistr4);
wprintf_s(L"%ls\n", atlstr2);
// std::wstring から CLI String へ
std::wstring ststr = L"std::wstring";
String ^clistr5 = StdToCliStr(ststr);
Console::WriteLine(clistr5);
// CLI String から std::wstring へ
String ^clistr6 = L"CLI String";
std::wstring ststr2 = CliStrToStd(clistr6);
wprintf_s(L"%ls\n", ststr2.c_str());
// 終わり
getchar();
return 0;
}
#ソース (StringConvetLib.h)
#pragma once
#include <Windows.h>
#include <stdio.h>
#include <locale.h>
#include <tchar.h>
#include <stdlib.h>
#include <string.h>
#include <msclr/marshal.h>
#include <atlbase.h>
#include <atlconv.h>
#include <atlstr.h>
#include <string>
using namespace System;
using namespace msclr::interop;
#define BUFFLEN 2048
namespace StringConvertLib {
//
// C スタイル文字から CLR 文字へ
// ==============================
Char^ CCharToClrChar(wchar_t c)
{
Char ^gc = gcnew Char(c);
return gc;
}
//
// CLR 文字からCスタイル文字へ
// ============================
wchar_t ClrCharToCChar(Char ^c)
{
wchar_t wc = (wchar_t)c;
return wc;
}
// Cスタイル文字列から CLR 文字列へ
// BSTR は wchar_t* と同じ
String^ BSTRToString(BSTR bstr)
{
String^ str = gcnew String(bstr);
return str;
}
// CLR 文字列から C スタイル文字列へ
BSTR ClrStringToBSTR(String ^str)
{
array<Char>^ buff = str->ToCharArray();
pin_ptr<Char> wptr = &buff[0];
BSTR bstr = SysAllocString(wptr);
return bstr;
}
// CLR 文字列から C スタイル文字列へ (marshaling バージョン)
// msclr/marshal.h が必要
void ClrStringToBSTR(String ^str, BSTR* pbstr)
{
marshal_context^ context = gcnew marshal_context();
const wchar_t* str2 = context->marshal_as<const wchar_t*>(str);
*pbstr = SysAllocString(str2);
}
// マルチバイト文字列からワイド文字列へ
BSTR AToW(LPSTR mbcs)
{
size_t rv;
wchar_t buffer[BUFFLEN];
mbstowcs_s(&rv, buffer, BUFFLEN, mbcs, _TRUNCATE);
size_t n = 2 * wcslen((const wchar_t*)buffer) + 2;
wchar_t* p = new wchar_t[n];
wcscpy_s(p, n, buffer);
return p;
}
// ワイド文字列からマルチバイト文字列へ
LPSTR WToA(BSTR wcs)
{
size_t rv;
char buffer[BUFFLEN];
setlocale(LC_CTYPE, "ja-JP");
wcstombs_s(&rv, buffer, BUFFLEN, wcs, _TRUNCATE);
size_t n = strlen(buffer);
n++;
char* astr = new char[n];
strcpy_s(astr, n, buffer);
return astr;
}
// マルチバイト文字列からワイド文字列へ
// atlconv.h が必要
void AToW(LPSTR mbcs, BSTR* wcs)
{
ATL::CA2W ws(mbcs);
size_t n = 2 * wcslen((wchar_t*)ws) + 2;
*wcs = new wchar_t[n];
wcscpy_s((wchar_t*)*wcs, n, (wchar_t*)ws);
return;
}
// ワイド文字列からマルチバイト文字列へ
// atlconv.h が必要
void WToA(BSTR wcs, LPSTR* mbcs)
{
setlocale(LC_CTYPE, "ja-JP");
ATL::CW2A str(wcs);
size_t n = strlen((char*)str) + 1;
*mbcs = new char[n];
strcpy_s(*mbcs, n, (char*)str);
return;
}
// CStringT から CLI String へ
String^ CStrToCliStr(CString str)
{
String^ clistr = gcnew String(str.GetBuffer());
return clistr;
}
// CLI String から CStringT へ
CString CliStrToCStr(String ^str)
{
array<Char>^ buff = str->ToCharArray();
pin_ptr<Char> wptr = &buff[0];
return CString(wptr);
}
// std::wstring から CLI String へ
String^ StdToCliStr(std::wstring str)
{
String ^clistr = gcnew String(str.c_str());
return clistr;
}
// CLI String から std::wstring へ
std::wstring CliStrToStd(String ^str)
{
array<Char>^ buff = str->ToCharArray();
pin_ptr<Char> wptr = &buff[0];
return std::wstring((wchar_t*)wptr);
}
}