我有一个C ++程序,该程序使用R API向R发送命令并显示结果。一旦简化为最小形式,它就是用C ++编码的R控制台。 过去(大部分时间)它在R.3.4.3和R.3.4.4上都能正常工作,但是当我尝试过渡到R.3.5.1时,一切都应运而生。 对于某些命令(通常是对“ par”或“ barplot”的调用,或与图形相关的任何东西),我收到错误消息:“ <我的命令>中的错误:未注册基本图形系统“
我以前从未遇到过此错误,谷歌搜索它给出的结果令人惊讶地少。
请注意,这种行为在常规R控制台中不会发生,因此它必须与C / C ++的R-API(以及它处理图形设备的方式)有关。
我的代码主要包含一个RManager类,该类调用API以与R交换,一个简单的窗口提供一个lineEdit(用户可以在其中输入命令)和一个文本字段,其中显示R结果(请参见上图)。 / p>
我将提供完整的代码以提高可重复性,但是如果您想跳到真正处理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的调用,该调用应该设置图形系统,从而防止出现此错误。我发现在这两种情况下,此函数都是在应用程序启动时被调用的,但不能使用相同的调用堆栈。
答案 0 :(得分:0)
我找到了解决方案(感谢R-devel邮件列表上的Luke Tierney)。 我只需要将对Rf_endEmbeddedR的调用移到RManager的析构函数中即可。
它并没有真正解释为什么它以R.3.4而不是R.3.5的方式工作,但是确实解决了实际问题。 也许从来没有这么快就调用过Rf_endEmbeddedR,但由于已修复了一个错误,它只能使用了。