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.