在前面的一篇文章中,向大家介绍过《基于Qt实现的自定义简易Lua脚本编辑器》,实际上就是使用Lua脚本拓展cpp应用程序。本文着重向大家分享使用javaScript脚本拓展CPP应用程序的方法。在接下来的多语言混合编程系列博文中,我还将和大家一起分享使用python、go以及shell(windows脚本和Linux脚本)拓展CPP应用程序的方法。
开始正题前,咱们先演示下效果。
编程环境:Qt4.8.3 + MingW
编程语言:C++ 及JavaScript
1、新建一个Qt应用程序WxCPP,在pro文件中修改,添加 scipt 模块
#-------------------------------------------------
#
# Project created by QtCreator 2020-05-23T12:11:00
#
#-------------------------------------------------
QT += core gui script
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = WxCPP
TEMPLATE = app
INCLUDEPATH += ./
SOURCES +=\
wxcpp.cpp \
wxcpp_main.cpp \
wxform.cpp
HEADERS += wxcpp.h \
wxform.h
FORMS += \
wxform.ui
2、添加一个ui界面类,并完成主体的ui设计部分。参照文章开始的演示图片。
3、实现js脚本拓展CPP应用程序的主要逻辑
// WxForm.h
#ifndef WXFORM_H
#define WXFORM_H
#include
#include
#include
#include
namespace Ui {
class WxForm;
}
class WxForm : public QWidget
{
Q_OBJECT
public:
explicit WxForm(QWidget *parent = 0);
~WxForm();
bool showScriptContext(QString fileName);
private slots:
void on_openScript_pushButton_clicked();
void on_execScript_pushButton_clicked();
void on_getValueFromJs(QVariant variant);
void on_closeScriptExec_pushButton_clicked();
void on_save_pushButton_clicked();
void on_saveAs_pushButton_clicked();
void on_scriptFinished();
private:
Ui::WxForm *ui;
QString m_curScriptFilePath;
WxCPP wxcpp;
QVariant m_ret;
};
#endif // WXFORM_H
// WxForm.cpp
#include "wxform.h"
#include "ui_wxform.h"
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef Q_OS_WIN
#include
#endif
#include
#include
WxForm::WxForm(QWidget *parent) :
QWidget(parent),
ui(new Ui::WxForm)
{
ui->setupUi(this);
ui->spinBox->setRange(1000,10000);
ui->spinBox->setSingleStep(1000);
m_ret = QVariant(0);
QObject::connect(ui->scriptExecType_comboBox,SIGNAL(currentIndexChanged(int)),&wxcpp,SLOT(setLoopFlag(int)));
QObject::connect(&wxcpp,SIGNAL(getValFromJs(QVariant)),this,SLOT(on_getValueFromJs(QVariant)));
QObject::connect(&wxcpp,SIGNAL(scriptFinished()),this,SLOT(on_scriptFinished()));
QObject::connect(ui->spinBox,SIGNAL(valueChanged(int)),&wxcpp,SLOT(setLoopInterval(int)));
}
WxForm::~WxForm()
{
if(ui->scriptExecType_comboBox->currentIndex() == 1){ // 优雅的结束线程
qDebug() << __FUNCTION__;
wxcpp.setLoopFlag(0);
#ifdef Q_OS_WIN
::Sleep(1000);
#else
sleep(1);
#endif
}
delete ui;
}
void WxForm::on_openScript_pushButton_clicked()
{
QString fileName = QFileDialog::getOpenFileName(this,
tr("Open Script"), qApp->applicationDirPath(), tr("Script Files (*.js *.py *.lua)"));
ui->scriptPath_lineEdit->setText(fileName);
m_curScriptFilePath = fileName;
showScriptContext(fileName);
}
bool WxForm::showScriptContext(QString fileName)
{
QMutex mutex;
mutex.lock();
QFile file(fileName);
if(!file.open(QIODevice::Text | QIODevice::ReadWrite)){
mutex.unlock();
return false;
}
ui->plainTextEdit->clear();
ui->plainTextEdit->appendPlainText(file.readAll());
file.close();
mutex.unlock();
return true;
}
void WxForm::on_execScript_pushButton_clicked()
{
if(m_curScriptFilePath.isEmpty()){
ui->textEdit->append("[error]: Please select one javaScript file at first!");
return;
}
ui->execScript_pushButton->setEnabled(false);
ui->scriptType_comboBox->setEnabled(false);
ui->openScript_pushButton->setEnabled(false);
wxcpp.setFileName(m_curScriptFilePath);
wxcpp.setValue(m_ret);
wxcpp.setLoopFlag(ui->scriptExecType_comboBox->currentIndex());
wxcpp.setLoopInterval(ui->spinBox->value());
wxcpp.setScriptType(ui->scriptType_comboBox->currentIndex());
wxcpp.start();
}
void WxForm::on_getValueFromJs(QVariant variant)
{
m_ret = variant;
ui->textEdit->append(QString("[javaScript]: %1").arg(m_ret.toInt()));
}
void WxForm::on_closeScriptExec_pushButton_clicked()
{
wxcpp.setLoopFlag(0);
}
void saveTextToFile(QString& text,QString filename){
QMutex mutex;
mutex.lock();
QFile file(filename);
if(!file.open(QIODevice::Text | QIODevice::WriteOnly)) {
mutex.unlock();
return;
}
QTextStream os(&file);
os << text;
file.close();
mutex.unlock();
}
void WxForm::on_save_pushButton_clicked()
{
QString context = ui->plainTextEdit->toPlainText();
saveTextToFile(context,m_curScriptFilePath);
}
void WxForm::on_saveAs_pushButton_clicked()
{
QString context = ui->plainTextEdit->toPlainText();
QString fileName = QFileDialog::getSaveFileName(this,
tr("Save Script"), qApp->applicationDirPath(), tr("Script Files (*.js *.py *.lua)"));
if(fileName.isEmpty()) return;
saveTextToFile(context,fileName);
}
void WxForm::on_scriptFinished()
{
ui->execScript_pushButton->setEnabled(true);
ui->scriptType_comboBox->setEnabled(true);
ui->openScript_pushButton->setEnabled(true);
}
脚本执行部分,放在独立的线程中执行,线程中将执行结果传递给GUI主线程,并在输出控件中显示输出结果。
// wxcpp.h
#ifndef WXCPP_H
#define WXCPP_H
#include
#include
#include
#include
class WxCPP : public QThread
{
Q_OBJECT
public:
WxCPP(QObject *parent = 0);
~WxCPP();
void run();
static int loadJavaScript(QString filename, QVariant &ret);
void setFileName(QString filename){ m_fileName = filename; }
void setValue(QVariant& variant){ m_variant = variant;}
void setScriptType(int type = 0){ m_scriptype = type;}
public slots:
void setLoopFlag(int flag = 0){ m_execFlag = flag;}
void setLoopInterval(int interval = 1000){m_interval = interval ;}
signals:
void getValFromJs(QVariant variant);
void scriptFinished();
public:
QString m_fileName;
QVariant m_variant;
int m_scriptype;
int m_execFlag;
int m_interval;
};
#endif // WXCPP_H
// wxcpp.cpp
#include "wxcpp.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef Q_OS_WIN
#include
#endif
#include
WxCPP::WxCPP(QObject *parent)
: QThread(parent)
{
}
WxCPP::~WxCPP()
{
}
void WxCPP::run()
{
do{
switch (m_scriptype) {
case 0: // javascript
loadJavaScript(m_fileName,m_variant);
break;
default:
break;
}
emit getValFromJs(m_variant);
if(m_execFlag){
#ifdef Q_OS_WIN
::Sleep(m_interval);
#else
sleep(m_interval/1000);
#endif
}
}while(m_execFlag);
emit scriptFinished();
}
int WxCPP::loadJavaScript(QString filename,QVariant& ret)
{
QScriptEngine engine;
engine.installTranslatorFunctions();
//! [1]
//! [2]
QScriptValue iValue = engine.newVariant(ret);
engine.globalObject().setProperty("i", iValue);
//! [2]
//! [3]
QMutex mutex;
mutex.lock();
QFile scriptFile(filename);
scriptFile.open(QIODevice::ReadOnly);
QTextStream stream(&scriptFile);
QString contents = stream.readAll();
scriptFile.close();
mutex.unlock();
//! [3]
//! [4]
QScriptValue result = engine.evaluate(contents, filename);
ret = result.toVariant();
qDebug() << "javaScript exec result:" << ret.toInt();
//! [4]
//! [5]
if (result.isError()) {
QMessageBox::critical(0, "Java Script",
QString::fromLatin1("%0:%1: %2")
.arg(filename)
.arg(result.property("lineNumber").toInt32())
.arg(result.toString()));
return -1;
}
engine.abortEvaluation();
}
1、本篇核心为JS脚本拓展CPP应用程序,即CPP应用程序(后面简称应用程序)在运行过程中,可动态检测js脚本,执行脚本文件,并将执行结果传递给CPP程序。本文以传递一个QVariant类型的变量为例,实际上QtScript模块可用于与JS交互的还可以是一个对象(包括自定义的继承于QObject类的对象),并在JS中对对象进行操作。
2、拓展脚本的运行我们考虑放在独立的线程中执行,可以避免主GUI线程卡界面的问题,软件更加流畅,效率更好。其次实现脚本的两种运行模式:普通脚本(单次执行),循环脚本(指定间隔时间循环执行),状态可动态切换,这里也运用了主流的优雅开启和关闭Qt线程的方法之一。
3、JS脚本动态修改,程序动态执行和运行。即可以在脚本运行过程中修改脚本内容,保存后,将自动执行最新的脚本程序。这里其实稍微重要的地方就是对文件的读写加锁。其他的小细节不做过多介绍了。有需要的朋友可以自行学习涉及到的功能模块,或者就相关问题留言。
下篇,我们接着讲如何用python拓展CPP应用程序。感兴趣小伙伴➕关注,喜欢的话也请点个赞。