R API for C 中 R.3.4.4 和 R.3.5.1 之间的区别

Posted

技术标签:

【中文标题】R API for C 中 R.3.4.4 和 R.3.5.1 之间的区别【英文标题】:Difference between R.3.4.4 and R.3.5.1 in R API for C 【发布时间】:2018-12-16 23:41:12 【问题描述】:

我有一个 C++ 程序,它使用 R API 向 R 发送命令并显示结果。一旦简化为最小形式,它就是一个用 C++ 编码的 R 控制台。 它曾经在 R.3.4.3 和 R.3.4.4 上正常工作(大部分时间),但是当我尝试过渡到 R.3.5.1 时,一切都崩溃了。 对于某些命令(通常是对“par”或“barplot”或与图形相关的任何内容的调用),我收到错误消息:“中的错误:基本图形系统未注册 em>"

我以前从未遇到过这个错误,谷歌搜索它得到的结果出奇地少。

我的控制台使用 R3.4.3(与 3.4.4 相同):

使用 R3.5.1 的相同命令:

请注意,此行为不会发生在常规 R 控制台中,因此它必须与 C/C++ 的 R-API (以及它处理图形设备的方式,也许?)有关。

我的代码本质上包含一个调用 API 与 R 交换的 RManager 类,以及一个提供 lineEdit 的简单窗口,用户可以在其中输入其命令,以及一个显示 R 结果的文本字段(见上图)。

我将提供完整的可重现性代码,但如果你想跳转到真正处理 R 通信的地方,这一切都发生在 rmanager.cpp 中,剩下的只是 GUI。

main.cpp:

#include "mainwindow.h"
#include <QApplication>
#include <thread>
#include <iostream>
#include <chrono>

#ifdef _WIN32
#include <windows.h>
#include <tlhelp32.h>
#include <QProcess>
#include <cwchar>
#endif


int main(int argc, char *argv[])

    int result = 0;

    QApplication a(argc, argv);
    MainWindow w;

    w.show();

    result = a.exec();

    return result;

MainWindow.h:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTextStream>
#include <QFile>
#include <QTimer>
#include <rmanager.h>


namespace Ui 
class MainWindow;


class MainWindow : public QMainWindow

    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private slots:
    // Command received from GUI
    void on_inputCmd_returnPressed();
    // Read result from the R Manager
    void getResult(QString);
    //Read errors from the R Manager
    void getError(QString);

private:
    Ui::MainWindow *ui;

    QTimer pollInput;
    RManager *rconsole;

    // Result buffer for last command
    QString resultBuffer;

    // Send command directly to R
    void sendRCmd(QString command);

signals:
    // Starts the R Manager event loop
    void runConsole();
;

#endif // MAINWINDOW_H

mainWindow.cpp:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)

    ui->setupUi(this);  // Just a QLineEdit for inputs and a QTextEdit to display result.

    ui->outputConsole->document()->setMaximumBlockCount(500);

    // R API connection
    rconsole = new RManager(parent);

    // Signals connection
    connect(rconsole,   SIGNAL(writeConsole(QString)),  this,       SLOT(getResult(QString)));
    connect(rconsole,   SIGNAL(writeConsoleError(QString)), this,   SLOT(getError(QString)));
    connect(this,       SIGNAL(runConsole()),           rconsole,   SLOT(runConsole()));

    pollInput.start(10);    // Check for R results every 10 ms.

    // R Callbacks event loop
    emit runConsole();


MainWindow::~MainWindow()

    delete ui;
    pollInput.stop();


/**
 * @brief MainWindow::getResult Aggregate results from R until an only '\n' is sent
 * Then send it to the user (RPP or GUI)
 * @param res
 */
void MainWindow::getResult(QString res)

    // While != "\n" add res to the result buffer
    if (res != "\n") 
        resultBuffer.append(res);
     else 
        // the res to the resultBuffer to conserve the last \n
        resultBuffer.append(res);

        // Get the current text values from the text fields and append the result
        ui->outputConsole->append(resultBuffer);

        resultBuffer.clear();
    


/**
 * @brief MainWindow::getError Send immediatly any error from R
 * @param error
 */
void MainWindow::getError(QString error)

    qDebug() << "getError called with error: " << error ;

    // Get the current text values from the text fields and append the result
    ui->outputConsole->append(error);


/**
 * @brief MainWindow::sendRCmd Low level method to send command to R
 * Display the command in the GUI
 * @param command
 */
