他プロセスが所有する HWND に SendMessage() 経由で有効なポインタ渡すため、プロセス間で共有するメモリを確保する方法


他プロセスで作成された HWND を使い、その HWND に SendMessage() することにより プロセス境界を越えて情報を得たい場合があります。

このとき送るメッセージが LVM_GETITEMCOUNT など「単純な値」を返すものである場合は、 特に問題なく、目的の情報を得ることができます。

しかし、LVM_GETITEM などのように「ポインタ」を渡す必要がある場合、 この「ポインタ」がプロセス境界を越えてしまうため、 HWND を所有する別プロセスでは無効なアドレスになってしまい、うまくいきません。

この問題を回避するためには、プロセス間で共有できるメモリを確保する必要があります。

なお、ここでいう HWND は全く任意のプロセスが所有するもと仮定します。
両プロセスともに自分で(プログラムを)作成しているような場合には、プロセス間で通信する様々な方法があり、 それを利用すればあらゆる情報の共有が可能です。(その方法についてはここでは説明しません)。


以下では、他プロセスが所有するタブコントロールの HWND を使ってアイテムのタイトルを取得する場合を例に考えます。


うまくいかない具体例

まず、何も考慮せずコードを書いてみましょう。

TCITEM	item;
char	text[256] = "";

	memset( &item, 0, sizeof( TCITEM ) );
	item.mask = TCIF_TEXT;
	item.pszText = text;
	item.cchTextMax = 256;

	if ( !::SendMessage( hwnd, TCM_GETITEM, 0, (LPARAM)&item ) )
		::GetLastError();

このように記述すると、SendMessage() で例外が発生します。

Windows95 では &item に本当に書き込んでいるようなので、 上手くすれば(?)相手のタスクをクラッシュさせることもできます。
この場合の結果は、&item の指すアドレスが相手のプロセス空間でどの程度重要な位置を指していたかによって、 「双方ハング」から「何も起こらない」まであり得ます。たぶん。

ちなみに、「双方ハング」は、SendMessage() 中に相手のタスクがハング -> SendMessage() から制御が戻ってこない -> 自分もハング となります。 SendMessageTimeout() を使えば、相手のみハングさせることが可能でしょう。たぶん。


共有メモリを確保する方法

プロセス間で共有できるメモリを確保する方法は、Windows95 と WindowsNT では異なります。

なお、ここで紹介する方法が、あらゆる条件で成功することを保証するものではない点に注意してください。

Windows95 の場合

考えられる方法は2つあります。


VirtualAlloc() を利用する方法

以下に、VirtualAlloc() を利用して共有メモリを確保する例を示します。

BYTE	*vAddr = (BYTE *)::VirtualAlloc( NULL, 0x1000, MEM_COMMIT|0x8000000, PAGE_READWRITE );
	if ( !vAddr ){
		::GetLastError();
		return;
	}
	ASSERT( (DWORD)vAddr & 0x80000000 );	// shared arena address?

TCITEM	*item = (TCITEM *)vAddr;
	item->mask = TCIF_TEXT;
	item->pszText = (LPTSTR)( vAddr + sizeof( TCITEM ) );
	item->cchTextMax = 256;
BOOL	res = ::SendMessage( hwnd, TCM_GETITEM, 0, (LPARAM)item );

	VERIFY( ::VirtualFree( vAddr, 0, MEM_RELEASE ) );

この方法の問題点は、VirtualAlloc() の非公開フラグ 0x8000000 を利用している点です。
が、記述はかなり短くなります。
でも、非公開機能を使うことはリスクが伴いますので、できれば避けるべきです。
なお、この方法は WindowsNT では通用しません。


メモリマップファイルを利用する方法

以下に、メモリマップファイルを利用して共有メモリを確保する例を示します。

HANDLE	hMap = ::CreateFileMapping( (HANDLE)0xffffffff, NULL,
				PAGE_READWRITE|SEC_COMMIT,
				0, 0x1000, NULL );
	if ( !hMap ){
		::GetLastError();
		return;
	}
LPVOID	pAddr = ::MapViewOfFile( hMap, FILE_MAP_WRITE, 0, 0, 0x1000 );
	if ( !pAddr ){
		::GetLastError();
		::CloseHandle( hMap );
		return;
	}
	ASSERT( (DWORD)vAddr & 0x80000000 );	// shared arena address?

