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

我之前从未遇到过此错误,而google搜索到的结果却很少。

我的控制台使用R3.4.3(与3.4.4相同):c++ - 用于C的R API中的R.3.4.4和R.3.5.1之间的区别-LMLPHP

使用R3.5.1的相同命令:c++ - 用于C的R API中的R.3.4.4和R.3.5.1之间的区别-LMLPHP

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

我的代码基本上包含一个RManager类,该类调用API来与R交换,一个简单的窗口提供一个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++编译器15.0(32位)进行编译,并使用Qt 5.11.0(用于GUI组件)进行编译。

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

对于R.3.4.3:
c&#43;&#43; - 用于C的R API中的R.3.4.4和R.3.5.1之间的区别-LMLPHP

对于R.3.5.1:
c&#43;&#43; - 用于C的R API中的R.3.4.4和R.3.5.1之间的区别-LMLPHP

最佳答案

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

它并没有真正解释为什么它以R.3.4而不是R.3.5的方式工作,但是确实解决了实际问题。
也许从来没有这么快就可以调用Rf_endEmbeddedR来解决这个问题,但是由于已经修复了一个错误,它只能使用了。

09-16 19:04