前の10件 4  5  6  7  8  9  10  11  12  13  14

記事一覧

DLLファイル名からCLSIDを取得する

詳細が公開されていないソフト内のインターフェースをホストしたい場合など、DLL内にあるCLSIDを取得したいことがごくまれにある。

LoadTypeLib でDLLを読み込みITypeLibを取得してから、ITypeIfo::GetTypeAttr()を使うことで取得できる。


下の使用例では、Windows標準のシェル関連DLL「shdocvw.dll」を読み込み、GUIDのCLSIDからプログラムIDを取得、出力した。
Shell.Explorer.1
Shell.Explorer.2
InternetExplorer.Application.1
Shell.UIHelper.1
ShellNameSpace.ShellNameSpace.1
ShellShellNameSpace.ShellShellNameSpace.1


bool	Test()
{
	bool	ret;
	size_t	i;
	size_t	nSize;
	HRESULT	hr;
	WCHAR*	pwszProgID;

	CAtlArray<GUID>		aguidCoClass;

	//shdocvw.dll内のCLSIDを取得
	ret = CLSIDFromDLLFile(_T("c:\\windows\\system32\\shdocvw.dll"),aguidCoClass);
	if(ret == false)
		return	false;

	nSize = aguidCoClass.GetCount();
	for(i = 0; i < nSize; i++)
	{
		//GUIDからプログラムIDの文字列に変換してATLTRACEで出力
		pwszProgID = NULL;
		hr =  ::ProgIDFromCLSID(aguidCoClass[i],&pwszProgID);
		if(SUCCEEDED(hr))
		{
			ATLTRACE("%s\n",(CAtlString)pwszProgID);
			::CoTaskMemFree(pwszProgID);
		}
	}

	return	true;
}






#include "atlstr.h"
#include "atlcoll.h"


/*!
 * \brief
 * DLL中のCLSIDを取得する
 * 
 * \param[in]	pszFile
 * タイプライブラリをチェックするDLLファイルのパス
 * 
 * \param[out]	aguidCoClass
 * COCLASSとして登録されているGUIDの配列
 * 
 * \retval	true	成功(1つ以上取得できた)
 * \retval	false	失敗(1つも取得できなかった)
 * 
 */
bool	CLSIDFromDLLFile(LPCTSTR pszFile,CAtlArray<GUID>& aguidCoClass)
{
	UINT		i;
	UINT		nCount;
	HRESULT		hr;
	TYPEATTR*	pTypeAttr;

	CComPtr<ITypeLib>	pITypeLib;
	CComPtr<ITypeInfo>	pITypeInfo;

	aguidCoClass.RemoveAll();

	hr = ::LoadTypeLib((CAtlStringW)pszFile,&pITypeLib);
	if(FAILED(hr))
		return	false;

	nCount = pITypeLib->GetTypeInfoCount();
	for(i = 0; i < nCount ; i++)
	{
		pITypeInfo = NULL;
		hr = pITypeLib->GetTypeInfo(i,&pITypeInfo);
		if(FAILED(hr))
			continue;

		pTypeAttr = NULL;
		hr = pITypeInfo->GetTypeAttr(&pTypeAttr);
		if(FAILED(hr))
			continue;

		if(pTypeAttr->typekind == TKIND_COCLASS)
		{
			aguidCoClass.Add(pTypeAttr->guid);
		}
		pITypeInfo->ReleaseTypeAttr(pTypeAttr);
	}

	return	aguidCoClass.GetCount() ? true : false;
}

プロジェクトファイルをダウンロード

ウインドウの位置情報を保存/復元するクラス

前回閉じたときと同じ場所にウインドウを開きたいと思うことがしばしばある。そういうときに使うためのクラス。

ウインドウ位置の取得、復元、レジストリへの書き込み、レジストリからの読み込みなどに対応する。


■ウインドウの位置を保存したいときは WM_DESTROY などが呼ばれたときに以下のように呼ぶ。
	CDnpWindowPlacement	cPlacement;
	cPlacement.GetCurrentPos(m_hWnd);
	cPlacement.SaveToReg(HKEY_CURRENT_USER,_T("software\\dinop\\test"),_T("WindowPlacement"));


■ウインドウの位置を復元したいときは CreateWindow によるウインドウ生成処理が終わった後で以下のように呼ぶ。
	CDnpWindowPlacement	cPlacement;
	cPlacement.LoadFromReg(HKEY_CURRENT_USER,_T("software\\dinop\\test"),_T("WindowPlacement"));
	cPlacement.Restore(hWnd);


依存環境:ATL
#pragma	once


#include "atlfile.h"
#include "atlstr.h"


/*!
 * \brief
 * ウインドウ位置取得/復元クラス
 * 
 * アプリケーション終了時の位置を保存しておく用途を想定
 * 
 * \remarks
 * APIのGetWindowPlacecement/SetWindowPlacecementにより
 *ウインドウ位置の取得と復元を行う
 * 
 * \attention
 * GetMonitorInfoを利用しているためWindows 2000以上必須
 * 
 */
class	CDnpWindowPlacement
{
public:
	WINDOWPLACEMENT	_sWindowPlacement;		//!< ウインドウの位置情報


	CDnpWindowPlacement()
	{
		::ZeroMemory(&_sWindowPlacement,sizeof(WINDOWPLACEMENT));
		//IsValid()で_sWindowPlacement.lengthを利用しているので
		//ここでそのメンバを初期化しない
	}




	/*!
	 * \brief
	 * このクラスに正常な情報が入っているかどうかのチェック
	 * 
	 * \retval	true	保持しているWINDOWPLACEMENT構造体に正しい情報が入っている
	 * \retval	false	正しい情報を保持していない
	 * 
	 * \todo
	 * もう少しチェックした方がいい?
	 */
	bool	IsValid(void)	const
	{
		if(_sWindowPlacement.length != sizeof(WINDOWPLACEMENT))
			return	false;

		return	true;
	}



	/*!
	 * \brief
	 * 指定したウインドウの位置情報をクラス内に読み込む
	 * 
	 * \param hWnd
	 * 位置情報を読み込みたいウインドウのHWND
	 * 
	 * \retval	true	成功
	 * \retval	false	失敗
	 * 
	 */
	bool	GetCurrentPos(HWND hWnd)
	{
		BOOL	ret;

		::ZeroMemory(&_sWindowPlacement,sizeof(WINDOWPLACEMENT));
		_sWindowPlacement.length = sizeof(WINDOWPLACEMENT);

		ret = ::GetWindowPlacement(hWnd,&_sWindowPlacement);
		if(ret == FALSE)
		{
			::ZeroMemory(&_sWindowPlacement,sizeof(WINDOWPLACEMENT));
			return	false;
		}

		return	true;
	}