void MainWindow::sendRCmd(QString command)

    ui->outputConsole->append("> "+command+"\n");

    // Send the command to R
    rconsole->parseEval(command);

    ui->inputCmd->clear();



/**
 * @brief MainWindow::on_inputCmd_returnPressed Send command to R from the GUI
 */
void MainWindow::on_inputCmd_returnPressed()

    // Get the current text values from the text fields
    QString command = ui->inputCmd->text();
    sendRCmd(command);

ui_mainwindow.h(由 Qt Creator 生成):

/********************************************************************************
** Form generated from reading UI file 'mainwindow.ui'
**
** Created by: Qt User Interface Compiler version 5.11.0
**
** WARNING! All changes made in this file will be lost when recompiling UI file!
********************************************************************************/

#ifndef UI_MAINWINDOW_H
#define UI_MAINWINDOW_H

#include <QtCore/QVariant>
#include <QtWidgets/QApplication>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QTextEdit>
#include <QtWidgets/QVBoxLayout>
#include <QtWidgets/QWidget>

QT_BEGIN_NAMESPACE

class Ui_MainWindow

public:
    QWidget *centralWidget;
    QVBoxLayout *verticalLayout;
    QTextEdit *outputConsole;
    QLineEdit *inputCmd;

    void setupUi(QMainWindow *MainWindow)
    
        if (MainWindow->objectName().isEmpty())
            MainWindow->setObjectName(QStringLiteral("MainWindow"));
        MainWindow->resize(382, 413);
        centralWidget = new QWidget(MainWindow);
        centralWidget->setObjectName(QStringLiteral("centralWidget"));
        verticalLayout = new QVBoxLayout(centralWidget);
        verticalLayout->setSpacing(6);
        verticalLayout->setContentsMargins(11, 11, 11, 11);
        verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
        outputConsole = new QTextEdit(centralWidget);
        outputConsole->setObjectName(QStringLiteral("outputConsole"));
        outputConsole->setFocusPolicy(Qt::NoFocus);
        outputConsole->setUndoRedoEnabled(false);
        outputConsole->setReadOnly(true);

        verticalLayout->addWidget(outputConsole);

        inputCmd = new QLineEdit(centralWidget);
        inputCmd->setObjectName(QStringLiteral("inputCmd"));
        inputCmd->setClearButtonEnabled(true);

        verticalLayout->addWidget(inputCmd);

        MainWindow->setCentralWidget(centralWidget);

        retranslateUi(MainWindow);

        QMetaObject::connectSlotsByName(MainWindow);
     // setupUi

    void retranslateUi(QMainWindow *MainWindow)
    
        MainWindow->setWindowTitle(QApplication::translate("MainWindow", "MainWindow", nullptr));
     // retranslateUi

;

namespace Ui 
    class MainWindow: public Ui_MainWindow ;
 // namespace Ui

QT_END_NAMESPACE

#endif // UI_MAINWINDOW_H

rmanager.h

#ifndef RMANAGER_H
#define RMANAGER_H

#include <QObject>

class RManager : public QObject

    Q_OBJECT
public:
    explicit RManager(QObject *parent = 0);

    // R side methods/callbacks
    int parseEval(const QString & line);

    // R interface callbacks
    void myShowMessage( const char* message );
    void myWriteConsoleEx( const char* message, int len, int oType );
    int  myReadConsole(const char *, unsigned char *, int, int);
    int  winReadConsole(const char*, char*, int, int);
    void myResetConsole();
    void myFlushConsole();
    void myCleanerrConsole();
    void myBusy( int which );

    static RManager &r();

signals:
    void writeConsole(QString);
    void writeConsoleError(QString);

public slots:
    void runConsole();

private:
    bool R_is_busy;
    static RManager *r_inst;
;

// Functions to match the library : call RManager's methods
void myR_ShowMessage( const char* message );
void myR_WriteConsoleEx( const char* message, int len, int oType );
int  myR_ReadConsole(const char *prompt, unsigned char *buf, int len, int addtohistory);
int  ReadConsole(const char *prompt, char *buf, int len, int addtohistory);
void myR_ResetConsole();
void myR_FlushConsole();
void myR_ClearerrConsole();
void myR_Busy( int which );

void myR_CallBack();
void myR_AskOk(const char *);
int  myR_AskYesNoCancel(const char *);

#endif // RMANAGER_H

最后是 rmanager.cpp

#include "rmanager.h"

#include <qmessagebox.h>
#include <QDebug>

#define R_INTERFACE_PTRS

