gpt4 book ai didi

c++ - 从重定向的 STDOUT 读取 Unicode(C++、Win32 API、Qt)

转载 作者:塔克拉玛干 更新时间:2023-11-03 02:20:10 24 4
gpt4 key购买 nike

我有一个动态加载插件 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 的建议,而不是上述两种方法:)

提前致谢!

最佳答案

这里的问题是从 wchar_tchar 的代码转换完全在插件 DLL 内完成,无论是 cout/wcout 它恰好正在使用的实现(如您所说,它可能与主应用程序正在使用的实现不同)。因此,让它表现不同的唯一方法是以某种方式拦截该机制,例如使用 streambuf 替换。

但是,正如您所暗示的,您在主应用程序中编写的任何代码都不一定与 DLL 使用的库实现兼容。例如,如果您在主应用程序中实现流缓冲区,则它不一定会使用与 DLL 中的流缓冲区相同的 ABI。所以这是有风险的。

我建议你实现一个wrapper DLL,它使用与插件相同的C++库版本,这样可以保证兼容,并且在这个wrapper DLL中对cout/wcout。它可以动态加载插件,因此可以与使用该库版本的任何插件重用。或者,您可以创建一些可重复使用的源代码,这些代码可以专门为每个插件编译,从而生成每个插件的净化版本。

包装 DLL 后,您可以将流缓冲区替换为 cout/wcout 以将数据保存到内存中,正如我认为您最初计划的那样,而不是必须弄乱文件句柄。

PS:如果您确实需要制作一个与 UTF-8 相互转换的 wstream,那么我建议使用 Boost 的 utf8_codecvt_facet。作为一种非常巧妙的方法。它易于使用,文档中有示例代码。(在这种情况下,您必须专门为插件使用的库版本编译一个 Boost 版本,但一般情况下不需要。)

关于c++ - 从重定向的 STDOUT 读取 Unicode(C++、Win32 API、Qt),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3193775/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com