	/*!
	 * \brief
	 * ウインドウの位置情報を指定されたウインドウに適用
	 * 
	 * \param hWnd
	 * 
	 * \param bMinimizeToRestore
	 * 位置情報保存時に最小化されていた場合の処理指定\n
	 * trueなら最小化されたウインドウは元に戻して表示\n
	 * falseなら最小化されたウインドウは最小化して表示\n
	 * 
	 * \param bNotModifyPlace
	 * trueならウインドウ位置は保存されていた情報通りに復元\n
	 * falseのときはウインドウ位置が変な場合は適宜変更する\n
	 * 
	 * \param prectDefault
	 * bNotModifyPlace==trueのときは無視される。\n
	 * prectDefaultが設定されていないときで、かつ、、、\n
	 * 復元したときにタイトルバーが一部しか見えない場合は、タイトルバーが見えるように上下移動\n
	 * 復元したときにウインドウが全然見えない場合は、占有モニターの左上に移動する\n
	 * prectDefaultが設定されている場合はprectDefaultに復元箇所を変更する
	 * 
	 * \attention
	 * OnCreate(WM_CREATE処理)内では利用できない!
	 * 起動時に処理したい場合はCreateWindowを呼んだ後もしくは
	 * CreateWindowを派生するか、何度も実行しないようにフラグ
	 * 変数を用意した上で何かのメッセージ処理時に利用する
	 * 
	 * \retval	true	成功
	 * \retval	false	失敗
	 * 
	 */
	bool	Restore(HWND hWnd,bool bMinimizeToRestore=true,bool bNotModifyPlace=false,const RECT* prectDefault=NULL)	const
	{
		BOOL			ret;
		WINDOWPLACEMENT	sWorkWP;			//作業用のWINDOWPLACEMENT

		if(IsValid() == false)
			return	false;

		sWorkWP = _sWindowPlacement;

		if(bNotModifyPlace == false)
		{
			HMONITOR	hMonitor;
			MONITORINFO	sMonitorInfo;

			hMonitor = ::MonitorFromRect(&sWorkWP.rcNormalPosition,MONITOR_DEFAULTTONEAREST);
			if(hMonitor)
			{
				bool	bModify;
				LONG	cx;
				LONG	cy;
				RECT	rect;

				bModify = false;

				::ZeroMemory(&sMonitorInfo,sizeof(MONITORINFO));
				sMonitorInfo.cbSize = sizeof(MONITORINFO);

				ret = ::GetMonitorInfo(hMonitor,&sMonitorInfo);
				if(ret)
				{
					//ウインドウの上部10ピクセル(タイトルバー全体とは言わないま
					//でも、その一部)が見えるかどうかのチェック
					if(bModify == false)
					{
						RECT	rectUp;

						rectUp = sWorkWP.rcNormalPosition;
						rectUp.bottom = (rectUp.top + 10 < rectUp.bottom) ? rectUp.top + 10 : rectUp.bottom;

						ret = IntersectRect(&rect,&rectUp,&sMonitorInfo.rcMonitor);
						if(ret == FALSE)
						{
							//復元場所はタイトルバーが表示されない位置にある

							//サイズは変更せずにウインドウ上部が見える位置に上下移動
							cy = sWorkWP.rcNormalPosition.bottom - sWorkWP.rcNormalPosition.top;
							sWorkWP.rcNormalPosition.top		= sMonitorInfo.rcMonitor.top;
							sWorkWP.rcNormalPosition.bottom	= sWorkWP.rcNormalPosition.top + cy;
							bModify = true;
						}
					}

					//画面全体のRECTとウインドウのRECTの重複部分領域を調べ
					//きちんと表示されるかをチェック
					if(bModify == false)
					{
						ret = IntersectRect(&rect,&sWorkWP.rcNormalPosition,&sMonitorInfo.rcMonitor);

						if(ret == FALSE)
						{
							//復元場所はウインドウがまったく見えない位置にある

							//サイズは変更せずにモニターの左上に移動
							cx = sWorkWP.rcNormalPosition.right - sWorkWP.rcNormalPosition.left;
							cy = sWorkWP.rcNormalPosition.bottom - sWorkWP.rcNormalPosition.top;
							sWorkWP.rcNormalPosition.left		= sMonitorInfo.rcMonitor.left;
							sWorkWP.rcNormalPosition.top		= sMonitorInfo.rcMonitor.top;
							sWorkWP.rcNormalPosition.right		= sWorkWP.rcNormalPosition.left + cx;
							sWorkWP.rcNormalPosition.bottom		= sWorkWP.rcNormalPosition.top + cy;
							bModify = true;
						}
					}

					//ウインドウの移動が必要な場合で、かつ、prectDefaultが設定されていたら
					//デフォルト表示位置をprectDefaultに変える
					if(bModify && prectDefault)
						sWorkWP.rcNormalPosition = *prectDefault;

					//bModify == falseでも表示できる
				}
			}
		}

		if(bMinimizeToRestore)
		{
			//最小化された状態の位置データが保存されていた
			//「最小化」して表示ではなく、「元の状態に戻す」をして表示する

			switch(sWorkWP.showCmd)
			{
			case	SW_MINIMIZE:
			case	SW_SHOWMINIMIZED:
			case	SW_SHOWMINNOACTIVE:
			case	SW_FORCEMINIMIZE:
			   if(sWorkWP.flags & WPF_RESTORETOMAXIMIZED)
					sWorkWP.showCmd = SW_SHOWMAXIMIZED;
			   else
					sWorkWP.showCmd = SW_RESTORE;
			   break;
			}
		}

		ret = ::SetWindowPlacement(hWnd,&sWorkWP);

		return	ret ? true : false;
	}



	/*!
	 * \brief
	 * ファイルに位置情報を保存(テスト用関数)
	 * 
	 * \param pszFile
	 * 保存するファイルパス
	 * 
	 * \retval	true	成功
	 * \retval	false	失敗
	 * 
	 * \remarks
	 * これはテスト用の関数です。実用性ゼロ。
	 * 
	 */
	bool	SaveFile(LPCTSTR pszFile)	const
	{
		CAtlFile	cFile;
		HRESULT		hr;
		DWORD		dwWritten;

		if(IsValid() == false)
			return	false;

		hr = cFile.Create(pszFile,GENERIC_WRITE,FILE_SHARE_READ,CREATE_ALWAYS);
		if(FAILED(hr))
			return	false;

		dwWritten = 0;
		cFile.Write(&_sWindowPlacement,sizeof(WINDOWPLACEMENT),&dwWritten);

		cFile.Close();

		if(dwWritten != sizeof(WINDOWPLACEMENT))
			return	false;

		return	true;
	}




