Windows ini file と ANSI/UNICODE ポータビリティー

2021/03/07

Windows ini ファイルは時代遅れという意見もあるが、構造がシンプルであることから素人手作業での取り回しがきわめて容易という利点は未だに失われていないと思います。
訳あって ini ファイルの読み込みを ANSI/UNICODE ポータブルに記述する必要があり、あとで参照するときの便利帳としてコードを置いておくことにします。
tstring, tcout, _TCHAR, _T マクロ等といったものの備忘録も兼ねています。


Win32API ini file 関数群の仕様と癖

Windows 10 の場合。

挙動調査のためのサンプルコード

この例では、読み込めた値は GetPrivateProfileSection で得たものから左辺(キー)と右辺(値)の分離を行っているが、上記の通りこの処置はあまり良くない。値の取得はあくまで「存在しないセクションやキーのテスト」のコメントがある箇所のように GetPrivateProfileString で行うのがベターである。

#include <windows.h>
#include <string>
#include <string.h> // _tcslen (= strlen & wcslen)
#include <iostream>
#include <vector>
#include <tchar.h>
#if defined(UNICODE) || defined(_UNICODE)
 #include <locale.h>
 #ifndef tcout
  #define tcout std::wcout
 #endif
 #ifndef tcerr
  #define tcerr std::wcerr
 #endif
 #ifndef tstring
  #define tstring std::wstring
 #endif
#else
 #ifndef tcout
  #define tcout std::cout
 #endif
 #ifndef tcerr
  #define tcerr std::cerr
 #endif
 #ifndef tstring
  #define tstring std::string
 #endif
#endif

using namespace std;

vector<tstring> splitszserial(const _TCHAR* szzString, const DWORD dwLen)
{
	vector<tstring> result;
	const _TCHAR* wp = szzString;
	size_t len_elem;
	
	while( (wp - szzString) < ptrdiff_t(dwLen) && (len_elem = _tcslen(wp) ) > 0){
		tcout << len_elem << '[' << tstring(wp) << ']' << endl;
		result.push_back(wp);
		wp += len_elem + 1;
	}
	return result;
}


int _tmain(int argc, const _TCHAR* argv[])
{
	// Windows コンソールにおける UNICODE 文字列の表示対応
	#if defined(UNICODE) || defined(_UNICODE)
	setlocale(LC_ALL, "Japanese");
	#endif
	// ANSI と UNICODE とでバッファ長の切り替え
	#if defined(UNICODE) || defined(_UNICODE)
	static const DWORD inibuflen = 32768;
	#else
	static const DWORD inibuflen = 65536;
	#endif
	
	// キー未発見時の文字列の戻り値(iniファイルの文字列値としては CRLF は存在できない)
	static const _TCHAR* defaultStr = _T("\x0D\x0A");
	// ini ファイル名:手抜き(argv[1] が有効であることを仮定)
	// なおカレントディレクトリの ini ファイルを読ませたい場合は
	// 起動引数に .\sample.ini としてパス指定を含める。
	const _TCHAR* iniFilePath = argv[1];
	
	DWORD dwRet;
	
	_TCHAR sectionNames[inibuflen];
	dwRet = GetPrivateProfileSectionNames(sectionNames, inibuflen, iniFilePath);
	tcout << "GetPrivateProfileSectionNames returns " << dwRet << endl;
	
	auto resultsections = splitszserial(sectionNames, dwRet);
	cout << resultsections.size() << endl;
	for ( auto & elemsection : resultsections ){
		tcout << _T("■セクション:[") << elemsection << ']' << endl ;
		_TCHAR keyValues[inibuflen];
		dwRet = GetPrivateProfileSection(elemsection.c_str(), keyValues, inibuflen, iniFilePath);
		tcout << "GetPrivateProfileSection returns " << dwRet << endl;
		auto resultkeys = splitszserial(keyValues, dwRet);
		for ( auto & elemkey : resultkeys ){
			tcout << _T("キー&値:[") << elemkey << "] ";
			// 左辺(キー)と右辺(値)の分離。ただし右辺の取り出しは GetPrivateProfileString の使用が望ましい。
			auto iDelim = elemkey.find('=');
			if(iDelim != tstring::npos){
				tcout
				<< _T("キー:[") << elemkey.substr(0, iDelim) << "] " 
				<< _T("値:[") << elemkey.substr(iDelim + 1) << "] " 
				;
				_TCHAR buf[inibuflen];
				dwRet = GetPrivateProfileString(elemsection.c_str(), elemkey.substr(0, iDelim).c_str(), defaultStr, buf, inibuflen, iniFilePath);
				tcout << endl << _T("GetPrivateProfileString returns ") << dwRet << endl ;
				tcout << "result: [" << buf << "] " << endl ;
			}else{
				tcout << _T("セクションにキーがありません") << endl;
			}
		}
	}
	
	// 存在しないセクションやキーのテスト
	tcout << _T("存在しないセクションやキーのテスト") << endl;
	_TCHAR buf[inibuflen];
	dwRet = GetPrivateProfileString(_T("Non-existentSection"), _T("Non-existentKey"), defaultStr, buf, inibuflen, iniFilePath);
	tcout << dwRet << "[" << buf << "]" << endl;
	if(tstring(buf) == tstring(defaultStr)){
		// キーが無い
		tcout << _T("セクションやキーが無い") << endl;
	}
	
	return 0;
}

ちなみに ANSI/UNICODE 両対応において _T マクロは非ASCII文字を含む文字列リテラルに必須だが、ASCII文字だけでできている文字列リテラルには必須ではない模様です。

マクロ UNICODE_UNICODE

UNICODE コンパイルを指定するにあたり、マクロ UNICODE_UNICODE のどっちを定義すればいいんだ?という疑問を調べたら、これらは対象物が違うので両方とも定義する必要がある。stackoverflow によると UNICODE は Win32API 関係(~A と ~W の切り替え)、_UNICODE は C/C++ runtime ヘッダ関係に寄与するとのこと。
具体的には cl /EHsc /MD /W4 /DUNICODE /D_UNICODE foobar.cpp 等とする。
今回の事例では、Win32API と CRT の両者にまたがる処置が必要なので、少なくとも片方が定義されていれば UNICODE コンパイルであると認識させるのが適切なようだ。
たしかこれと類似の話が TCHAR_TCHAR の関係性にもあった気がする。
また TEXT()_T() は異なるヘッダ(winnt.h と tchar.h)で定義されているため UNICODE_UNICODE が片方しか定義されていない場合はヘッダによって挙動が異なってしまうので要注意。UNICODE_UNICODE が両方とも定義されていれば実質的には同じになるとのこと。

参考文献


略歴


トップページ / 計算機関係の雑多なこと / Windows ini file と ANSI/UNICODE ポータビリティー
このサイトについて・著者連絡先