satoh

知っているようで知らないデジタル図形処理 ~任意形状図形の選択から交点・接点・接線計算まで、方程式では解けないノウハウを紹介

   

OutputDebugStringで出力したデバッグライトを自前で受け取る

デバッグ情報を自前で受け取りたい

WindowsプログラムをCで組む時に使うもので、OutputDebugString()というWin32APiがあるのはご存知だと思います。
VisualStudioなど統合環境で開発しているならば、デバッグ中に出力ウインドウに出力することができます。
フリーウェアでもDbgMOnなどがありますが、ここは自分に便利なデバッグモニタが欲しいところです。受信したデバッグ情報をシリアル通信で飛ばすなどリモート機能も簡単に作れますし。
というわけで簡単に作ってしまいましょう。

どんな仕組みになっているのか

OutputDebugString はどこへ出力しているのでしょうか。
ファイルに出力しているわけではありません。通信でもありません。出力先は共有メモリ(プロセス間の、です)です。
まず、”DBWIN_BUFFER”という名前付きマップドファイルを用意します。次に”DBWIN_BUFFER_READY”という名前付きイベントをオンにすることでOutputDebugStringが用意した共有メモリに出力します。OutputDebugStringは自分の出力が終わると、”DBWIN_DATA_READY”という名前付きイベントをオンにします。
面白いのは、Windows上でいろいろなプログラムが動いているとき、それぞれがOutputDebugStringで出力している情報をすべて取得できてしまうことです。そのためにその情報にはプロセスIDも含まれていて、ターゲットのプロセスだけに限定することも可能です。
早速、雛形になるようなものを作ってみましょう。

OutputDebugString出力を受信してファイル出力する

早速ソースコードを書いてみます。
マップドファイル、イベントともセキュリティ指定が必要なことを忘れないようにしてください。

//----------------------------------------------------------------------
//		OutputDebugStringを受信してファイル出力する
//----------------------------------------------------------------------
int WaitOutputDebugString()
{
	HANDLE      hEvAck ;
	HANDLE      hEvReady ;
	HANDLE      hMapFile ;
	LPVOID      pMem ;
	BOOL        bstat ;
	DWORD       dstat ;
	int         len ;
	SYSTEMTIME  st ;
	FILE        *fp ;
	char        fname[260] ;
	char        buff[4096] ;
	char        name[260] ;
	char        limit[260] ;
	DWORD       lastPID ;
	LPDWORD     pPID ;
	LPSTR       pSTR ;

	SECURITY_ATTRIBUTES     sa ;
	SECURITY_DESCRIPTOR     sd ;

//--------------- セキュリティ
	sa.nLength = sizeof(SECURITY_ATTRIBUTES) ;
	sa.bInheritHandle = TRUE ;
	sa.lpSecurityDescriptor = &sd ;

	bstat = InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION) ;
	if (!bstat) return -1 ;

	bstat = SetSecurityDescriptorDacl(&sd, TRUE, (PACL)NULL, FALSE) ;
	if (!bstat) return -2 ;

//--------------- ACKイベント(シグナル状態で、次のOutputDebugString()用意)
	hEvAck = CreateEvent(&sa, FALSE, FALSE, "DBWIN_BUFFER_READY") ;
	if (hEvAck == 0) return -3 ;
	if (GetLastError() == ERROR_ALREADY_EXISTS) return -4 ;

//--------------- READYイベント(シグナル状態でOutputDebugString()出力が完了)
	hEvReady = CreateEvent(&sa, FALSE, FALSE, "DBWIN_DATA_READY") ;
	if (hEvReady == 0) return -5 ;
	if (GetLastError() == ERROR_ALREADY_EXISTS) return -6 ;

//--------------- "DBWIN_BUFFER"という名称の共有メモリ(4096bytes)
	hMapFile = CreateFileMapping((HANDLE)0xFFFFFFFF,
								 &sa,
								 PAGE_READWRITE,
								 0,
								 4096,
								 "DBWIN_BUFFER") ;
	if (hMapFile == 0) return -7 ;

	pMem = MapViewOfFile(hMapFile,
						 FILE_MAP_READ,
						 0,
						 0,
						 4096) ;
	if (pMem == 0) return -8 ;

//--------------- 先頭DWORDがプロセスID、以下が格納文字列
	pPID = (LPDWORD)pMem ;
	pSTR = (LPSTR)(pPID + 1) ;
	lastPID = 0xFFFFFFFF ;

	SetEvent(hEvAck) ;
	fp = 0 ;

	while (TRUE) {

		dstat = WaitForSingleObject(hEvReady, INFINITE) ;
		if (dstat != WAIT_OBJECT_0) return -9 ;

		len = lstrlen(pSTR) ;

//------------ ここで受け取った文字列、pSTR をコンソールなりファイルなりに出力すればよい
//				この例ではプロセスIDごとにファイル名を変えてファイル出力している
		sprintf(fname, "%08X.out", *pPID) ;
		fp = fopen(fname, "a+") ;
		if (fp) {
			GetLocalTime(&st) ;
			fprintf(fp, "[%04d/%02d/%02d %02d:%02d:%02d] %sn",
							st.wYear, st.wMonth, st.wDay,
							st.wHour, st.wMinute, st.wSecond,
							pSTR) ;
			fclose(fp) ;
		}

		SetEvent(hEvAck) ;
	}

	return 0 ;
}

 - C, Win32API, Windows