从重定向的STDOUT(C,Win32 API,Qt)读取Unicode
我有一个动态加载插件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,_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,BUFFER_SIZE); if (ReadFile(stdoutRead,NULL) && nofRead) { s.append(buf); } } } else { memset(buf,BUFFER_SIZE); if (ReadFile(stdoutRead,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,BUFFER_SIZE); } } // clean up on thread finish cleanUp(); } 但是,这个解决方案似乎有一个障碍 – C运行时库,它依赖于语言环境.因此,发送到wcout的任何输出都没有到达我的缓冲区,因为C运行时会截断UTF-16编码字符串中存在的非可打印ASCII字符的字符串.调用setlocale()演示,C运行时执行字符串重新/编码. setlocale()对我来说没有任何帮助,因为不了解文本的语言或语言环境,因为插件DLL从系统外部读取并且可能混合了不同的语言. 附:任何关于cout / wcout重定向到UI而不是两个提到的方法的建议也是受欢迎的:) 先感谢您! 解决方法
这里的问题是从wchar_t到char的代码转换完全是在插件DLL中完成的,无论它碰巧使用什么cout / wcout实现(正如你所说的那样,可能与主要的实现不一样)应用程序正在使用).因此,使其表现不同的唯一方法是以某种方式拦截该机制,例如使用streambuf替换.
但是,正如您所暗示的那样,您在主应用程序中编写的任何代码都不一定与DLL使用的库实现兼容.例如,如果在主应用程序中实现流缓冲区,则它不一定会使用与DLL中的流缓冲区相同的ABI.所以这很危险. 我建议你实现一个使用与插件相同的C库版本的包装器DLL,因此它保证兼容,并且在这个包装器DLL中对cout / wcout进行必要的干预.它可以动态加载插件,因此可以使用任何使用该库版本的插件重用.或者,您可以创建一些可重用的源代码,可以为每个插件专门编译,从而生成每个插件的清理版本. 一旦DLL被包装,您可以将流缓冲区替换为cout / wcout,将数据保存到内存中,就像我认为您最初计划的那样,并且根本不必弄乱文件句柄. PS:如果你确实需要制作一个转换为UTF-8和从UTF-8转换的wstream,那么我建议使用Boost的 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |