« 2004年12月 | トップページ | 2005年2月 »

2005年1月31日 (月)

Nethack::テレポート能力でつるはしを持ったまま店に入って商品が未払いのまま穴を掘って下に下りようとすると

「注意してください。穴から落ちますよ。」と店主に言われる。
その後落ちる寸前に店主が背負い袋だけつかんで自分は落ちる。残るのはつるはしと装備品のみ。
要するに盗みはできないらしい…。

| | コメント (0) | トラックバック (0)

2005年1月29日 (土)

Dynabook T5/X16PMEについて

いいところ

画面がSXGA+(1400*1050)なので広い。
ノートパソコンのスピーカーにしてはいい音がする。

悪いところ。

もろい。ちょっと本体を捻ると結構な確率でハードエラーで青画面になったりする。
でかい。とことんでかい。
CD-ROMドライブが打たれ弱い。(どのノートでもそうだけど…。)

4年くらい使うとCPUファンのグリスが切れます。油とかさすと吉。

| | コメント (0) | トラックバック (0)

専門語辞書参照サービス

英単語を調べるのに便利。
Sleipnirの検索に登録するとよいかと。

http://wwwd.nova.co.jp/webdic/webdicc.cgi?&lang=X&dic=BASE&dic=MATH&dic=MEDI&dic=AERO&dic=BUSI&dic=COMP&dic=META&dic=ARCH&dic=OCEA&dic=TRAD&dic=ELEC &dic=CHEM&dic=PLAN&dic=DEFE&dic=FINA&dic=MECH&dic=BIO&dic=ENER&dic=ECOL&dic=LAW&dic=ENTE&magazine=yes

うえのURLにさらに
&adrs=emailアドレス
&word
=検索文字列
を追加して使います。

| | コメント (0) | トラックバック (0)

2005年1月28日 (金)

元に戻す

元に戻すを実装しようとして四苦八苦。
元に戻すはごみ箱からの復元だけでなく、コピーや移動の処理も取り消せる。
ということは結果的にIDragDropやMoveFileExを利用する方法は使えず、IContextMenuを利用して元に戻すを実行するしかないと考えて組んでみたものの。
以下のようなコードを実行したけれど元に戻すのメニューは取得できなかった。


int __stdcall	WinMain( HINSTANCE, HINSTANCE, LPSTR, int )
{
	IMalloc*	pMalloc;
	TB_ENFORCE( NotEqualFail( S_OK ), CoGetMalloc( 1, &pMalloc ) );
	LPITEMIDLIST	pIdList;
	TB_ENFORCE( NotEqualFail( S_OK ), SHGetFolderLocation( NULL, CSIDL_DESKTOP, NULL, NULL, &pIdList ) );

	IShellFolder*	pDesktop;
	TB_ENFORCE( NotEqualFail( NOERROR ), SHGetDesktopFolder( &pDesktop ) );
	// IContextMenuを取得します。
	// m_pDFolderはデスクトップフォルダを示すIShellFolderです。
	IContextMenu*	pContextMenu;
	TB_ENFORCE( NotEqualFail( S_OK ), pDesktop->GetUIObjectOf( NULL, 1, (LPCITEMIDLIST*)(&pIdList), IID_IContextMenu, 0, reinterpret_cast< void ** >( &pContextMenu ) ) );

	HMENU	hMenu = TB_ENFORCE( API< HMENU >( NULL ), ::CreatePopupMenu() );

	// メニューを取得します。
	HRESULT	re = pContextMenu->QueryContextMenu( hMenu, 0, 1, 0xffff, CMF_NORMAL );
	Uint	itemIndex = UINT_MAX;
	//
	TB_LOOP_1( GetMenuItemCount( hMenu ) )
	{
		StringReceiver	receiver;
		if( 0 != GetMenuString( hMenu, index, receiver.Lock( 255 ), 254, MF_BYPOSITION ) )
		{
			//文字列が取得できた。
			if( std::string::npos != receiver.UnLock().find("元に戻す") )
			{
				itemIndex = index;
				break;
			}
		}
	}

	if( UINT_MAX == itemIndex )
	{
		//「元に戻す」がない==削除や移動をしていなくて元に戻すファイルがない。
		return 0L;
	}

	// コマンドを実行します。
	CMINVOKECOMMANDINFO	invokeInfo = { sizeof( CMINVOKECOMMANDINFO ) };
	invokeInfo.fMask = 0;
	invokeInfo.hwnd = NULL;
	invokeInfo.lpVerb = MAKEINTRESOURCE( itemIndex );//MAKEINTRESOURCE( iCommand - 1 );
	invokeInfo.lpParameters = NULL;
	invokeInfo.lpDirectory = NULL;
	invokeInfo.nShow = SW_SHOWNORMAL;
	invokeInfo.dwHotKey = 0;
	invokeInfo.hIcon = NULL;

	TB_ENFORCE( NotEqualFail( NOERROR ), pContextMenu->InvokeCommand( &invokeInfo ) );

	window.Wait();
	pContextMenu->Release();
	pDesktop->Release();
	pMalloc->Free( pIdList );
	pMalloc->Release();

	return 0L;
}


