草庐IT

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

coder 2024-02-25 原文

我有一个动态加载插件 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/

有关c++ - 从重定向的 STDOUT 读取 Unicode(C++、Win32 API、Qt)的更多相关文章

  1. ruby-on-rails - unicode 字符串的长度 - 2

    在我的Rails(2.3,Ruby1.8.7)应用程序中,我需要将字符串截断到一定长度。该字符串是unicode,在控制台中运行测试时,例如'א'.length,我意识到返回了双倍长度。我想要一个与编码无关的长度,以便对unicode字符串或latin1编码字符串进行相同的截断。我已经了解了Ruby的大部分unicode资料,但仍然有些一头雾水。应该如何解决这个问题? 最佳答案 Rails有一个返回多字节字符的mb_chars方法。试试unicode_string.mb_chars.slice(0,50)

  2. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  3. ruby-on-rails - ActionController::RoutingError: 未初始化常量 Api::V1::ApiController - 2

    我有用于控制用户任务的Rails5API项目,我有以下错误,但并非总是针对相同的Controller和路由。ActionController::RoutingError:uninitializedconstantApi::V1::ApiController我向您描述了一些我的项目,以更详细地解释错误。应用结构路线scopemodule:'api'donamespace:v1do#=>Loginroutesscopemodule:'login'domatch'login',to:'sessions#login',as:'login',via::postend#=>Teamroutessc

  4. ruby - 将 spawn() 的标准输出/标准错误重定向到 Ruby 中的字符串 - 2

    我想使用spawn(针对多个并发子进程)在Ruby中执行一个外部进程,并将标准输出或标准错误收集到一个字符串中,其方式类似于使用Python的子进程Popen.communicate()可以完成的操作。我尝试将:out/:err重定向到一个新的StringIO对象,但这会生成一个ArgumentError,并且临时重新定义$stdxxx会混淆子进程的输出。 最佳答案 如果你不喜欢popen,这是我的方法:r,w=IO.pipepid=Process.spawn(command,:out=>w,:err=>[:child,:out])

  5. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  6. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  7. ruby-on-rails - Mandrill API 模板 - 2

    我正在使用Mandrill的RubyAPIGem并使用以下简单的测试模板:testastic按照Heroku指南中的示例,我有以下Ruby代码:require'mandrill'm=Mandrill::API.newrendered=m.templates.render'test-template',[{:header=>'someheadertext',:main_section=>'Themaincontentblock',:footer=>'asdf'}]mail(:to=>"JaysonLane",:subject=>"TestEmail")do|format|format.h

  8. Qt Designer的简单使用 - 2

    在前面两节的例子中,主界面窗口的尺寸和标签控件显示的矩形区域等,都是用C++代码编写的。窗口和控件的尺寸都是预估的,控件如果多起来,那就不好估计每个控件合适的位置和大小了。用C++代码编写图形界面的问题就是不直观,因此Qt项目开发了专门的可视化图形界面编辑器——QtDesigner(Qt设计师)。通过QtDesigner就可以很方便地创建图形界面文件*.ui,然后将ui文件应用到源代码里面,做到“所见即所得”,大大方便了图形界面的设计。本节就演示一下QtDesigner的简单使用,学习拖拽控件和设置控件属性,并将ui文件应用到Qt程序代码里。使用QtDesigner设计界面在开始菜单中找到「Q

  9. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

    文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

  10. ruby-on-rails - 在 Ruby (on Rails) 中使用 imgur API 获取图像 - 2

    我正在尝试使用Ruby2.0.0和Rails4.0.0提供的API从imgur中提取图像。我已尝试按照Ruby2.0.0文档中列出的各种方式构建http请求,但均无济于事。代码如下:require'net/http'require'net/https'defimgurheaders={"Authorization"=>"Client-ID"+my_client_id}path="/3/gallery/image/#{img_id}.json"uri=URI("https://api.imgur.com"+path)request,data=Net::HTTP::Get.new(path

随机推荐