	/*!
	 * \brief
	 * ファイルから位置情報を読み込む(テスト用関数)
	 * 
	 * \param pszFile
	 * 保存されているファイルパス
	 * 
	 * \retval	true	成功
	 * \retval	false	失敗
	 * 
	 * \remarks
	 * これはテスト用の関数です。実用性ゼロ。
	 * 読み込んだ位置情報はクラス内に保持され、 CDnpWindowPlacement::Restore() 関数でウインドウを
	 * 復元できます。
	 * 
	 */
	bool	ReadFile(LPCTSTR pszFile)
	{
		CAtlFile	cFile;
		HRESULT		hr;
		DWORD		dwRead;

		WINDOWPLACEMENT	sTmp;

		hr = cFile.Create(pszFile,GENERIC_READ,FILE_SHARE_READ,OPEN_EXISTING);
		if(FAILED(hr))
			return	false;

		dwRead = 0;
		cFile.Read(&sTmp,sizeof(WINDOWPLACEMENT),dwRead);

		cFile.Close();

		if(dwRead != sizeof(WINDOWPLACEMENT))
			return	false;

		if(sTmp.length != sizeof(WINDOWPLACEMENT))
			return	false;

		::memcpy_s(&_sWindowPlacement,sizeof(WINDOWPLACEMENT),&sTmp,sizeof(WINDOWPLACEMENT));

		return	true;
	}



	/*!
	 * \brief
	 * 位置情報のレジストリへの書き込み
	 * 
	 * このクラス内に保持する位置情報を書き込む。事前に GetCurrentPos() を実行して位置情報を取得しておく必要あり。
	 * 
	 * \param[in]	hRootKey
	 * 書き込むレジストリのルート種類。\n
	 * HKEY_CURRENT_USER を推奨
	 * 
	 * \param[in]	pszSubKey
	 * 書き込むレジストリの場所。\n
	 * 「software\\dinop\\test\\」のようにする。
	 * 
	 * \param[in]	pszName
	 * 位置情報を書き込むときのレジストリの名前。たとえば「WindowPlacement」のようにする。
	 * 
	 * \retval	true	成功
	 * \retval	false	失敗
	 * 
	 */
	bool	SaveToReg(HKEY hRootKey,LPCTSTR pszSubKey,LPCTSTR pszName)
	{
		LONG	ret;
		CRegKey	cReg;

		if(IsValid() == false)
			return	false;

		ret = cReg.Create(hRootKey,pszSubKey);
		if(ret != ERROR_SUCCESS)
			return	false;

		ret = cReg.Open(hRootKey,pszSubKey,KEY_WRITE);
		if(ret != ERROR_SUCCESS)
			return	false;

		ret = cReg.SetBinaryValue(pszName,&_sWindowPlacement,sizeof(WINDOWPLACEMENT));

		cReg.Close();

		if(ret != ERROR_SUCCESS)
			return	false;

		return	true;

	}



	/*!
	 * \brief
	 * 位置情報のレジストリからの読み込み
	 * 
	 * あくまでもこのクラス内に位置情報を読み込むだけで、実際のウインドウの位置は変更しない。
	 * 
	 * \param[in]	hRootKey	SaveToReg() で書き込むときに指定したのと同じ値を指定
	 * \param[in]	pszSubKey	SaveToReg() で書き込むときに指定したのと同じ値を指定
	 * \param[in]	pszName		SaveToReg() で書き込むときに指定したのと同じ値を指定
	 * 
	 * \retval	true	成功
	 * \retval	false	失敗
	 * 
	 */
	bool	LoadFromReg(HKEY hRootKey,LPCTSTR pszSubKey,LPCTSTR pszName)
	{
		LONG	ret;
		CRegKey	cReg;
		ULONG	nBytes;

		ret = cReg.Open(hRootKey,pszSubKey,KEY_READ);
		if(ret != ERROR_SUCCESS)
			return	false;

		nBytes = sizeof(WINDOWPLACEMENT);
		ret = cReg.QueryBinaryValue(pszName,&_sWindowPlacement,&nBytes);

		cReg.Close();

		if(ret != ERROR_SUCCESS || nBytes != sizeof(WINDOWPLACEMENT) || IsValid() == false)
		{
			::ZeroMemory(&_sWindowPlacement,sizeof(WINDOWPLACEMENT));
			return	false;
		}

		return	true;

	}







	/*!
	 * \brief
	 * 16進文字列として位置情報を返す
	 * 
	 * \param strData
	 * 取得した位置情報文字列
	 * 
	 * \remarks
	 * WINDOWPLACEMENT構造体をASCIIのHEX文字列でベタに返している
	 * 
	 * \retval	true	成功
	 * \retval	false	失敗
	 * 
	 */
	bool	GetFormatedData(CAtlString& strData)	const
	{
		bool	ret;
		TCHAR	pszTmp[sizeof(WINDOWPLACEMENT) * 2 + 10];

		ret = GetFormatedData(pszTmp,sizeof(WINDOWPLACEMENT) * 2 + 10);
		if(ret == false)
			return	false;

		strData = pszTmp;

		return	true;

	}



	/*!
	 * \brief
	 * 16進文字列として位置情報を返す
	 * 
	 * \param pszData
	 * 取得した位置情報文字列を格納するバッファ
	 * 
	 * \param cchLength
	 * 取得した位置情報文字列を格納するバッファの文字数
	 * (sizeof(WINDOWPLACEMENT) * 2 + 1)以上必要
	 * 
	 * \remarks
	 * WINDOWPLACEMENT構造体をASCIIのHEX文字列でベタに返している
	 * バッファーサイズの指定ミスを減らすため CDnpWindowPlacement::GetFormatedData() を利用すべき
	 * 
	 * \retval	true	成功
	 * \retval	false	失敗
	 * 
	 */
	bool	GetFormatedData(TCHAR* pszData,int cchLength)	const
	{
		int		i;
		bool	ret;
		TCHAR*	pszTmp;
		BYTE*	pcbData;

		if(pszData == NULL)
			return	false;

		*pszData = NULL;
		if(cchLength < sizeof(WINDOWPLACEMENT) * 2 + 1)
			return	false;

		if(IsValid() == false)
			return	false;

		pszTmp = pszData;
		pcbData = (BYTE*)&_sWindowPlacement;
		for(i = 0; i < sizeof(WINDOWPLACEMENT); i++)
		{
			ret = BinToHex(pcbData[i],pszTmp[0],pszTmp[1]);
			pszTmp += 2;
			if(ret)
				continue;

			*pszData = NULL;
			return	false;
		}
		*pszTmp = NULL;

		return	true;
	}




