はじめに
Win32APIを利用するプログラムや、.NETのDebug.WriteLine()で利用されるデバッガに出力するAPI(OutputDebugString())を受信するサンプルプログラムです
OutputDebugString()は、デバッガーに引数の文字列を送信するAPIです
通常、IDEのコンソールに表示されますが、プログラムを単独で起動してもDebugViewなどで受信することができます
つまり、必要な時にだけデータを収集できるログとして利用することができます。ログファイルのように後片付けも不要なので、重宝していました
DebugView簡易版を作りたくなったので、ChatGPT(o3-min-high)に相談したのですが、一部正しくなかったので修正して動くようにしてみました
OutputDebugString()を受信するプログラムの流れ
OutputDebugString()
は、共有メモリ(DBWIN_BUFFER
)経由でデータを受け渡します。
また、プロセス間でリソースを共有するため、DBWIN_BUFFER_READY
、DBWIN_DATA_READY
という2つのイベントを利用して共有メモリの書き込み排他制御を行います
DBWIN_BUFFER
:受け取り側が作成する共有メモリ領域
DBWIN_BUFFER_READY
:受け取り側が共有メモリ領域を作成し受信の準備ができたことを通知するイベント
DBWIN_DATA_READY
:OutputDebugString()
呼び出し側が共有メモリに書き込み完了したことを通知するイベント
OutputDebugString()呼び出し側の流れ(概要)
- 共有メモリ
DBWIN_BUFFER
があれば開く。なければ処理を終了する - イベント
DBWIN_BUFFER_READY
、DBWIN_DATA_READY
を開く。なければ処理を終了する - イベント
DBWIN_BUFFER_READY
が通知され(シグナル状態にな)るまで待機 - 通知する文字列を共有メモリに書き込む
- イベント
BWIN_DATA_READY
をシグナル状態にして受信側に通知する
OutputDebugString()受信側の流れ(概要)
- 2つのイベント
DBWIN_BUFFER_READY
と、DBWIN_DATA_READY
を作成 - 共有メモリ
DBWIN_BUFFER
を作成(CreateFileMapping())。共有メモリは4Kbytで、最初の4ByteはプロセスID、残りは受信するNull終端文字列 -
DBWIN_BUFFER_READY
イベントをシグナル状態にして、共有メモリが使用可能になったことをOutputDebugString()呼び出し側プロセスへ通知 -
DBWIN_DATA_READY
イベントが通知されるまで待機(WaitForSingleObject())。OutputDebugString()が共有メモリに書き込み完了したタイミングで通知される - 共有メモリからプロセスIDと、通知された文字列を読み込む
- ステップ3に戻る(次の文字列を受信する)
OutputDebugString()を受信するソース
#include <windows.h>
#include <stdio.h>
// DBWIN(共有メモリの構造体)
struct DBWIN_BUFFER_STRUCT {
DWORD dwProcessId;
char szData[4096 - sizeof(DWORD)];
};
void cleanUp(HANDLE hBufferReady, HANDLE hDataReady = NULL, HANDLE hMapFile = NULL, LPVOID pBuf = NULL)
{
if (!pBuf) { UnmapViewOfFile(pBuf); }
if (!hMapFile) { CloseHandle(hMapFile); }
if (!hDataReady) { CloseHandle(hDataReady); }
if (!hBufferReady) { CloseHandle(hBufferReady); }
}
int main(void)
{
// DBWIN_BUFFER_READY イベントを作成(自動リセットではなく手動リセット)
HANDLE hBufferReady = CreateEvent(NULL, TRUE, FALSE, TEXT("DBWIN_BUFFER_READY"));
if (!hBufferReady) {
printf("CreateEvent(DBWIN_BUFFER_READY) failed with error %lu\n", GetLastError());
return 1;
}
// DBWIN_DATA_READY イベントを作成
HANDLE hDataReady = CreateEvent(NULL, FALSE, FALSE, TEXT("DBWIN_DATA_READY"));
if (!hDataReady) {
printf("CreateEvent(DBWIN_DATA_READY) failed with error %lu\n", GetLastError());
cleanUp(hBufferReady);
return 1;
}
// 共有メモリ領域 DBWIN_BUFFER を作成
HANDLE hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE,
0, sizeof(DBWIN_BUFFER_STRUCT), TEXT("DBWIN_BUFFER"));
if (!hMapFile) {
printf("CreateFileMapping failed with error %lu\n", GetLastError());
cleanUp(hBufferReady, hDataReady);
return 1;
}
// バッファが使用可能になったことを通知
SetEvent(hBufferReady);
// 共有メモリをマップする
LPVOID pBuf = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, sizeof(DBWIN_BUFFER_STRUCT));
if (!pBuf) {
printf("MapViewOfFile failed with error %lu\n", GetLastError());
cleanUp(hBufferReady, hDataReady, hMapFile);
return 1;
}
printf("Waiting for debug output...\n");
// 無限ループでデバッグ文字列を受信
while (true) {
// DBWIN_DATA_READY イベントがシグナル状態になるのを待つ
DWORD dwWait = WaitForSingleObject(hDataReady, INFINITE);
if (dwWait == WAIT_OBJECT_0) {
// 共有メモリから送信元プロセスIDと文字列を取得
DBWIN_BUFFER_STRUCT* pDBWin = reinterpret_cast<DBWIN_BUFFER_STRUCT*>(pBuf);
DWORD pid = pDBWin->dwProcessId;
char* msg = pDBWin->szData;
printf("PID %lu: %s", pid, msg);
}
}
cleanUp(hBufferReady, hDataReady, hMapFile, pBuf);
return 0;
}