OutputDebugString()

<windows.h> 檔案聲明了 OutputDebugString() 函式的兩個版本 - 一個用於 ASCII,一個用於 Unicode - 不像絕大多數 Win32 API 一樣,原始版本是 ASCII。而大多數的 Win32 API 的原始版本是 Unicode。使用一個 NULL 結尾的字元串緩衝區簡單調用 OutputDebugString() 將導致信息出現在調試器中,如果有調試器的話。

OutputDebugString ()

堅定的 Win32 開發人員可能對 OutputDebugString() API 函式比較熟悉,它可以使你的程式和調試器進行交談。它要比創建日誌檔案容易,而且所有“真正的”調試器都能使用它。應用程式和調試器交談的機制相當簡單,而本文將揭示整件事情是如何工作的。
本文首先是由以下事件促使的,我們觀察到 OutputDebugString()管理員和非管理員用戶試圖一起工作或遊戲時並不總是能可靠地工作(至少在 Win2000 上)。我們懷疑是一些相關的核心對象的許可權問題,此間涉略了相當多不得不寫下來的信息。
請注意,儘管我們使用了“調試器”這一術語,但不是從調試 API 的意義上來使用的:並沒有“單步執行”、“斷點”或者“附著到進程”等可以在 MS Visual C 或者一些真正的互動開發環境中找到的東西。從某種意義上來說,任何實現了協定的程式都是“調試器”。可能是一個非常小的命令行工具,或者像來自於 SysInternals 那幫聰明的傢伙們的 DebugView 那樣的高級貨。

應用程式用法

構建一條信息並傳送之的通常用法是:
sprintf(msgbuf, "Cannot open file %s [err=%ld]\n", fname, GetLastError());OutputDebugString(msgbuf);不過在實際環境中我們中的不少人會創建一個前端函式,以允許我們使用 printf 風格的格式化。下面的 odprintf() 函式格式化字元串,確保結尾有一個合適的回車換行(刪除原來的行結尾),並且傳送信息到調試器。
#include <stdio.h>#include <stdarg.h>#include <ctype.h>void __cdecl odprintf(const char *format, ...){char buf[4096], *p = buf;va_list args;va_start(args, format);p += _vsnprintf(p, sizeof buf - 1, format, args);va_end(args);while ( p > buf && isspace(p[-1]) )*--p = '\0';*p++ = '\r';*p++ = '\n';*p = '\0';OutputDebugString(buf);}於是在代碼中使用它就很簡單:
...odprintf("Cannot open file %s [err=%ld]", fname, GetLastError());...我們已經這樣使用多年了。

協定

在應用程式和調試器之間傳遞數據是通過一個 4KB 大小的共享記憶體塊完成的,並有一個互斥量和兩個事件對象用來保護對他的訪問。下面就是相關的四個核心對象
對象名稱對象類型DBWinMutexMutexDBWIN_BUFFERSection (共享記憶體)DBWIN_BUFFER_READYEventDBWIN_DATA_READYEvent互斥量通常一直保留在系統中,其他三個對象僅當調試器要接收信息才出現。事實上 - 如果一個調試器發現後三個對象已經存在,它會拒絕運行。
當 DBWIN_BUFFER 出現時,會被組織成以下結構。進程 ID 顯示信息的來源,字元串數據填充這 4K 的剩餘部分。按照約定,信息的末尾總是包括一個 NULL 位元組。
struct dbwin_buffer { DWORD dwProcessId; char data[4096-sizeof(DWORD)]; }; 當 OutputDebugString() 被套用調用時,它執行以下步驟。注意在任意位置的錯誤都將放棄整個事情,調試請求被認為是什麼也不做(不會傳送字元串)。
打開 DBWinMutex 並且等待,直到我們取得了獨占訪問。映射 DBWIN_BUFFER 段到記憶體中:如果沒有發現,則沒有調試器在運行,將忽略整個請求。打開 DBWIN_BUFFER_READY 和 DBWIN_DATA_READY 事件對象。就像共享記憶體段一樣,缺少對象意味著沒有可用的調試器。等待 DBWIN_BUFFER_READY 事件對象為有信號狀態:表示記憶體緩衝區不再被占用。大部分時候,這一事件對象一被檢查就處於有信號狀態,但等待緩衝區就緒不會超過 10 秒(逾時將放棄請求)。複製數據直到記憶體緩衝區中接近 4KB,再保存當前進程 ID。總是放置一個 NULL 位元組到字元串結尾。通過設定 DBWIN_DATA_READY 事件對象告訴調試器緩衝區就緒。調試器從那兒取走它。釋放互斥量。關閉事件對象和段對象,但保留互斥量的句柄以備後用。在調試器端會簡單一點。互斥量根本不需要,如果事件對象和/或共享記憶體對象已經存在,則假定其他調試器已經在運行。系統中任意時刻只能存在一個調試器。
創建共享記憶體段以及兩個事件對象。如果失敗,退出。設定 DBWIN_BUFFER_READY 事件對象,由此應用程式得知緩衝區可用。等待 DBWIN_DATA_READY 事件對象變為有信號狀態。從記憶體緩衝區中提取進程 ID 和 NULL 結尾的字元串。轉到步驟 2。這使我們認為這決不是一種低消耗的傳送信息的方法,應用程式的運行速度會受到調試器的左右。

相關詞條

熱門詞條

聯絡我們