【发布时间】:2011-03-12 17:57:40
【问题描述】:
我有一个动态加载插件 DLL 的 C++ 应用程序。 DLL 通过 std::cout 和 std::wcout 发送文本输出。基于 Qt 的 UI 必须从 DLL 中获取所有文本输出并显示出来。 由于运行时库的差异,DLL 可能具有不同的 cout/wcout 实例,因此使用流缓冲区替换的方法并不完全有效。因此,我应用了 Windows 特定的 STDOUT 重定向,如下所示:
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 字符串“按原样”放入管道中,而不需要依赖于语言环境的转换?
附言也欢迎任何关于 cout/wcout 重定向到 UI 而不是提到的两种方法的建议:)
提前谢谢你!
【问题讨论】:
-
你说得对,问题在于运行时如何处理(根据标准)
std::wcout。如果发送者和接收者都使用普通的 Windows API 函数,这将是最简单的方法。 -
谢谢菲利普!由于遗留问题,插件使用 cout/wcout,我必须让它们工作。
-
如果他们写信给
cout,您可能会尝试强制他们使用UTF-8,但我不知道如何处理wcout,因为标准需要修改某些本地编码通常是过时的 8 位编码。 -
Philipp,您能告诉我您实际指的是什么标准吗?