#include <Rembedded.h>
#ifndef _WIN32
#include <Rinterface.h>     // For Linux.
#endif
#include <R_ext/RStartup.h>
#include <Rinternals.h>
#include <R_ext/Parse.h>
#include <locale.h>

RManager* RManager::r_inst = 0 ;

RManager::RManager(QObject *parent) : QObject(parent)

    if (r_inst) 
        throw std::runtime_error( tr("Il ne peut y avoir qu'une instance de RppConsole").toStdString() ) ;
     else 
        r_inst = this ;
    

    const char *argv[] = "RConsole", "--gui=none", "--no-save",
                        "--silent", "--vanilla", "--slave";
    int argc = sizeof(argv) / sizeof(argv[0]);

    setlocale(LC_NUMERIC, "C"); //try to ensure R uses .

#ifndef _WIN32
    R_SignalHandlers = 0;               // Don't let R set up its own signal handlers
#endif

    Rf_initEmbeddedR(argc, (char**)argv);      // The call that is supposed to register the graphics system, amongst other things.

    R_ReplDLLinit();                    // this is to populate the repl console buffers

    structRstart Rst;
    R_DefParams(&Rst);
    Rst.R_Interactive = (Rboolean) false;       // sets interactive() to eval to false
#ifdef _WIN32
    Rst.rhome = getenv("R_HOME");
    Rst.home = getRUser();
    Rst.CharacterMode = LinkDLL;
    Rst.ReadConsole = ReadConsole;
    Rst.WriteConsole = NULL;
    Rst.WriteConsoleEx = myR_WriteConsoleEx;
    Rst.CallBack = myR_CallBack;
    Rst.ShowMessage = myR_AskOk;
    Rst.YesNoCancel = myR_AskYesNoCancel;
    Rst.Busy = myR_Busy;
#endif
    R_SetParams(&Rst);

    // Assign callbacks to R's
#ifndef _WIN32
    ptr_R_ShowMessage = myR_ShowMessage ;
    ptr_R_ReadConsole = myR_ReadConsole;
    ptr_R_WriteConsoleEx = myR_WriteConsoleEx ;
    ptr_R_WriteConsole = NULL;
    ptr_R_ResetConsole = myR_ResetConsole;
    ptr_R_FlushConsole = myR_FlushConsole;
    ptr_R_ClearerrConsole = myR_ClearerrConsole;
    ptr_R_Busy = myR_Busy;


    R_Outputfile = NULL;
    R_Consolefile = NULL;
#endif

#ifdef TIME_DEBUG
    _earliestSendToRBool = false;
#endif
    Rf_endEmbeddedR(0);


RManager &RManager::r()

    return *r_inst;


void RManager::runConsole()

    // Start the event loop to get results from R
    R_ReplDLLinit();
    while (R_ReplDLLdo1() > 0) 



/**
 * @brief RManager::parseEval is the core of this console, sending commands to R.
 * @param line
 * @return
 */
int RManager::parseEval(const QString &line) 
    ParseStatus status;
    SEXP cmdSexp, cmdexpr = R_NilValue;
    int i, errorOccurred, retVal=0;

    // Convert the command line to SEXP
    PROTECT(cmdSexp = Rf_allocVector(STRSXP, 1));
    SET_STRING_ELT(cmdSexp, 0, Rf_mkChar(line.toLocal8Bit().data()));
    cmdexpr = PROTECT(R_ParseVector(cmdSexp, -1, &status, R_NilValue));

    switch (status)
    case PARSE_OK:
        // Loop is needed here as EXPSEXP might be of length > 1
        for(i = 0; ((i < Rf_length(cmdexpr)) && (retVal==0)); i++)
            R_tryEval(VECTOR_ELT(cmdexpr, i), R_GlobalEnv, &errorOccurred);
            if (errorOccurred) 
                retVal = -1;
            
        
        break;
    case PARSE_INCOMPLETE:
        // need to read another line
        retVal = 1;
        break;
    case PARSE_NULL:
        Rf_warning(tr("%s: Etat d'analyse de commande : NULL (%d)\n").toStdString().data(), "RPPConsole", status);
        retVal = -2;
        break;
    case PARSE_ERROR:
        Rf_warning(tr("Erreur d'analyse de la commande : \"%s\"\n").toStdString().data(), line.toStdString().c_str());
        retVal = -2;
        break;
    case PARSE_EOF:
        Rf_warning(tr("%s: Etat d'analyse de commande : EOF (%d)\n").toStdString().data(), "RPPConsole", status);
        break;
    default:
        Rf_warning(tr("%s: Etat d'analyse de commande non documenté %d\n").toStdString().data(), "RPPConsole", status);
        retVal = -2;
        break;
    
    UNPROTECT(2);
    return retVal;