TCITEM	*item = (TCITEM *)pAddr;
	item->mask = TCIF_TEXT;
	item->pszText = (LPTSTR)( (BYTE *)pAddr + sizeof( TCITEM ) );
	item->cchTextMax = 256;
BOOL	res = ::SendMessage( hwnd, TCM_GETITEM, 0, (LPARAM)item );

	VERIFY( ::UnmapViewOfFile( pAddr ) );
	VERIFY( ::CloseHandle( hMap ) );

この方法の問題点は、Windows95 ではメモリマップファイルが共有アドレスに確保される ということに依存している点です。 つまり、メモリマップファイルが共有メモリに確保される保証があるわけではないのです。 少なくとも関数のドキュメントには「呼び出したプロセスにマップされる」と記述されています。
実際、WindowsNT では 共有アドレスにマップされるわけではありません。

しかし、ここWindows95 ではメモリマップファイルは Shared Arena に確保されると言う記述があるので、 Windows95 ではこの方法を採るといいのではないでしょうか。
95 ではほかに、「 DPMI server に要求をだすと Shared Arena からメモリを獲得できる」とあるので、 暇のある方は試してみてはどうでしょう。たぶん DPMI Function Call (int 31H) を使えばいいんじゃないかなぁ。



WindowsNT4.0 の場合

WindowsNT4.0では VirtualAllocEx() を利用することにより、この問題に対処できます。
なお、WindowsNT ではプロセス間で共有するメモリを確保することはできません。

以下に、VirtualAllocEx() を利用して他プロセスに有効なメモリを確保する例を示します。
この方法は Windows95 の場合とは違い、目的のプロセスにのみ有効なアドレスを確保している点に注意してください。

DWORD	err = 0;
DWORD	idProcess = NULL;
DWORD	idThread = ::GetWindowThreadProcessId( hwnd, &idProcess );
	if ( !idThread || !idProcess ){
		err = ::GetLastError();
		return;
	}

HANDLE	hProcess = ::OpenProcess( PROCESS_VM_READ|PROCESS_VM_WRITE
				 |PROCESS_VM_OPERATION, FALSE, idProcess );
	if ( !hProcess ){
		err = ::GetLastError();
		return;
	}
	// !!! VirtualAllocEx()/VirtualFreeEx() は NT4.0 以上のみのサポート !!!
	// よって NT 専用でないアプリでは ::GetProcAddress() を使用する必要がある
LPVOID	vAddr = ::VirtualAllocEx( hProcess, NULL, 0x1000, MEM_COMMIT, PAGE_READWRITE );
	if ( !vAddr ){
		err = ::GetLastError();
		::CloseHandle( hProcess );
		return;
	}

TCITEM	item;
	memset( &item, 0, sizeof( TCITEM ) );
	item.mask = TCIF_TEXT;
	item.pszText = LPTSTR( (BYTE *)vAddr + sizeof( TCITEM ) );
	item.cchTextMax = 256;

DWORD	len = 0;
BOOL	res = 0;
BYTE	text[256] = "";

	if ( ::WriteProcessMemory( hProcess, vAddr, &item, sizeof( TCITEM ), &len ) ){
	    res = ::SendMessage( hwnd, TCM_GETITEM, 0, (LPARAM)vAddr );
	    if ( res )
		::ReadProcessMemory( hProcess, item.pszText, text, sizeof( text ), &len );
	}
	VERIFY( ::VirtualFreeEx( hProcess, vAddr, 0, MEM_RELEASE ) );
	::CloseHandle( hProcess );

注意してほしいのは、VirtualAllocEx() は WindowsNT4.0 以上でしかサポートされていない点です。
Windows95 からも起動する場合には、VirtualAllocEx() は GetProcAddress() を使って呼び出すようにする必要があります。

この方法は少々面倒ですが、公開された機能のみで構成されているので、将来的にも動作することが期待できるのではないでしょうか。
WindowsNT4.0 SP3 での動作を確認しましたが、あらゆるプロセスに対して有効かは不明です。


戻る


[TopPage]  [CG.Works]  [KLARA]  [Program]  [BBS]  [Links]