	/*!
	 * \brief
	 * 16進文字列の位置情報をクラス内に読み込む
	 * 
	 * \param pszData
	 * 読み込む位置情報
	 * 
	 * \retval	true	成功
	 * \retval	false	失敗
	 * 
	 */
	bool	SetFormatedData(LPCTSTR pszData)
	{
		int		i;
		bool	ret;
		LPCTSTR	pszTmp;
		BYTE*	pcbData;

		if(pszData == NULL)
			return	false;

		if(::_tcslen(pszData) != sizeof(WINDOWPLACEMENT) * 2)
			return	false;

		pszTmp = pszData;
		pcbData = (BYTE*)&_sWindowPlacement;
		for(i = 0; i < sizeof(WINDOWPLACEMENT); i++)
		{
			ret = HexToBin(pcbData[i],pszTmp[0],pszTmp[1]);
			pszTmp += 2;
			if(ret)
				continue;

			::ZeroMemory(&_sWindowPlacement,sizeof(WINDOWPLACEMENT));
			return	false;
		}

		if(IsValid())
			return	true;

		::ZeroMemory(&_sWindowPlacement,sizeof(WINDOWPLACEMENT));

		return	false;
	}





private:



	//
	//	ウインドウの重複部分の取得
	//
	//	APIと異なりNormalizeRectされていない場合も失敗しない(内部で正規化しているだけ)
	//
	BOOL	IntersectRect(LPRECT lprcDst,const RECT* lprcSrc1,const RECT* lprcSrc2)	const
	{
		if(lprcDst == NULL || lprcSrc1 == NULL || lprcSrc2 == NULL)
		{
			if(lprcDst)
			{
				lprcDst->left	= 0;
				lprcDst->right	= 0;
				lprcDst->top	= 0;
				lprcDst->bottom	= 0;
			}

			return	FALSE;
		}

		RECT	src1;
		RECT	src2;

		src1 = *lprcSrc1;
		src2 = *lprcSrc2;

		NormalizeRect(&src1);
		NormalizeRect(&src2);

		lprcDst->left	= (src1.left > src2.left) ? src1.left : src2.left;
		lprcDst->right	= (src1.right < src2.right) ? src1.right : src2.right;
		lprcDst->top	= (src1.top > src2.top) ? src1.top : src2.top;
		lprcDst->bottom	= (src1.bottom < src2.bottom) ? src1.bottom : src2.bottom;

		if(lprcDst->left >= lprcDst->right || lprcDst->top >= lprcDst->bottom)
		{
			lprcDst->left	= 0;
			lprcDst->right	= 0;
			lprcDst->top	= 0;
			lprcDst->bottom	= 0;

			return	FALSE;
		}

		return	TRUE;
	}


	//
	//	RECTの正規化
	//
	void	NormalizeRect(LPRECT lprc)	const
	{
		if(lprc == NULL)
			return;

		LONG	tmp;

		if(lprc->left > lprc->right)
		{
			tmp = lprc->left;
			lprc->left = lprc->right;
			lprc->right = tmp;
		}

		if(lprc->top > lprc->bottom)
		{
			tmp = lprc->top;
			lprc->top = lprc->bottom;
			lprc->bottom = tmp;
		}
	}



	//
	//	1バイトのバイナリを16進数文字の2文字にする
	//
	bool	BinToHex(BYTE cb,TCHAR& cUpper,TCHAR& cLower)	const
	{
		BYTE	tmp;

		tmp = cb >> 4;
		if(tmp < 10)
			cUpper = _T('0') + tmp;
		else
			cUpper = _T('A') + tmp - 10;

		tmp = cb & 0x0F;
		if(tmp < 10)
			cLower = _T('0') + tmp;
		else
			cLower = _T('A') + tmp - 10;

		return	true;
	}


	//
	//	16進数文字の2文字を1バイトのバイナリにする
	//
	bool	HexToBin(BYTE& cb,const TCHAR& cUpper,const TCHAR& cLower)	const
	{
		BYTE	tmp;

		if(cUpper >= _T('0') && cUpper <= _T('9'))
			tmp = (BYTE)cUpper - _T('0');
		else if(cUpper >= _T('A') && cUpper <= _T('F'))
			tmp = (BYTE)cUpper - _T('A') + 10;
		else if(cUpper >= _T('a') && cUpper <= _T('f'))
			tmp = (BYTE)cUpper - _T('a') + 10;
		else
			return	false;
		cb = tmp << 4;

		if(cLower >= _T('0') && cLower <= _T('9'))
			tmp = (BYTE)cLower - _T('0');
		else if(cLower >= _T('A') && cLower <= _T('F'))
			tmp = (BYTE)cLower - _T('A') + 10;
		else if(cLower >= _T('a') && cLower <= _T('f'))
			tmp = (BYTE)cLower - _T('a') + 10;
		else
			return	false;
		cb += tmp;

		return	true;
	}
};
プロジェクトファイルをダウンロード

キューを実現するクラス

CAtlListを使えば簡単にキューを作ることができる。

ここではテンプレートクラスとして作成したため、キュー化する変数の型は自由に設定できる。

■使用例
int型のキューを用意して「1 2 3 4 5」の順番でデータを追加。順番に取り出すと「1 2 3 4 5」という順でデータが取り出せる。
#include "DnpQue.h"

void	Test()
{
	int		n;
	bool	ret;
	CDnpQue<int>	cQue;

	cQue.AddData(1);		//データ追加
	cQue.AddData(2);
	cQue.AddData(3);
	cQue.AddData(4);
	cQue.AddData(5);

	do
	{
		ret = cQue.GetData(n);
		if(ret)
		{
			ATLTRACE("%d ",n);
		}
	}
	while(ret);
}


■以下が「DnpQue.h」の内容

依存環境:ATL
#pragma	once

#include "atlcoll.h"