エクスプローラー(&X)とかは取れるんだけどなぁ。
フォルダの拡張メニューはIShellFolderからGetUIObjectOfしたのではだめなのかもしれない。


上の記事の後に前進があったのでメモ

・CoInitializeとCoUninitializeを忘れてた。追加したら突如としてQueryContextMenuが以前より多い数のメニューを登録してきた。
謎。エラーになってるなら最初からエラー終了してほしい。

・フォルダのコンテキストメニューは無事取得成功。
したのはいいんだけど冷静になって考えると「元に戻す」があるのはフォルダの右クリックメニューではなくて、エクスプローラーの右ペインの何も無いところを右クリックしたときに出てくるメニュー。
これをとるためにIShellViewをとって、それのGetItemObjectを使った。
無事コンテキストメニューが取れた…のだが。

ない。狙ったように元に戻すの項目だけメニューに追加されてこない。
QueryContextMenuのパラメーターをいろいろといじるものの二進も三進も行かず。
IContextMenuでInvokeCommandすればいいなんて言い出したのは誰なんだ…。教えてほしい(^^;
とりあえずいったんあきらめた次第。

・IShellViewのメソッドをにらむにTranslateAcceleratorというのがある。これでCTRL+Zを送ってやろうかと思ったものの失敗。
MSG構造体を偽造するのは難しい。

int __stdcall	WinMain( HINSTANCE, HINSTANCE, LPSTR, int )
{
	CoInitialize( NULL );

	IMalloc*	pMalloc;
	TB_ENFORCE( NotEqualFail( S_OK ), CoGetMalloc( 1, &pMalloc ) );
	LPITEMIDLIST	pIdList;
	TB_ENFORCE( NotEqualFail( S_OK ), SHGetFolderLocation( NULL, CSIDL_DESKTOPDIRECTORY, NULL, NULL, &pIdList ) );

	IShellFolder*	pDesktop;
	TB_ENFORCE( NotEqualFail( NOERROR ), SHGetDesktopFolder( &pDesktop ) );

	// IContextMenuを取得します。
	IContextMenu*	pContextMenu;
	IShellView*		pView;
	TB_ENFORCE( NotEqualFail( S_OK ), pDesktop->CreateViewObject( NULL, IID_IShellView, reinterpret_cast< void ** >( &pView ) ) );
	TB_ENFORCE( NotEqualFail( S_OK ), pView->GetItemObject( SVGIO_BACKGROUND, IID_IContextMenu, reinterpret_cast< void ** >( &pContextMenu ) ) );

	// IContextMenu2へQueryInterface
	IContextMenu3*	pContextMenu2;
	TB_ENFORCE( NotEqualFail( S_OK ), pContextMenu->QueryInterface( IID_IContextMenu3, reinterpret_cast< void ** >( &pContextMenu2 ) ) );
	pContextMenu->Release();

	HMENU	hMenu = TB_ENFORCE( API< HMENU >( NULL ), ::CreatePopupMenu() );

	// メニューを取得します。
	HRESULT	re = pContextMenu2->QueryContextMenu( hMenu, 0, 1, 0xffff, CMF_NODEFAULT );
	Uint	itemIndex = UINT_MAX;
	//
	TB_LOOP_1( GetMenuItemCount( hMenu ) )
	{
		StringReceiver	receiver;
		if( 0 != GetMenuString( hMenu, index, receiver.Lock( 255 ), 254, MF_BYPOSITION ) )
		{
			//文字列が取得できた。
			if( std::string::npos != receiver.UnLock().find("元に戻す") )
			{
				itemIndex = index;
				break;
			}
		}
	}

	if( UINT_MAX == itemIndex )
	{
		//「元に戻す」がない==削除や移動をしていなくて元に戻すファイルがない。
		return 0L;
	}

	// コマンドを実行します。
	CMINVOKECOMMANDINFO	invokeInfo = { sizeof( CMINVOKECOMMANDINFO ) };
	invokeInfo.fMask = 0;
	invokeInfo.hwnd = NULL;
	invokeInfo.lpVerb = MAKEINTRESOURCE( 7 );//itemIndex );//MAKEINTRESOURCE( iCommand - 1 );
	invokeInfo.lpParameters = NULL;
	invokeInfo.lpDirectory = NULL;
	invokeInfo.nShow = SW_SHOWNORMAL;
	invokeInfo.dwHotKey = 0;
	invokeInfo.hIcon = NULL;

	TB_ENFORCE( NotEqualFail( NOERROR ), pContextMenu2->InvokeCommand( &invokeInfo ) );

	pContextMenu2->Release();
	pDesktop->Release();
	pMalloc->Free( pIdList );
	pMalloc->Release();

	CoUninitialize();

	return 0L;
}

| | コメント (0)

2005年1月27日 (木)

ごみ箱から指定のファイルを「元に戻す」方法

ごみ箱にファイルを「元に戻せるように」捨てるのは

void	FileSystem::Delete( const FilePath& path )
{
	CountDownTimer	timer( 2000 );
	while( timer() )
	{
		//ゴミ箱に捨てる。
		MultiString	deletefilename;

		SHFILEOPSTRUCT shFileOp;
		shFileOp.hwnd = NULL;
		shFileOp.wFunc = FO_DELETE;
		shFileOp.pFrom = deletefilename.Make( vector< string >( 1, path() ) );
		shFileOp.pTo = NULL;
		shFileOp.fFlags = FOF_ALLOWUNDO|FOF_NOCONFIRMATION|FOF_NOERRORUI;
		shFileOp.fAnyOperationsAborted = TRUE;
		if( SHFileOperation( &shFileOp ) == 0 )
			return;
		//直前まで書き込みが行われていた場合など、エラーになるときがある。2秒まで再試行
	}
	TB_ENFORCE( TrueFail(), ("FileSystem::Deleteに失敗:" + path()).c_str() );
}

まぁなんかこんな感じでSHFileOperationとFOF_ALLOWUNDOを使えばよい、というのはググればすぐに出てくるが、
肝心の「元に戻す」方法はほとんど資料がない。どうも話によるとごみ箱のコンテキストメニューをIFolderとIContextMenuを使って取得して
InvorkCommandで元に戻すを実行すればできる…らしいという話はある。

が、それも直前の操作を元に戻すものでしかない。ごみ箱に捨てた○○というファイルを元に戻すにはどうすればいいのか。

まずごみ箱はたいていC:\RECYCLERの下にある。ちなみにNT系を前提とする。しかも今回紹介する方法は2k以降でしか使えない。
C:\RECYCLERの下に、SID(セキュリティ識別子)と呼ばれるたいてい"S-1-5-21"といった文字列から始まるIDのフォルダが切られて、その中にごみ箱のファイルがある。ちなみに"Dc"+番号.拡張子という名前にリネームされている。

元のファイル名の情報はどこにあるのかというと、同じフォルダに"INFO2"という隠しファイルがあってそれがデータベースとなっている。
INFO2のフォーマットは
ヘッダ(謎)+(元のファイル名(ASCII)+NULL領域+Dcの後に来る番号(2バイト)+NULL領域+マルチバイトに変換されたファイル名+NULL領域)*
というもののようだ。元に戻した場合ファイル名のレコードは削除されないが、元のファイル名の最初の一文字が削られることで無効フラグとなっているらしい。

と、いうわけでかなり怪しげなものの、以下の方法でごみ箱から指定のファイルを元に戻すことができる。
・ごみ箱のINFO2ファイルを開く。
・元の位置のパスでINFO2を検索。
・見つかったらそのファイル名の後ろのNULL領域の次に来る2バイトがごみ箱内のファイルの番号。
・ごみ箱のファイルを元の位置に移動する。

INFO2ファイルのパスはIShellFolderを利用することで取得できる。
データベースファイルはどの環境でも"INFO2"というファイル名なのか、中のフォーマットは本当に正しいのか、ごみ箱のリネーム後のファイル名のヘッダはどこでも"Dc"なのか、などこの辺は確かな情報はない模様。

とりあえずうちで動作する「元に戻す」関数。

//! ごみ箱から指定のファイルを検索して復元
void	FileSystem::Restore( const FilePath& restoreFile )
{
	//ごみ箱内のファイルをリストアップ
	std::vector< FilePath >	paths;
	{
		//ShellAPIを使う。
		IMalloc*	pMalloc;
		TB_ENFORCE( NotEqualFail( S_OK ), CoGetMalloc( 1, &pMalloc ) );
		LPITEMIDLIST	pIdList;
		TB_ENFORCE( NotEqualFail( S_OK ), SHGetFolderLocation( NULL/*hwnd*/, CSIDL_BITBUCKET, NULL, NULL, &pIdList ) );
		IShellFolder*	pDesktop;
		TB_ENFORCE( NotEqualFail( NOERROR ), SHGetDesktopFolder( &pDesktop ) );
		IShellFolder*	pTrash;
		TB_ENFORCE( NotEqualFail( S_OK ), pDesktop->BindToObject( pIdList, NULL, IID_IShellFolder, reinterpret_cast< void ** >( &pTrash ) ) );
		LPENUMIDLIST	pEnumIDList;
		TB_ENFORCE( NotEqualFail( NOERROR ), pTrash->EnumObjects( NULL/*hwnd*/, SHCONTF_NONFOLDERS |SHCONTF_INCLUDEHIDDEN | SHCONTF_FOLDERS, &pEnumIDList ) );
		LPITEMIDLIST	pFileIDList;
		ULONG			result;
		while( pEnumIDList->Next( 1, &pFileIDList, &result ) == NOERROR )
		{
			// ファイルパスの取得。
			STRRET		stFileName;
			TB_ENFORCE( NotEqualFail( NOERROR ), pTrash->GetDisplayNameOf( pFileIDList, SHGDN_FORPARSING, &stFileName ) );
			paths.push_back( String::ToAscii( stFileName.pOleStr ) );
			pMalloc->Free( stFileName.pOleStr );
			pMalloc->Free( pFileIDList );
		}
		pMalloc->Free( pEnumIDList );
		pTrash->Release();
		pDesktop->Release();
		pMalloc->Free( pIdList );
		pMalloc->Release();
	}
	//ごみ箱が空なら帰る。
	TB_ENFORCE( TrueFail(), paths.empty() );
	//ごみ箱の元のファイル名データベースを開く。
	FilePath	info2Path( paths[0].GetFolder(), "INFO2" );
	TB_ENFORCE( FalseFail(), FileSystem::IsExist( info2Path ) );
	//INFO2を開く。
	BinaryFile	info2( info2Path );
	//検索
	std::string	restoreString = restoreFile();
	char*	pInfo2 = reinterpret_cast< char* >( info2() );
	char*	pInfo2End = pInfo2 + info2.GetSize();
	char*	pFilePosition = std::search( pInfo2, pInfo2End, restoreString.begin(), restoreString.end() );
	TB_ENFORCE( TrueFail(), pFilePosition == pInfo2End );
	//見つかった。パスの後にNULLが連続していて、その後にファイルの番号が来る。
	pFilePosition += restoreString.length();
	while( pFilePosition < pInfo2End && '\0' == pFilePosition[0] )
		++pFilePosition;
	//ごみ箱内からIDを検索する
	std::string	id = String( *reinterpret_cast< Uint16* >( pFilePosition ) )();
	FilePath	source;
	TB_FOR_EACH( std::vector< FilePath >, paths )
	{

		std::string	basename = it->GetBaseName();
		std::string::size_type	index = basename.find( id );
		if( std::string::npos == index )
			continue;
		//Dc198.lzhで、9番がヒットするのを防ぐ。
		//見つけた位置にIDの長さを加えたらベース名の長さになること。
		if( basename.length() != index + id.length() )
			continue;
		//ありえるのかわからないけど、ファイル名部分がIDと一緒ならいいとする。
		//198.lzhみたいな。
		if( basename.length() != id.length() )
		{
			//IDが見つかった位置はベース名の最後。
			//サイズが違うのならindexは0以上。
			//index-1が数字だったらやり直す。
			//98のときに198.lzhみたいな。
			if( '0' <= basename[index-1] && basename[index-1] <= '9' )
				continue;
		}
		//ここまでくれば見つかったことになる。
		source = *it;
		break;
	}
	//元に戻す
	FileSystem::Move( source, restoreFile );
}

| | コメント (0) | トラックバック (0)

2005年1月26日 (水)

JNethack::倉庫番::1F

 

続きを読む "JNethack::倉庫番::1F"

| | コメント (0) | トラックバック (0)

2005年1月24日 (月)

ディスクの管理を一発で起動する。

AirG Development (Windowsで比較的に使用頻度の高いコマンド)
を参照のこと。

| | コメント (0) | トラックバック (0)

2005年1月14日 (金)

ファイルサイズの取得

エクスプローラでファイルのプロパティを見ると「サイズ」という項目と「ディスク上のサイズ」という項目がある。
この「ディスク上のサイズ」をとるのは工夫がいる。
以下のような感じ。SetupAPIをつかう。
{
	HDSKSPC	hDiskSpace = SetupCreateDiskSpaceList( NULL, NULL, SPDSL_IGNORE_DISK );
	SetupAddToDiskSpaceList( hDiskSpace, path().c_str(), GetSize( path ), FILEOP_COPY, NULL, NULL );
	LONGLONG	size;
	SetupQuerySpaceRequiredOnDrive( hDiskSpace, path.GetDriveLetter().c_str(), &size, NULL, NULL );
	SetupDestroyDiskSpaceList( hDiskSpace );
}
「サイズ」はGetFileSizeで取得できるように思うが、実際にはこの関数はそれほど性能がよくない。
GetFileSizeを使うにはハンドルが必要でファイルをCreateFileで開かなければならない。
が、たまに非共有モードですでにファイルが開かれていたりするとCreateFileは失敗する。

んじゃぁどうするのかというとFindFile系を使う。以下のような感じ。

{
	const char*	pFolder = "c:\\*";
	const char*	pFileName = "test.txt";
	const char*	pFullPath = "c:\\test.txt";

	WIN32_FIND_DATA findData;
	HANDLE	hFind = FindFirstFile(pFullPath,&findData);
	if( hFind != INVALID_HANDLE_VALUE )
	{
		FindClose( hFind );
		return findData.nFileSizeHigh*MAXDWORD+findData.nFileSizeLow;
	}
	else
	{
		//FindFirstFileではだめなのに、FindNextFileだと見つかる場合がある。ものすっごく謎。
		//アクセス権がないファイルの場合などに起こるみたいな気がする。
		hFind = FindFirstFile( pFolder, &findData );

		for( BOOL result = TRUE; result; result = FindNextFile( hFind, &findData ) )
		{
			if( lstrcmp( pFileName, findData.cFileName ) == 0 )
			{
				FindClose( hFind );
				return findData.nFileSizeHigh*MAXDWORD+findData.nFileSizeLow;
			}
		}
		//なければエラー
		FindClose( hFind );
		assert( !"ファイルが見つかりませんでした。" );
		return 0;
	}
}
ちゃんとやろうとすればするほどコードが長くなるというお話。

| | コメント (0)

2005年1月12日 (水)

クラスのデストラクタについて

基本的にクラスのデストラクタは仮想にしてthrow()をつける。

class A
{
public:
	virtual ~A() throw(){}
};

仮想にするのは派生クラスのデストラクタを確実に呼ぶため。

class	A{ public: ~A(){ assert(0); } };
class	B : public A{};

{
	B	b;
}

上記の場合、Aのデストラクタは呼ばれない。

派生しないクラスの場合仮想にすると仮想関数テーブルの分だけクラスのサイズが増えるし、書かなくても自動でデストラクタが作られるが、上記が問題となるバグはvirtualの付け忘れに起因することが多い。
そしてたいていこのバグは非常に見つかりにくく、3時間くらいは時間を無駄にしてしまう。
だからたとえ派生させるつもりがないクラスでも習慣的に仮想デストラクタを書く癖をつけたほうがいいと思う。

throw()をつけるのはデストラクタから例外を呼ばないことを保障するため。
class	A{ public: virtual	~A(){ throw 0; } };

try
{
	A	a5;
	{
		A	a4;
		{
			A	a3;
			{
				A	a2;
				{
					A	a1;
				}	//1
			}
		}
	}
}
catch( int& i )
{
	//2
}

上記のコードを追っていくと1のところでa1がスコープアウトしてAのデストラクタが呼ばれ、throw 0;によりスコープの巻き戻しが始まる。
次にa2が解体され、a2のデストラクタで再び例外が投げられる。
このとき例外処理中に例外が起こったことでterminateが呼ばれてプログラムは終了してしまう。
つまりa3~a5は解体されないし、2のところまで実行されることもない。

このためデストラクタでは例外は呼ばないほうがいい。
たとえ例外がネストしないような設計で組んでいても、ふと疲れたときやコードの複雑化で間違えてネストさせてしまうとまたしても解決に時間のかかるバグとなる。

| | コメント (0)

2005年1月11日 (火)

std::copy

std::copyは内部でmemmoveを使っている。
安全性を考えると仕方ないが、遅い。速度をとりたいときはmemcpy

| | コメント (0)

« 2004年12月 | トップページ | 2005年2月 »