Ser*_*eev 5 c++ winapi qt runtime
我有一个动态加载插件 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();
}
Run Code Online (Sandbox Code Playgroud)
然而,这个解决方案似乎有一个障碍——C 运行时库,它依赖于语言环境。因此,发送到 wcout 的任何输出都不会到达我的缓冲区,因为 C 运行时会截断 UTF-16 编码字符串中存在的不可打印 ASCII 字符处的字符串。调用setlocale()演示了 C 运行时执行字符串重新/编码。setlocale()对我来说没有帮助,因为我不知道文本的语言或区域设置,因为插件 DLL 从系统外部读取,并且可能混合了不同的语言。经过深思熟虑后,我决定放弃这个解决方案,恢复到 cout/wcout 缓冲区替换,并要求 DLL 调用初始化方法,原因有两个:UTF16 没有传递到我的缓冲区,然后是计算编码的问题在缓冲区中。但是,我仍然很好奇是否有一种方法可以通过 C 运行时将 UTF-16 字符串“按原样”输入管道,而不需要依赖于语言环境的转换?
ps 也欢迎任何有关 cout/wcout 重定向到 UI 而不是上述两种方法的建议:)
先感谢您!
我不知道这是否可行,但也许您可以在单独的进程中启动 DLL,并使用 Windows 等效项pipe(无论是什么,但 QtQProcess应该为您处理)捕获该进程的输出。这与 Firefox 处理进程外插件的方式类似(3.6.6 中默认,但在 64 位 Firefox 和 32 位 Flash 插件中已经这样做了一段时间)。您必须想出某种方法来与单独进程中的 DLL 进行通信,例如共享内存,但这应该是可能的。不一定很漂亮,但有可能。
| 归档时间: |
|
| 查看次数: |
2270 次 |
| 最近记录: |