/*!
 * \brief
 * キューテンプレートクラス
 * 
 * 先に入れたデータが先に出力される。
 * 
 * 以下のコードでは「1 2 3 4 5」が出力する。
 * \code
	int		n;
	bool	ret;
	CDnpQue<int>	cQue;

	cQue.AddData(1);		//データ追加
	cQue.AddData(2);
	cQue.AddData(3);
	cQue.AddData(4);
	cQue.AddData(5);

	do
	{
		ret = cQue.GetData(n);
		if(ret)
		{
			ATLTRACE("%d ",n);
		}
	}
	while(ret);
 * \endcode
 */
template<class VALUE>
class CDnpQue
{

public:
	CDnpQue()
	{
	}

	virtual ~CDnpQue()
	{
	}

	CAtlList<VALUE>	_listData;		//!< 保持しているキューデータ


	/*!
	 * \brief
	 * キューにデータ追加
	 * 
	 * \param	data
	 * 追加するデータ
	 * 
	 */
	void	AddData(VALUE data)
	{
		_listData.AddTail(data);
	};


	/*!
	 * \brief
	 * キューの先頭にデータ追加
	 * 
	 * \param	data
	 * 追加するデータ
	 * 
	 * \remarks
	 * 通常のキュー動作を無視して先頭にデータを挿入する。
	 * 挿入したデータは次回の CDnpQue<VALUE>::GetData(VALUE&) により取得される。
	 */
	void	AddPrimeData(VALUE data)
	{
		_listData.AddHead(data);
	};




	/*!
	 * \brief
	 * キューからデータ取得
	 * 
	 * \param[out]	data
	 * 取得したデータ
	 * 
	 * \retval	true	成功
	 * \retval	false	失敗
	 * 
	 */
	bool	GetData(VALUE& data)
	{
		if(_listData.GetCount() > 0)
			data = _listData.RemoveHead();
		else
		{
//			data = NULL;
			return	false;
		}
		return	true;
	};


	/*!
	 * \brief
	 * キューからデータ取得
	 * 
	 * \return
	 * 取得したデータ
	 * 
	 * \attention
	 * データがなかった場合に何が返るか不定になるので、この関数は使わずに
	 * CDnpQue<VALUE>::GetData(VALUE&) を利用するべき。
	 */
	VALUE	GetData(void)
	{
		VALUE	ret;
		GetData(ret);
		return	ret;
	};


	/*!
	 * \brief
	 * キューデータを見る(GetDataのように削除はしない)
	 * 
	 * \param[out]	data
	 * 取得したデータ
	 * 
	 * \retval	true	成功
	 * \retval	false	失敗
	 */
	bool	ReferData(VALUE& data)
	{
		if(_listData.GetCount() > 0)
			data = _listData.GetHead();
		else
		{
//			data = NULL;
			return	false;
		}
		return	true;
	}


	/*!
	 * \brief
	 * キューデータを見る(GetDataのように削除はしない)
	 * 
	 * \return
	 * 取得したデータ
	 * 
	 * \attention
	 * データがなかった場合に何が返るか不定になるので、この関数は使わずに
	 * CDnpQue<VALUE>::ReferData(VALUE&) を利用するべき。
	 */
	VALUE	ReferData(void)
	{
		VALUE	ret;
		ReferData(ret);
		return	ret;
	}



	/*!
	 * \brief
	 * 現在保持しているデータ数を返す
	 * 
	 * \return
	 * データ数
	 */
	int		GetSize(void)
	{
		return	(int)_listData.GetCount();
	};


	/*!
	 * \brief
	 * 現在保持している全データを削除
	 * 
	 */
	void	RemoveAll(void)
	{
		_listData.RemoveAll();
	};
};

プロジェクトファイルをダウンロード

スタックを実現するクラス

CAtlListを使えば簡単にスタックを作ることができる。

ここではテンプレートクラスとして作成したため、スタック化する変数の型は自由に設定できる。



ちなみにこのクラスは画像をスライドショー表示するユーティリティを作ったときに作ったクラス。表示した画像を順次スタックに入れておき、ユーザーが「戻る」ボタンを押したときにスタックから画像を取り出して表示する、、、というようにすることで履歴機能を作成した。また、24時間連続で表示するようなスライドショーなため、無限に画像をスタックに入れておくとメモリー消費量が大変なことになる。そのためスタックへ追加する最大数をコンストラクタで設定できるようにした。



■使用例
int型のスタックを用意して「1 2 3 4 5」の順番でデータを追加。順番に取り出すと「5 4 3 2 1」という順でデータが取り出せる。
#include "DnpStack.h"

void	Test()
{
	int		n;
	bool	ret;
	CDnpStack<int>	cStack;		//ここを「cStack(3)」にして最大数を3に設定すると出力は「5 4 3」になる

	cStack.AddData(1);		//データ追加、1,2,3,4,5の順に追加
	cStack.AddData(2);
	cStack.AddData(3);
	cStack.AddData(4);
	cStack.AddData(5);

	do
	{
		ret = cStack.GetData(n);
		if(ret)
		{
			ATLTRACE("%d ",n);
		}
	}
	while(ret);
	ATLTRACE("\n");
}




■以下が「DnpStack.h」の内容

依存環境:ATL
#pragma	once

#include "atlcoll.h"

/*!
 * \brief
 * スタックテンプレートクラス
 * 
 * データ格納順序を「A->B->C->D」とすると、
 * 「D->C->B->A」の順で取得できる。
 * 
 * 以下のコードでは「5 4 3 2 1」と出力される
 * \code
	int		n;
	bool	ret;
	CDnpStack<int>	cStack;		//ここを「cStack(3)」にして最大数を3に設定すると出力は「5 4 3」になる

	cStack.AddData(1);		//データ追加
	cStack.AddData(2);
	cStack.AddData(3);
	cStack.AddData(4);
	cStack.AddData(5);

	do
	{
		ret = cStack.GetData(n);
		if(ret)
		{
			ATLTRACE("%d ",n);
		}
	}
	while(ret);
	ATLTRACE("\n");
 * \endcode
*/
template<class VALUE>
class CDnpStack
{
	UINT		_nMaxSize;

public:
	CAtlList<VALUE>	_listData;			//!< スタック用内部データ


	/*!
	 * \brief
	 * コンストラクタ(最大保持サイズの設定可能)
	 * 
	 * \param[in]	nMaxSize
	 * データ最大保持サイズ(0なら無制限)
	 * 
	 * nMaxSizeに3を設定して、「A->B->C->D」の順で格納すると、
	 * 「D->C->B」の順で取得できる。「A」のデータは内部で削除される。
	 * 
	 */
	CDnpStack(int nMaxSize = 0)
	{
		_nMaxSize = nMaxSize;
	}