// RManager callbacks implementation
void RManager::myShowMessage(const char *message)

    // Never called till now
    QMessageBox::information(qobject_cast<QWidget*>(parent()),QString(tr("Bonjour le monde")),QString(message),QMessageBox::Ok,QMessageBox::NoButton);


void RManager::myWriteConsoleEx(const char *message, int len, int oType)

    QString msg;

    if (len) 
        msg = QString::fromLocal8Bit(message, len);
        if(!oType)
            emit writeConsole(msg);
        else
            emit writeConsoleError(msg);
    


int RManager::myReadConsole(const char* /*prompt*/, unsigned char* /*buf*/, int /*len*/, int /*addtohistory*/ )
    return 0;


// For Windows, unsigned char is replaced by char
int RManager::winReadConsole(const char* /*prompt*/, char* /*buf*/, int /*len*/, int /*addtohistory*/ )
    return 0;


void RManager::myResetConsole()




void RManager::myFlushConsole()




void RManager::myCleanerrConsole()




void RManager::myBusy( int which )
    R_is_busy = static_cast<bool>( which ) ;


// Connects R callbacks to RManager static methods
void myR_ShowMessage( const char* message )
    RManager::r().myShowMessage( message ) ;


void myR_WriteConsoleEx( const char* message, int len, int oType )
    RManager::r().myWriteConsoleEx(message, len, oType);


int myR_ReadConsole(const char *prompt, unsigned char *buf, int len, int addtohistory)
    return RManager::r().myReadConsole( prompt, buf, len, addtohistory ) ;


int ReadConsole(const char *prompt, char *buf, int len, int addtohistory) 
    return RManager::r().winReadConsole( prompt, buf, len, addtohistory ) ;


void myR_ResetConsole()
    RManager::r().myResetConsole();


void myR_FlushConsole()
    RManager::r().myFlushConsole();


void myR_ClearerrConsole()
    RManager::r().myCleanerrConsole();


void myR_Busy( int which )
    RManager::r().myBusy(which);


void myR_CallBack() 
//    Called during i/o, eval, graphics in ProcessEvents


void myR_AskOk(const char* /*info*/) 



int myR_AskYesNoCancel(const char* /*question*/) 
    const int yes = 1;
    return yes;

提前感谢您对问题可能存在的想法。它是 R.3.5.1 错误,还是我应该定义/连接和错过的东西?我阅读了 R.3.5.1 更改说明,但没有找到任何线索。

PS:我在 windows 10 下,使用 Microsoft Visual C++ Compiler 15.0(32 位)编译,并使用 Qt 5.11.0(用于 GUI 组件)。

PPS:按照 user2554330 的建议,我检查了对 GEregisterSystem 的调用,它应该设置图形系统,从而防止出现此错误。我发现在这两种情况下,在应用程序启动时都会调用此函数,但不是使用相同的调用堆栈。

对于 R.3.4.3:

对于 R.3.5.1:

【问题讨论】:

图形系统在对GEregisterSystem的调用中注册。我会使用调试器来查看您在旧 R 版本与当前版本中对该函数的调用是否存在差异。 您好!我试过你的建议。我发现在这两种情况下都调用了 GEregisterSystem(令人惊讶的是,我预计它不会在 3.5.1 中调用)。但是,调用堆栈不一样。我将更新我的帖子以添加这些信息,并继续探索。 【参考方案1】:

我找到了解决方案(感谢 R-devel 邮件列表上的 Luke Tierney)。 我只需要将对 Rf_endEmbeddedR 的调用移到 RManager 的析构函数中,它应该在哪里。

它并没有真正解释为什么它在 R.3.4 中而不是在 R.3.5 中以以前的方式工作,但它确实解决了实际问题。 也许这不应该与这么快调用的 Rf_endEmbeddedR 一起工作,而且它只是过去,多亏了一个已修复的错误。

【讨论】:

以上是关于R API for C 中 R.3.4.4 和 R.3.5.1 之间的区别的主要内容,如果未能解决你的问题,请参考以下文章

For loop in MATLAB,R,C programming language.

For loop in MATLAB,R,C programming language.

二维数组(扩展hash数组)以及json,严格模式,字符串的常用api

R:for循环中的文本进度条

r - 迭代2个变量for

如何在 Shiny R 中添加反应式 for 循环?