我有一個C++應用程序,它動態加載插件DLL。該DLL通過std :: cout和std :: wcout發送文本輸出。基於Qt的UI必須抓取所有來自DLL的文本輸出並顯示它。 由於運行時庫差異,DLL可能具有不同的cout/wcout實例,因此使用流緩衝區替換的方法並不完全可行。因此,我已經應用Windows特定STDOUT重定向如下:從重定向的STDOUT(C++,Win32 API,Qt)讀取Unicode
StreamReader::StreamReader(QObject *parent) :
QThread(parent)
{
// void
}
void StreamReader::cleanUp()
{
// restore stdout
SetStdHandle (STD_OUTPUT_HANDLE, oldStdoutHandle);
CloseHandle(stdoutRead);
CloseHandle(stdoutWrite);
CloseHandle (oldStdoutHandle);
hConHandle = -1;
initDone = false;
}
bool StreamReader::setUp()
{
if (initDone)
{
if (this->isRunning())
return true;
else
cleanUp();
}
do
{
// save stdout
oldStdoutHandle = ::GetStdHandle (STD_OUTPUT_HANDLE);
if (INVALID_HANDLE_VALUE == oldStdoutHandle)
break;
if (0 == ::CreatePipe(&stdoutRead, &stdoutWrite, NULL, 0))
break;
// redirect stdout, stdout now writes into the pipe
if (0 == ::SetStdHandle(STD_OUTPUT_HANDLE, stdoutWrite))
break;
// new stdout handle
HANDLE lStdHandle = ::GetStdHandle(STD_OUTPUT_HANDLE);
if (INVALID_HANDLE_VALUE == lStdHandle)
break;
hConHandle = ::_open_osfhandle((intptr_t)lStdHandle, _O_TEXT);
FILE *fp = ::_fdopen(hConHandle, "w");
if (!fp)
break;
// replace stdout with pipe file handle
*stdout = *fp;
// unbuffered stdout
::setvbuf(stdout, NULL, _IONBF, 0);
hConHandle = ::_open_osfhandle((intptr_t)stdoutRead, _O_TEXT);
if (-1 == hConHandle)
break;
return initDone = true;
} while(false);
cleanUp();
return false;
}
void StreamReader::run()
{
if (!initDone)
{
qCritical("Stream reader is not initialized!");
return;
}
qDebug() << "Stream reader thread is running...";
QString s;
DWORD nofRead = 0;
DWORD nofAvail = 0;
char buf[BUFFER_SIZE+2] = {0};
for(;;)
{
PeekNamedPipe(stdoutRead, buf, BUFFER_SIZE, &nofRead, &nofAvail, NULL);
if (nofRead)
{
if (nofAvail >= BUFFER_SIZE)
{
while (nofRead >= BUFFER_SIZE)
{
memset(buf, 0, BUFFER_SIZE);
if (ReadFile(stdoutRead, buf, BUFFER_SIZE, &nofRead, NULL)
&& nofRead)
{
s.append(buf);
}
}
}
else
{
memset(buf, 0, BUFFER_SIZE);
if (ReadFile(stdoutRead, buf, BUFFER_SIZE, &nofRead, NULL)
&& nofRead)
{
s.append(buf);
}
}
// Since textReady must emit only complete lines,
// watch for LFs
if (s.endsWith('\n')) // may be emmitted
{
emit textReady(s.left(s.size()-2));
s.clear();
}
else // last line is incomplete, hold emitting
{
if (-1 != s.lastIndexOf('\n'))
{
emit textReady(s.left(s.lastIndexOf('\n')-1));
s = s.mid(s.lastIndexOf('\n')+1);
}
}
memset(buf, 0, BUFFER_SIZE);
}
}
// clean up on thread finish
cleanUp();
}
然而,這種方案似乎有障礙物 - C運行時庫,其是區域設置相關。因此,發送到wcout的任何輸出都不會到達我的緩衝區,因爲C運行時會以UTF-16編碼的字符串中存在的不可打印的ASCII字符截斷字符串。調用setlocale()演示了C運行時執行字符串重新編碼。 setlocale()對我來說沒有任何幫助,因爲沒有關於文本的語言或語言環境的知識,因爲插件DLL從系統外部讀取,並且可能會混合使用不同的語言。 在給出了一個N想法後,我決定放棄這個解決方案,並恢復到cout/wcout緩衝區替換,並且要求DLL調用初始化方法,原因有兩個:UTF16不傳遞到我的緩衝區,然後解決問題編碼在緩衝區中。然而,我仍然對是否有辦法通過C運行時將UTF-16字符串轉換爲「原樣」管道,而無需依賴於區域的轉換?
p.s.任何關於cout/wcout重定向到UI的建議,而不是以上提及的兩種方法都是受歡迎的:)
提前致謝!
您說得對,問題在於運行時如何處理(根據標準的要求)'std :: wcout'。如果發送者和接收者都使用普通的Windows API函數,這將是最簡單的方法。 – Philipp 2010-07-07 10:21:12
謝謝菲利普!由於遺留問題插件使用cout/wcout,我必須讓它們工作。 – 2010-07-07 14:31:41
如果他們寫入'cout',你可能會試圖強制他們使用UTF-8,但是我不知道如何處理'wcout',因爲標準要求對某些本地編碼進行編碼,這通常是一個過時的8-位編碼。 – Philipp 2010-07-09 20:47:29