	virtual ~CDnpStack()
	{
	}


	/*!
	 * \brief
	 * スタックにデータ追加
	 * 
	 * \param[in]	data
	 * 追加するデータ
	 * 
	 */
	void	AddData(VALUE data)
	{
		_listData.AddTail(data);

		//古いデータ削除(ポインタだとメモリリークの可能性あり)
		if(_nMaxSize > 0 && _listData.GetCount() > _nMaxSize)
			_listData.RemoveHead();
	};


	/*!
	 * \brief
	 * スタックからデータを取り出す
	 * 
	 * \param[out]	data
	 * 取りだしたデータ
	 * 
	 * \retval	true	成功
	 * \retval	false	失敗
	 * 
	 */
	bool	GetData(VALUE& data)
	{
		if(_listData.GetCount() > 0)
			data = _listData.RemoveTail();
		else
		{
//			data = NULL;
			return	false;
		}
		return	true;
	};


	/*!
	 * \brief
	 * スタックからデータを取り出す
	 * 
	 * \return	取りだしたデータ
	 * 
	 * \attention
	 * データ取得に失敗した場合、どのようなデータが返るかわからないので
	 * 安全のために CDnpStack<VALUE>::GetData(VALUE&) を利用すべき
	 */
	VALUE	GetData(void)
	{
		VALUE	ret;
		GetData(ret);
		return	ret;
	};


	/*!
	 * \brief
	 * 先頭のデータを取得する(GetDataのように内部データの削除はしない)
	 * 
	 * \param[out]	data
	 * 取りだしたデータ
	 * 
	 * \retval	true	成功
	 * \retval	false	失敗
	 */
	bool	ReferData(VALUE& data)
	{
		if(_listData.GetCount() > 0)
			data = _listData.GetTail();
		else
		{
//			data = NULL;
			return	false;
		}
		return	true;
	}



	/*!
	 * \brief
	 * 先頭のデータを取得する(GetDataのように内部データの削除はしない)
	 * 
	 * \return	取りだしたデータ
	 * 
	 * \attention
	 * データ取得に失敗した場合、どのようなデータが返るかわからないので
	 * 安全のために CDnpStack<VALUE>::ReferData(VALUE&) を利用すべき
	 */
	VALUE	ReferData(void)
	{
		VALUE	ret;
		ReferData(ret);
		return	ret;
	}



	/*!
	 * \brief
	 * スタックに格納されているデータ数取得
	 * 
	 * \return
	 * スタックに格納されているデータ数
	 */
	int		GetSize(void)
	{
		return	(int)_listData.GetCount();
	};


	/*!
	 * \brief
	 * 内部データの全削除
	 */
	void	RemoveAll(void)
	{
		_listData.RemoveAll();
	};
};
プロジェクトファイルをダウンロード

2007年04月05日

Vistaのバグ(その6) Windows Updateがきちんと動いていない!

vista6_1.gif
左の図は私のPCでのWindows Updateの更新履歴を示したものだ。この図を見て何か気がつくことはないだろうか?

よく見ると「MSXML 4.0 SP2 セキュリティ更新プログラム(KB927978)」と「MSXML 4.0 SP2 セキュリティ更新プログラム(925672)」という項目が何度もインストールされている。本来であれば1度だけインストールすればいいものを何度もインストールしているのだ。

vista6_2.gif
また、インストールするたびにCドライブのルートに新しいフォルダを作成して「msxml4-KB927978-enu.log」というファイルなどを作成する。この図の場合は一度インストールした後の状態なのでフォルダが2つ作られているだけだが、10回インストールした後であれば20個ものフォルダとファイルが作られることになる。

vista6_3.gif
これらの更新プログラムが何度もインストールされないようにするには、Windows Updateの画面から「利用可能な更新プログラムを表示します」をクリックする。

vista6_4.gif
そしてチェックが入っている「MSXML 4.0 SP2 セキュリティ更新プログラム(KB927978)」という項目の上で右クリックをして現れたメニューから「更新プログラムの非表示」を選択する。

vista6_5.gif
同様に「MSXML 4.0 SP2 セキュリティ更新プログラム(925672)」に対しても同じ操作を行う。

vista6_6.gif
これで次回からWindows Updateを実行しても同じ更新プログラムがインストールされなくなる



...とは言うものの、この対処方法では、また同じように何度もインストールされる更新プログラムが現れたときには同じように非表示に設定する必要がある。手間のかかる話しだ。


2007年04月04日

Vistaのバグ(その5) 削除したはずのファイルが消えていない!

vista5_1.gif
このバグは非常にたちが悪いものの1つだ。
このバグはファイルをドライブからドライブへ移動するときなどに起きることがある。通常に移動が終わったように見えてもエクスプローラーで表示を更新すると移動したはずのファイルがまだ残っているというものだ。

このバグは1GB以上あるような大き目のファイルを扱ったときに起きる。消えているべきファイルは削除しようとしてもこの図のように「対象のフォルダへのアクセスは拒否されました。この操作を実行するアクセス許可が必要です。」と言われ、削除できない。ファイルとして扱われるべきものがフォルダとして扱われているのだろうか?

vista5_2.gif
図に示した例はmpegの映像ファイルでの例だ。このバグが生じたファイルは再生しようとしても「ファイルにアクセスできません。」と言われて再生できない。

このバグの困った点は、"大きいファイルで起こる"ことと"削除できない"という点だ。OS上はファイル自体は存在しているので、HDDの容量は取られたままになる。最近はHDDの容量が大きいため、問題ないと考えがちだ。しかし最近はハイビジョンなどデータ量の多い映像も多いため馬鹿にならない。ちなみにこの文章を書いている今、私のPCにはこのバグにより削除できていないファイルが15個残っている。

このように削除できないファイルを削除したい場合は、一度Windows Vistaをログオフしてログオンしなおすか、再起動するしかない。サーバー用途など長時間起動したままにするシステムでは致命的だ。


2007年03月30日

Office 2007はライセンス認証しないと閲覧モードに移ってしまう


office2007_01.gif
Microsoft Office 2007はインストールにプロダクトキーが必要ない。ただしWordやExcelの起動時に「25文字のプロダクトキーを入力してください。」という旨の画面が表示される。

office2007_02.gif
「×」ボタンを押してウインドウを閉じようとすると「セットアップをキャンセルしてもよろしいですか?」と聞かれるので「はい」ボタンを押す。

office2007_03.gif
すると「Microsoft Office Professional 2007の構成が中止されました。」という画面が表示され...

office2007_04.gif
問題なくWordやExcelなどの全機能を利用できていた(既に過去形)。このようにしてプロダクトキーを指定しない状態でOfficeを使っていても、起動時に入力用の画面が表示される以外はまったく問題なく利用できていた。バージョン情報の画面を見ても「残り39回利用できます」のようなメッセージが表示されているわけでもなかった。
しかし、今日起動してみると図のようにメニュー項目が灰色になってしまい。まったくと言っていいほど利用できなくなった。

office2007_05.gif
Excelだけでなく、Wordでも同様だ。ファイルを開いて閲覧することはできるが、入力や編集が一切できない。Excelは検索機能が動いたが、Wordでは検索機能も動かない。


何も表示されていなかったのだが、おそらくプロダクトキーを入力しないで利用している場合は50回の利用制限があったのだろう。つまり51回目の起動からこのように機能が利用できなくなるのだろう。




office2007_06.gif
当然ことながらこのままでは使い物にならないのでプロダクトキーを入力した。

office2007_07.gif
すると「インストールの種類を選択してください」と言われた。しかし種類を選択するも何も、「今すぐインストール」と「ユーザー設定」しかない。ここではとりあえず「ユーザー設定を」を選択した。

office2007_08.gif
すると氏名などの編集画面が表示された。ユーザー設定項目はこれだけのようだ。

office2007_09.gif
「今すぐインストール」ボタンを押すと「Microsoft Office Professional 2007を構成しています...」と10秒程度待たされた。

office2007_10.gif
そして「Microsoft Office Professional 2007の構成が正常に完了しました。」という表示。

office2007_11.gif
しかし、ここまで進んでもまだOfficeの機能は灰色になっていて利用できなかった。




office2007_12.gif
一度Excelを終了して、再度起動すると今度は「Microsoft Office ライセンス認証ウィザード」が開いた。インターネット経由でのライセンス認証(アクティベーション)は気分的に嫌なのでここでは電話経由を選択した。

ちなみにWindows Vistaの場合はインターネットに接続していると強制的にネット経由でアクティベーションを行ってしまうということがあったので、事前にネットワーク接続を切った状態でプロダクトキーの入力などをしていた。その可能性は低いだろうがもしかすると接続時は強制的にオンライン認証されるのかもしれない。

office2007_13.gif
そして表示されているフリーダイヤルに電話をして確認コードを入力。電話のアナウンスでは「ステップ4のA~Gの欄に...」のように言っていたが、確認コードの入力はステップ3のようだ。

office2007_14.gif
これで無事にライセンス認証が終了した。

office2007_15.gif
これで機能が灰色化することもなく正常に利用できるようになった。

DLLなどからエクスポートされている関数を一覧する

test129.gif
DLLからエクスポートされている関数を調べるにはDLLをPEファイルの構造を元にバイナリレベルで解析して調べる方法とAPIを利用して情報を取得する方法とがある。
ここではデバッグ用に用意されているAPIのImageDirectoryEntryToDataを利用して取得した。

依存環境:ATL
#include "atlstr.h"

#include "Dbghelp.h"
#pragma	comment(lib,"Dbghelp.lib")


bool	ShowExportedFunction(LPCTSTR pszDLLPath)
{
	HMODULE	hModule;
	ULONG	nSize;
	DWORD	i;
	DWORD	j;
	char**	ppszFunctionName;
	WORD*	pwFunctionOrdinal;
	IMAGE_EXPORT_DIRECTORY*	pImageExportDir;

	CAtlString	strMessage;

	hModule = ::LoadLibrary(pszDLLPath);
	if(hModule == NULL)
		return	false;

	pImageExportDir = (IMAGE_EXPORT_DIRECTORY*)::ImageDirectoryEntryToData((PVOID)hModule,TRUE,IMAGE_DIRECTORY_ENTRY_EXPORT,&nSize);
	if(pImageExportDir == NULL)
	{
		::FreeLibrary(hModule);
		return	false;
	}

	strMessage.AppendFormat(_T("%sがエクスポートしている関数一覧\n\n"),pszDLLPath);


	//pImageExportDir内のアドレスはhModuleからの相対的なアドレス
	ppszFunctionName	= (char**)(pImageExportDir->AddressOfNames + (ULONGLONG)hModule);
	pwFunctionOrdinal	= (WORD*)(pImageExportDir->AddressOfNameOrdinals + (ULONGLONG)hModule);

	for(i = 0; i < pImageExportDir->NumberOfFunctions; i++)
	{
		strMessage.AppendFormat(_T("序数:%4d(0x%04x)"),pImageExportDir->Base + i,pImageExportDir->Base + i);

		for(j = 0; j < pImageExportDir->NumberOfNames; j++)
		{
			if(pwFunctionOrdinal[j] != i)		//pwFunctionOrdinal[j]は序数ではなくインデックス
				continue;

			//hModuleからの相対アドレスで関数名が格納されている
			strMessage.AppendFormat(_T(" 関数名:%s"),(char*)(ppszFunctionName[j] + (ULONGLONG)hModule));
			break;
		}

		strMessage += _T("\n");
	}

	::FreeLibrary(hModule);

	::MessageBox(NULL,strMessage,_T(""),MB_OK);

	return	true;
}


bool	Test()
{
	return	ShowExportedFunction(_T("hotplug.dll"));
}

プロジェクトファイルをダウンロード

2007年03月29日

簡単に処理時間を計測する

test128.gif
ベンチマークソフトを作りたい!と本格的に思わなくとも処理時間を計測したい場面は多々ある。簡易的に測定したのならGetTickCountを利用すると簡単に計測できる。

GetTickCountはPCを起動してからの経過時間をミリ秒単位で返すための関数だ。この関数を計測したい処理の前後に実行して、取得した値の差を取れば処理に要した時間となる。

ただしGetTickCountを利用する上で注意しておきたい点がある。

まず、この関数で返される値は「まれにゼロに戻る」という点だ。具体的には49.7日周期でゼロに戻る。そのため49.7日を超える処理時間は確実に計測できないし、処理時間が仮に数秒の場合でもちょうどゼロに戻るタイミングに当たった場合は処理時間が負の値となりきちんと計測できない。

また、WindowsはマルチタスクOSなため、バックグラウンド処理が重たいと計測時間が長くなるという点だ。これはGetTickCountの欠点ではないが場合によっては問題となり得る。


何はともあれ、「GetTickCountの値がゼロに戻ることがある」という点だけは絶対に頭に入れておかないとダメだ。

#include "atlstr.h"

bool	Test()
{
	DWORD		dwStart;
	ULONGLONG	dwEnd;

	dwStart = ::GetTickCount();

	//処理時間を計測したい処理
	{
		int		i;
		int		j;

		for(i = 0; i < 1000; i++)
		{
			for(j = 0; j < 1000; j++)
			{
				char	pszBuff[256];

				::sprintf_s(pszBuff,256 * sizeof(char),"%d %d",i,j);
			}
		}
	}

	dwEnd = ::GetTickCount();

	//GetTickCountは49.7日に1回ゼロに戻る。
	//計測中にゼロに戻っても正常な値になるようにゼロに戻った場合は桁上がりさせる
	//※注意:この桁上がり処理をした場合も49.7日を超える時間は計測できない
	if(dwStart > dwEnd)
		dwEnd += 0x100000000;		//桁上がり

	CAtlString	strMessage;

	strMessage.Format(_T("処理にかかった時間は %d ミリ秒です"),dwEnd - dwStart);
	::MessageBox(NULL,strMessage,_T(""),MB_OK);

	return	true;
}
プロジェクトファイルをダウンロード

たくさんある文字列比較用関数から必要なものを見つける

C言語をやっていた頃はstrcmp(とstrncmp)だけ知っていれば文字列比較には十分だった。

しかしWindowsプログラミングになるとユニコードやらTCHAR型が登場したおかげで非常に複雑になっている。さらに同じ挙動を示す関数が名前違いで複数用意されているものだから混沌としている。

しかし関数の名前の法則(?)を押さえておけば何となく使うべき関数をチョイスできる。

・「w」がついている場合はユニコード文字列の比較
・「t」が付いている場合はTCHAR型文字列の比較
・「str」が付いている場合は通常の文字列(SHIFT-JISやANSI文字列)の比較

・「n」が付いている場合は文字数を指定しての比較
・「i」が付いている場合は大文字と小文字を区別しない比較

という命名慣習だ。

例えば「strcmp」は「str」が含まれるから通常文字列用で、「i」や「n」が含まれないから大文字と小文字を区別して文字数の指定ができない比較用となるし、「stricmp」は大文字小文字を同一視する比較用、「strncmp」は文字数を指定しての比較用となる。


実際のプログラミングでは...

通常の文字列に対応したstrcmp、strncmp(文字数指定)、stricmp(大文字小文字同一視)、_strnicmp(文字数指定かつ大文字小文字同一視)。

TCHAR型文字列に対応した_tcscmp、_tcsncmp(文字数指定)、_tcsicmp(大文字小文字同一視)、_tcsnicmp(文字数指定かつ大文字小文字同一視)。

の合計8個程度を覚えておけば問題ないだろう。


MSDNライブラリから適当にピックアップしただけで以下のものが見つかった。きちんと探せばまだまだある。いったいいくつあるのだろう?

char wchar_t TCHAR 比較長 大小同一 ロケール 対応OS 補足
_tcscmp
_tccmp
strcmp
wcscmp
_mbscmp MBCS
_tcsncmp
_tcsnccmp
strncmp
wcsncmp
_mbsnbcmp  MBCS
_mbsncmp MBCS
_tcsncmp_l MBCS
_mbsnbcml MBCS
_mbsncmp_l MBCS
_tcsicmp
_stricmp
_wcsicmp
_mbsicmp MBCS
_tcsncicmp
_strnicmp
_wcsnicmp
_mbsnicmp MBCS
_tcsnicmp
_strnicmp
_wcsnicmp
_mbsnbicmp MBCS
_tcsncicmp_l
_strnicmp_l
_wcsnicmp_l
_mbsnicmp_l MBCS
StrCmp
StrCmpA
StrCmpW
StrCmpN
StrCmpNA
StrCmpNW
StrCmpI
StrCmpIA
StrCmpIW
StrCmpNI
StrCmpNIA
StrCmpNIW
StrNCmp
StrNCmpI
StrCmpC Windows 2000以降
StrCmpCA Windows 2000以降
StrCmpCW Windows 2000以降
StrCmpNC Windows 2000以降
StrCmpNCA Windows 2000以降
StrCmpNCW Windows 2000以降
StrCmpIC Windows 2000以降
StrCmpICA Windows 2000以降
StrCmpICW Windows 2000以降
StrCmpNIC Windows 2000以降
StrCmpNICA Windows 2000以降
StrCmpNICW Windows 2000以降
StrCmpLogicalW Windows XP以降イコウ 含まれる数値を比較に考慮
strcmpi (現在は利用しない)
CompareString 可能 可能 戻り値に注意!等しいときはCSTR_EQUAL(=2)を返す
CompareStringA 可能 可能 戻り値に注意!等しいときはCSTR_EQUAL(=2)を返す
CompareStringW 可能 可能 戻り値に注意!等しいときはCSTR_EQUAL(=2)を返す
CompareStringEx 可能 可能 Windows Vista以降 戻り値に注意!等しいときはCSTR_EQUAL(=2)を返す
CompareStringOrdinal 可能 可能 Windows Vista以降 戻り値に注意!等しいときはCSTR_EQUAL(=2)を返す
CompareStringWrapW 可能 可能 Windows 2000以降 戻り値に注意!等しいときはCSTR_EQUAL(=2)を返す
strcoll
wcscoll
_mbscoll MBCS
_tcsncoll
_strncoll
_wcsncoll
_mbsnbcoll MBCS
 
_tcsncoll_l
_strncoll
_wcsncoll
_mbsncoll MBCS
_strncoll_l
_wcsncoll_l
_mbsncoll_l  MBCS
_mbsnbcoll_l  MBCS
_tcsnicoll
_strnicoll
_wcsnicoll
_mbsnbicoll MBCS
_tcsnicoll_l
_strnicoll_l
_wcsnicoll_l
_mbsnbicoll_l MBCS
 
_tcsncicoll
_strnicoll
_wcsnicoll
_mbsnbicoll MBCS
_tcsnicoll
_strnicoll
_wcsnicoll
_mbsnbicoll MBCS
_tcsnicoll_l
_strnicoll_l
_wcsnicoll_l
_mbsnbicoll_l MBCS
前の10件 4  5  6  7  8  9  10  11  12  13  14